Android Lint 是构建工具链中默认启用的一套静态代码分析器,它能在编译前扫描源码和资源,发现潜在崩溃、性能缺陷、API 兼容性问题。内置的规则覆盖了常见场景,但团队的约束往往更具体,例如禁止直接使用 android.util.Log、强制某个初始化调用顺序、要求特定组件继承统一基类。这些规则无法靠扩展配置文件实现,只能通过自定义 Lint 规则把检查逻辑注入到分析引擎中。自定义规则本质上就是实现一个 Detector,声明一个 Issue,再通过 Registry 注册给 Lint。

一、Lint 规则体系的三个核心对象
理解自定义规则之前,需要先分清 Issue、Detector 和 IssueRegistry 的职责。Issue 是一个规则的元数据描述,包含 id、摘要、详细说明、严重级别、所属类别和优先级。Detector 是检查逻辑的载体,它继承自 Detector 基类并实现特定接口,例如 SourceCodeScanner 表示源码级别扫描,XmlScanner 表示资源文件扫描,ResourceFolderScope 相关接口可处理文件路径。IssueRegistry 则负责把多个 Issue 收集起来,让 Lint 工具能够发现并执行它们。
Lint 分析源码时并不是直接读取纯文本,而是先通过 IDE 的编译器前端把 Kotlin 或 Java 源码解析成 UAST 抽象语法树。UAST 统一了 Kotlin 和 Java 的结构,因此一个 Detector 可以同时处理两种语言。在扫描阶段,Lint 会按访问者模式调用 Detector 中的回调方法。如果你的规则只关心方法调用,可以实现 SourceCodeScanner 接口里的 getApplicableMethodNames 返回方法名列表,再在 visitMethodCall 中判断调用点;如果关心类继承,则实现 applicableSuperClasses 和 visitClass 回调。理解这些回调的触发时机,能避免写出遍历整个文件的低效逻辑。
此外,Detector 可以通过 JavaContext 或 XmlContext 获取上下文信息。JavaContext 提供 getEvaluator、isEnabled、report 等方法,其中 report 是输出检测结果的关键入口。它会自动关联代码位置、变量名、错误信息和快速修复建议。Lint 还支持 oldApi 抑制机制,但自定义规则默认会参与检查。
二、编写一个禁止直接使用 Log 的规则
这里以一个常见约束为例:业务代码中禁止直接调用 android.util.Log,需要统一使用封装后的 Logger。首先在 Gradle 模块中引入 lint-api 和 lint-checks 依赖。如果只是为了编译规则,lint-api 已经足够;而 lint-checks 提供了很多内置检测器的完整实现,可以作为学习参考。推荐把规则放在独立的 Java 或 Kotlin library module 中,这样不会污染主工程依赖。
dependencies {
compileOnly 'com.android.tools.lint:lint-api:31.1.0'
compileOnly 'com.android.tools.lint:lint-checks:31.1.0'
}
接下来定义 Issue。Issue 的 id 建议使用驼峰命名,并且不要和系统规则冲突。严重级别有三个常用选项:Severity.ERROR、Severity.WARNING 和 Severity.SEVERITY_INFORMATIONAL。禁止直接使用 Log 可以选择 WARNING,但如果是强制规范,可以设为 ERROR。说明文案要清晰,让开发者在报告里直接明白修改方向。以下 Issue 定义放在一个 object 或类中:
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.Scope
object LogUsageIssue {
@JvmField
val ISSUE = Issue.create(
id = "DirectLogUsage",
briefDescription = "禁止直接使用 android.util.Log",
explanation = "请使用统一日志组件 Logger 替代 android.util.Log,以便控制日志开关和格式化。",
category = Category.CORRECTNESS,
priority = 6,
severity = Severity.WARNING,
implementation = Implementation(
LogUsageDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
}
上面的代码用 @JvmField 暴露给 Lint 框架,因为 Issue.create 的 implementation 参数需要指向 Detector 类。Scope.JAVA_FILE_SCOPE 表示只扫描 Java 或 Kotlin 源文件。LogUsageDetector 的职责是找到所有调用 android.util.Log 的方法,然后报告。实现如下:
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.SourceCodeScanner
import org.jetbrains.uast.UCallExpression
class LogUsageDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> {
return listOf("d", "e", "i", "v", "w", "wtf")
}
override fun visitMethodCall(context: JavaContext, node: UCallExpression) {
val evaluator = context.evaluator
if (!evaluator.isMemberInClass(node, "android.util.Log")) {
return
}
val message = "请使用 Logger 工具类代替 android.util.Log"
context.report(
issue = LogUsageIssue.ISSUE,
scopeClass = node,
location = context.getCallLocation(node, includeReceiver = true, includeArguments = false),
message = message
)
}
}
这个检测器只对方法名列表做初筛,再通过 evaluator.isMemberInClass 判断调用是否来自 android.util.Log。这样做既能避免字符串误判,也能保持高效。getCallLocation 会给出调用表达式的精确位置,includeReceiver 保留接收者,方便报告中显示 Log.d 这样的完整调用。如果需要在报告中携带修复建议,可以调用 report 的重载方法,传入 LintFix 对象。LintFix 支持替换文本、删除节点等操作,对于这种规则可以给出替换为 Logger.d 的建议。
Detector 编写完成后,还需要 IssueRegistry 将 Issue 暴露给 Lint。新建一个类继承 IssueRegistry,并重写 issues 与 api 属性。注意 Registry 必须有无参构造器,Lint 通过反射实例化它:
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.detector.api.Issue
class LogIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(LogUsageIssue.ISSUE)
override val api: Int
get() = com.android.tools.lint.detector.api.ApiKt.CURRENT_API
}
api 属性返回当前 Lint API 版本,确保代码和 Lint 工具兼容。如果使用较新的 lint-api,可以直接引用 CURRENT_API。完成后把 registry 声明在 module 的 MANIFEST 中或通过 Gradle 的 lintOptions 配置,但最方便的是把该模块作为 lintChecks 依赖。
三、用测试框架验证规则
静态检查规则如果只在真实项目里跑一次,调试成本很高。Lint 提供了专门的测试库 lint-tests,可以让规则在 JVM 上快速执行。先添加测试依赖:
dependencies {
testImplementation 'com.android.tools.lint:lint-tests:31.1.0'
testImplementation 'junit:junit:4.13.2'
}
测试类继承 LintDetectorTest,并实现 getDetector、getIssues 方法。测试用例通常使用 lint().files(...).run().expect(...) 的流式 API。files 方法可以传入内联源码,Lint 会把字符串当作虚拟文件扫描。下面的例子分别验证违规代码被报告,以及合规代码不产生警告:
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
class LogUsageDetectorTest : LintDetectorTest() {
fun testLogUsageDetected() {
lint()
.files(
kotlin(
"""
package com.example.app
import android.util.Log
class Sample {
fun printMessage(message: String) {
Log.d("Sample", message)
}
}
""".trimIndent()
)
)
.issues(LogUsageIssue.ISSUE)
.run()
.expect("Warning: 请使用 Logger 工具类代替 android.util.Log")
}
override fun getDetector(): Detector = LogUsageDetector()
override fun getIssues(): List<Issue> = listOf(LogUsageIssue.ISSUE)
}
测试代码里的 kotlin 辅助方法会把字符串标记为 Kotlin 源码。如果规则只关注 Java,可以换成 java 方法。expect 接受的是报告中出现的描述文本,可以写多个 expect 参数来匹配多行输出。也可以使用 expectClean() 验证输入文件没有任何报告。通过单元测试,可以快速验证规则对正常调用、完全限定名、继承关系、注释抑制等场景的反应,也可以防止后续重构破坏规则。
除了单测,还可以利用 Lint 的基线功能。如果团队希望新规则只对新增代码生效,可以先生成 lint-baseline.xml,让既有违规暂时被抑制,后续提交时逐步清理。基线的意义不是逃避问题,而是避免历史债务阻塞新代码的检查。
四、集成进 Gradle 并生成报告
把自定义规则模块发布为本地 Maven 依赖,或者直接以 project 依赖引入主工程。Gradle 配置示例如下:
dependencies {
lintChecks project(':lint-rules')
}
lintChecks 是 AGP 提供的专用配置,它会把模块中的 Registry 和 Issue 加入 Lint 扫描的规则列表,但不会把这些类打包进 APK,因为它是 compileOnly。执行 ./gradlew lint 后,控制台会输出违规数量,HTML 报告默认生成在 build/reports/lint-results-debug.html。如果使用的是 Kotlin DSL,对应配置为 lintChecks(project(":lint-rules"))。对于多模块工程,lintChecks 需要添加到所有希望执行该规则的 module,或者通过根项目的统一配置实现。
运行时如果发现规则没有生效,优先检查三点:Registry 是否被正确反射加载;Issue 的 Scope 是否包含当前扫描的范围;模块的 lintChecks 配置是否放在了正确的位置。你可以用 --enable 参数强制启用规则,或者用 --check 指定只运行某一个规则。Lint 提供了丰富的命令行参数,例如 ./gradlew lintDebug --check DirectLogUsage,能快速验证规则是否被识别。如果使用 IDE 实时检查,还需要将规则模块发布到本地 Maven,并在 Android Studio 的设置中启用 Lint 的自定义规则仓库。
自定义 Lint 规则不是一次性脚本,而是团队规范的技术载体。为了长期维护,建议把规则和对应的单元测试放在同一个模块,遵循清晰的 Issue id 命名,并在说明中附上修改示例。规则稳定后,可逐步增加快速修复和基线迁移策略,让静态检查真正成为代码评审的前置门槛。
Android Lint自定义规则代码检查修改时间:2026-10-05 14:04:47