preface

Lint is a static code scanning tool built into Android Studio that checks Android project source files for potential errors and needs to be optimized for correctness, security, performance, ease of use, convenience, and internationalization. AS already has a lot of Lint checking rules built in, but when we need to customize rules, we need to consider custom Lint.

First, think carefully about when you need to use custom Lint.

Use your imagination, I originally wanted to push WebP as an alternative to PNG, which would require developers to consciously convert PNG to WebP every time, but it was impossible to pair program and supervise the conversion, so custom Lint came in handy.

However, the vast majority of blogs on the web are still stuck with the AS 2.x Lint API, and there is almost nothing about the AS 3.x Lint API. From AS 2.x to AS 3.x, Lint API has changed a lot, especially for detecting Java source files, from JavaScanner to JavaPsiScanner to UastScanner, The way custom Lint is registered and referenced has also changed. This article was also gleaned from a Google Sample reference system’s built-in Lint implementation.

Here are the custom Lint implementations in the project:

What is the use of the above Detector?

ToastDetector: Android’s built-in Lint rule is used to remind Toast to forget to call the show method. In the BuiltinIssueRegistry class you can view all the built-in Detector, which is also the main reference for me to implement custom Lint.

SampleCodeDetector: Demo in Google Sample that detects whether a text expression contains a specific string.

PngDetector: Used to detect PNG resources referenced in all layouts or Java files, prompting webP.

LogDetector: Used to detect Log output methods such as I, D, and E using the Log class. Prompts you to use a unified Log tool class. The severity is Error, so the compilation process is interrupted by default. If the severity is Warning, an amber Warning is generated.

ThreadDetector: Used to detect threads created directly through new Threads, indicating that a unified Thread pool should be used.

Source code address: github.com/Omooo/Custo…

The body of the

To see how a simple custom Lint can be written step by step, this example is actually from github.com/googlesampl… , the Rep is a SampleCodeDetector, as mentioned above.

There are four steps to customizing Lint:

Step 1: Create the Java Library project

Add dependencies to build.gradle:

compileOnly "Com. Android. Tools. Lint: lint - API: 26.4.1"
compileOnly "Com. Android. Tools. Lint: lint - checks: 26.4.1"Copy the code

Step 2: Create the Detector

Public Class SampleDetector implements Detector.UastScanner { Define ISSUE public static final ISSUE ISSUE = issue.create ("ShortUniqueId"// Unique ID"Lint Mentions"// A brief description"Blah blah blah.", // detail description of Category.CORRECTNESS, safety, etc. 6, // weight severity.warning, // new Implementation(// Including process instance and Scope SampleCodeDetector. Class Scope. JAVA_FILE_SCOPE)); @override public List<Class<? Override public List<Class<? extends UElement>>getApplicableUastTypes() {
        return Collections.singletonList(ULiteralExpression.class);
    }
​
    @Override
    public UElementHandler createUastHandler(@NotNull JavaContext context) {
        return new UElementHandler() {
            @Override
            public void visitLiteralExpression(@NotNull ULiteralExpression expression) {
                String string = UastLiteralUtils.getValueIfStringLiteral(expression);
                if (string == null) {
                    return;
                }
                if (string.contains("Omooo") && string.matches(".*\\bOmooo\\b.*"// ISSUE Context. report(ISSUE, expression, context.getLocation(expression),"This code mentions `Omooo`"); }}}; }}Copy the code

There are a number of scanners built into the Lint API:

Scanner type Desc
UastScanner Scan Java and Kotlin source files
XmlScanner Scanning XML files
ResourceFolderScanner Scanning resource folders
ClassScanner Scanning Class files
BinaryResourceScanner Scan binary resource files

More, please reference: static. The javadoc. IO/com. Android…

Note:

One thing to note here is that if the corresponding ISSUE has a severence.error, the compilation process is broken by default, although you can also configure LintOptions to suppress Lint errors.

Step 3: Register Detector

public class CustomIssueRegistry extends IssueRegistry {
​
    @NotNull
    @Override
    public List<Issue> getIssues() {
        return Arrays.asList(
                SampleCodeDetector.ISSUE);
    }
​
    @Override
    public int getApi() {
        returnApiKt.CURRENT_API; }}Copy the code

Multiple detectors can be registered here, and the latest version of Lint has 360 built-in detectors, all in the BuiltinIssueRegistry class, which serves as a good reference case for us to write our own custom Lint.

Step 4: Introduce custom Lint

Gradle file in lint_library. The complete code is as follows:

apply plugin: 'java-library'
​
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
​
    compileOnly "Com. Android. Tools. Lint: lint - API: 26.4.1"
    compileOnly "Com. Android. Tools. Lint: lint - checks: 26.4.1"
}
​
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
​
jar {
    manifest {
        attributes("Lint-Registry-v2": "top.omooo.lint_library.CustomIssueRegistry")}}Copy the code

Build. Gradle in your App Module:

dependencies {
    //...
    lintChecks project(":lint_library")}Copy the code

Lint advanced

Above, a simple custom Lint is done, but it feels like a bit more than enough. That’s when you need to use your imagination and think about what you need. You can check out the source code I gave you, or check out the built-in Android Lint source code to see what they can do.

This section is very important, but I will not tell you how to implement such and such functionality, look at the source code to learn, because it is really not difficult wow.

The last

If you are lazy and tired of typing./gradlew lint every time to check the output of Lint, you can mount the Debug task before installing the Debug package:

/** * Mount lintDebug before executing assembleDebugTask */ project.afterevaluate {def assembleDebugTask = project.tasks.find { it.name =='assembleDebug' }
    def lintTask = project.tasks.find { it.name == 'lintDebug' }
    assembleDebugTask.dependsOn(lintTask)
}Copy the code

(escape ~