Lint 简单介绍

更新时间:2016-04-02

简介

Android 的 Lint 工具是一种静态的代码检查工具,作为 SDK Tools (版本号 >= 16)的一部分自动安装,用于帮助发现潜在的 BUG ,提高代码的兼容性和性能。 在 Android Studio 中,可以点击 Analyze > Inspect Code 运行。正是 Lint 这一点,在我第一次接触 Android Studio 时,觉得不可思议的好用。 效果图:

Lint 效果图

另外,要是觉得提示的程度太显眼了,你可以点击 Android Studio 右下角的老头:
调整高亮程度

一张图概括过程

lint_workflow

配置

当需要关闭 Lint 时, 可使用 annotation 和 attribute 来设置。

  • 对于 Java 中的类或方法,使用 @SuppressLint ,忽略全部则使用关键字 @SuppressLint("all")
// 进行关于 “NewApi” 的检查时忽略 onCreate() 方法  
@SuppressLint("NewApi")
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    //...
}
  • 对于 XML ,使用 tools:ignore ,忽略全部则使用关键字 tools:ignore="all"
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    <!--忽略两个以上的检查-->
    tools:ignore="UnusedResources,NewApi" >

    <TextView
        android:text="@string/auto_update_prompt" />
</LinearLayout>

另一个方式是通过在设置项中的 lint.xml 文件。如果是自己手动添加的,需要将 lint.xml 文件放置到项目的根目录下;在 Android Studio 中会自动添加。
lint.xml 文件以 lint 为父节点,包含多个 issue 子节点,每个 issue 有一个唯一的 id 属性。 举个栗子:

<?xml version="1.0" encoding="UTF-8"?>
<lint>
    <!-- 关闭在这个工程所给定的检查 -->
    <issue id="IconMissingDensityFolder" severity="ignore" />

    <!-- 忽略 ObsoleteLayoutParam issue 在相应的文件中 -->
    <issue id="ObsoleteLayoutParam">
        <ignore path="res/layout/activation.xml" />
        <ignore path="res/layout-xlarge/activation.xml" />
    </issue>

    <!-- 忽略 UselessLeaf issue 在相应的文件中 -->
    <issue id="UselessLeaf">
        <ignore path="res/layout/main.xml" />
    </issue>

    <!-- 将 硬编码 String 的严重程度提升为 "error" -->
    <issue id="HardcodedText" severity="error" />
</lint>

自定义 Lint

现在 Android Studio 已经自带超过 200 条的检查。但有时候我们需要添加自己的 Lint 规则。 每个 Link 都包含以下四个部分:

  • Issue :定义可能存在于 Android 项目中的问题或 BUG 。每次 Lint 运行时都会针对这些问题去检查,并报告发现的问题。
  • Detectors :用于扫描你的代码,寻找 Issues。
  • Implementations :链接了一个具体的 Detectors 类和相关的 Issues。可以告诉 Detectors 可以针对性的扫描某些区域。
  • Registries :维护一张包含所有将会扫描的 Issues 的表。默认 Lint 中涉及到的 Issues 都在 BuiltinIssueRegistry 类中。

Lint 结构图

创建 Java 工程,配置 Gradle

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.tools.lint:lint-api:24.5.0'
    compile 'com.android.tools.lint:lint-checks:24.5.0'
}

lint-api :官方给出的 API ,不过不是最终版,官方提醒随时都有可能改变接口。
lint-checks :已有的检查

创建 Detector

一个简单的例子:

public class MyDetector extends ResourceXmlDetector {
    public static final Issue ISSUE = Issue.create(
            "MyId",
            "My brief summary of the issue",
            "My longer explanation of the issue",
            Category.CORRECTNESS, 6, Severity.WARNING,
            new Implementation(MyDetector.class, Scope.RESOURCE_FILE_SCOPE));
    
    @Override
    public Collection<String> getApplicableElements() {
        return Collections.singletonList(
                "com.google.io.demo.MyCustomView");
    }

    @Override
    public void visitElement(XmlContext context, Element element) {
        if (!element.hasAttributeNS(
                "http://schemas.android.com/apk/res/com.google.io.demo",
                "exampleString")) {
            context.report(ISSUE, element, context.getLocation(element),
                    "Missing required attribute 'exampleString'");
        }
    }
}

其中 Implementtation 包括两个部分:

  1. Detector Class :前面的例子就是 MyDetector.class,这个类继承自 ResourceXmlDetector,实际上这个类内部是继承自 Detector 和实现了 Detector 的 Scanner 接口 ,所以根据自己的需求针对性的选择实现相应的接口,例如:
// 针对 Java 文件的检查
public class JavaDetector extends Detector implements Detector.JavaScanner {}

Lint 使用了 Lombok 做抽象语法树的分析,而不是一行一行代码地去找。所以在我们需要告诉它所需的类型后, 它就会把相应的 Node 返回给我们。

  1. Scope Set :定义所要扫描的范围,针对需要选择。 可选择的有 Scope.JAVA_FILE_SCOPE, Scope.MANIFEST_SCOPE 等。查看更多范围:传送门

Issue 部分包含:

public static final Issue ISSUE = Issue.create(
    ISSUE_ID,                 // 唯一的标识符,不能为 null
    ISSUE_DESCRIPTION,        // 简略描述
    ISSUE_EXPLANATION,        // 详细描述
    ISSUE_CATEGORY,           // 类别名
    ISSUE_PRIORITY,           // 优先级[1,10],数字越大越重要
    ISSUE_SEVERITY,           // 严重程度,一般为 WARNING :程序还能继续运行,还有 Fatal,ERROR,Informational, Ignore 可选择
    IMPLEMENTATION            // 前面讲过的
);

IssueRegistry

最后需要注册,本例中只有 Mydetector.ISSUE 这一个。

public class MyIssueRegistry extends IssueRegistry {
    public MyIssueRegistry() {
    }

    @Override
    public List<Issue> getIssues() {
        return Collections.singletonList(
                MyDetector.ISSUE
        );
    }
}

getIssue() 方法中返回需要被检测的 Issue List。 在 Build.gradle 中补充声明 Lint-Registy 属性

jar {
    manifest {
        attributes("Lint-Registry": "包名.MyIssueRegistry")
    }
}

Jar 包使用

自定义Lint是一个Java工程,那么打出的jar包如何使用呢?以下介绍两种方案:

  1. Google 方案:将 jar 拷贝到 ~/.android/lint 中
$ mkdir ~/.android/lint/
$ cp customrule.jar ~/.android/lint/

缺点:针对所有工程,会影响同一台机器其他工程的 Lint 检查。即使触发工程时拷贝过去,执行完删除,但其他进程或线程使用 ./gradlew lint 仍会受到影响。

  1. LinkedIn 方案:将 jar 放到一个 arr 中。这样我们就可以针对工程进行自定义 Lint, lint.jar 只对当前工程有效。具体介绍:传送门

写在最后

本文写得比较简略,要是细节进一步了解可以去看下文末的“参考”。
Lint 还支持通过命令行来执行,感兴趣的可以去看看:传送门

参考:

Tags

Lint

Tags

Lint