preface

In the process of development, we have more or less encountered the following prompt:

Write code that can go wrong:

Using an outdated API:

XML uses Chinese directly

On top of that, there are plenty of IDE alerts that let us see if our code is up to scratch and if there are any potential risks. These tips are all feedback from AndroidStudio’s built-in Lint tool after it checks our code. Of course, we can also configure to eliminate the above prompt. So what is Lint? Can we define our own rules to check and give feedback on the code we write?

What is Lint?

define

Android Lint is a code scanning tool provided by ADT (Android Developer Tools) 16. It can help you identify problems with resources, code structure, and check for improvements in correctness, security, performance, ease of use, convenience, and internationalization. The system reports the detected problems and provides a description and severity level to quickly identify the changes that need to be prioritized.

AS already has a number of built-in Lint checks, but when we need to customize rules, we need to customize Lint.

Ban check

I see so many prompt information, very unhappy, but I do not want to change, how to do?

Usually, for red errors, the suggestion is to correct them, but if you really don’t want to change them, you can comment them out.

With the @SuppressLint annotation, those pesky red error messages are gone.

XML? Can it be eliminated?

1. Declare the namespacenamespace xmlns:tools="http://schemas.android.com/tools" 2. Use it in layouts

Lint works by:

The figure above shows the workflow of the Lint tool. Here’s a look at the concepts.

  • App Source Files: Contains the Files that make up the Android project, such as Java, Kotlin, XML, ICONS, Proguard configuration Files, etc
  • Lint.xml: a configuration file that you can use to specify any Lint checks you want to exclude and to customize the severity level of the problem.
  • Lint Tool: a static code scanning Tool that can be run against projects from the command line or from AS
  • Lint Output: Lint Inspection Results can be viewed on the console or in the Android Studio Inspection Results window

The Lint process consists of three parts: The Lint Tool (detection Tool), the Source Files (project Source Files), and the Lint.xml (configuration file). The Lint Tool reads the Source Files. Output the final result according to the rules configured in Lint.xml (issue).

lint.xml

The lint. XML file is created in the root directory of the project. The lint. XML file is created in the root directory of the project. Format is as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <lint> <! -- Disable the given check in this project --> <issue id="IconMissingDensityFolder" severity="ignore" /> <! - ignore ObsoleteLayoutParam problem in the specified file - > < issue id = "ObsoleteLayoutParam" > < ignore path = "res/layout/activation. XML" / > <ignore path="res/layout-xlarge/activation.xml" /> </issue> <! <issue ID ="UselessLeaf"> <issue id="UselessLeaf"> </issue> <! -- Change the severity of the hard-coded string to "error" --> < Issue ID ="HardcodedText" severity="error" /> </lint>Copy the code

Run the lint

The command line runs Lint

If you are using Android Studio, you can call the Lint task on your project using Gradle wrapper by typing one of the following commands in the project’s root directory:

  • On Windowsgradlew lint
  • On Linux or Mac./gradlew lint
> Task :app:lint Ran lint on variant release: 9 issues found Ran lint on variant debug: 9 issues found / / path generated report demonstrate the HTML report to file:///D:/lintlearn/app/build/reports/lint-results.html demonstrate XML report to file:///D:/lintlearn/app/build/reports/lint-results.xmlCopy the code

Lint is used in Android Studio

From the menu bar, select Analyze > Inspect Code

Select check range

And then you can see the results

On the left is the problem category and on the right is the specific problem

What issues Lint looks at

Lint concerns

  • Correctness: e.g. hard coding, using outdated apis, etc
  • Security: for example, JavaScriptInterface is allowed in WebView
  • Performance: Encoding that has an impact, such as static references, circular references, etc
  • Usability: There are better alternatives such as layout, icon format suggestions, PNG format, etc
  • Accessibility: For example, the contentDescription of an ImageView is often suggested in an attribute
  • Internationalization: using Chinese characters directly without using resource references, etc

Lint Problem level

  • Fatal: An error of this type, which will interrupt the ADT export to APK directly
  • Error: Specifies the errors that need to be resolved, including Crash, clear bugs, serious performance problems, and code specifications noncompliance. The errors must be fixed.
  • Warning: Warning, which includes code suggestions, possible bugs, and performance optimizations that may be a potential problem
  • Informational: There may be no problem, but the check found that there are some sayings about the code
  • Ignore: The user does not want to see this problem

Lint related apis, go through them quickly

Import Lint

To make this easier, import the Lint API and add dependencies to build.gradle to see the related projects in the dependency package

Dependencies {implementation "com. Android. Tools. Lint: lint - API: 27.2.1" implementation "Com. Android. Tools. Lint: lint - checks: 27.2.1"}Copy the code

  • Com. Android. Tools. Lint: lint – API: this package provides the lint API, including the Context, the Project, the Detector, Issue, IssueRegistry, etc
  • Com. Android. Tools. Lint: lint – checks: this package implements the android native lint rules.

Custom Lint development requires calling the apis provided by Lint. The main ones are as follows.

Issue:

Represents a Lint rule. Each Issue has an ID, and this ID is unique

Public static final Issue Issue = issue.create ("Id", // unique Id, brief surface of the current problem "briefDescription", // Describe the current problem briefly "explanation",// explain the current problem in detail and the proposed fix, Category. Severity.ERROR,// Severity: FATAL (collapse), ERROR, WARNING,INFORMATIONAL,IGNORE new Implementation(LogDetector. Class, Scope.java_file_scope)// Which Detector the Issue is bound to, and declare the Scope to be checked);Copy the code
  • Scope: Declares the Scope of code to be scanned by the Detector, such as Java source file, XML resource file, Gradle file, etc. Each Issue can contain multiple scopes.

IssueRegistry (registry) :

Use to register a list of issues to check. Custom Lint needs to generate a JAR file whose Manifest points to the IssueRegistry class.

Public Class MyIssueRegistry extends IssueRegistry {@notnull @override public class MyIssueRegistry extends IssueRegistry {@notnull @override Public List<Issue> getIssues() {return arrays.aslist (logArrays.issue); }}Copy the code

The Detector:

Use to detect and report issues in code. Each Issue contains an Detector. The process of customizing Lint is to override the methods associated with the Detector.

Public class LogDetector extends Detector implements Detector.UastScanner {.... }Copy the code

Take a look at some of the methods we’ll be using next, and how queries and checks map:

  • getApplicableUastTypes(): This method returns the type of the AST node to be checked. Uelements matching the type will be checked by the UElementHandler(Visitor) created by createUastHandler(createJavaVisitor).
  • CreateUastHandler (): Create a uastHandler to check for the UElement that needs to be checked, which is the UElement returned by getApplicableUastTypes
  • GetApplicableMethodNames (): Returns a list of the method names you need to check, which will be checked through the visitMethod method
  • VisitMethod (): Checks the method that matches getApplicableMethodNames
  • GetApplicableConstructorTypes () : returns a list of a constructor which needs to be checked, type matching method will be checked by visitConstructor methods
  • VisitConstructor () : check getApplicableConstructorTypes matching constructor
  • GetApplicableReferenceNames () : returns need to check the reference path name, matching reference will be check by visitReference
  • Matching with the getApplicableReferenceNames visitReference () : check the reference path
  • AppliesToResourceRefs (): Returns the referenced resource to be checked, which is checked by visitResourceReference
  • VisitResourceReference (): Checks the resources that match appliesToResourceRefs
  • ApplicableSuperClasses (): Returns a list of superclass names to check
  • VisitClass (): Checks the class returned by applicableSuperClasses

These methods are very important, and we’re going to have to use them in a second, different rules and different ways to implement them.

Scanner:

Through the above code, we can see in addition to the inheritance of Detecor, but also to achieve xxxScanner, Scanner is what to use? It is used to scan and find issues in code. Each Detector can implement one or more scanners. A major part of the custom Lint development process is implementing the Scanner.

  • UastScanner: scans Java and Kotlin source files
  • XmlScanner: scans XML files
  • ResourceFolderScanner: scans resource files
  • ClassScanner: scans class files
  • BinaryResourceScanner: scans binary files
  • ResourceFolderScanner: scans a resource folder
  • GradleScanner: Scans Gradle scripts
  • OtherFileScanner: scans files of other types

JavaScanner and JavaPsiScanner have been replaced by UastScanner. The Scanner that scans Java source files has experienced three versions.

  1. Starting with JavaScanner, Lint parses Java source code through Lombok libraries into an AST (abstract syntax tree), which is then scanned by JavaScanner
  2. In Android Studio 2.2 and Lint-API 25.2.0, Lint replaced Lombok AST with PSI(Program Structure Interface, a set of apis for parsing code in IDEA), JavaScanner is deprecated and JavaPsiScanner is recommended
  3. In Android Studio 3.0 and Lint-API version 25.4.0,Lint replaced PSI with UAST (General Abstract Syntax Tree) and recommended using UastScanner. UAST is more syntactically independent and supports both Java and Kotlin. UAST nodes are essentially supersets of Java and Kotlin support. When you write rules using UAST, your Lint rules apply to both Java and Kotlin files without having to write two sets of rules. You can also view gradle files and XML files.

Customize Lint practices

Now that we have a general idea of the API used to customize a Lint rule, let’s do it in action.

We create a Log lint rule for the project if usedLog.d("",""); //e,v,w,wtfSuch log printing prompts the need to use the methods in the LogUtil utility class integrated with the project.

Create a Java library

Custom rules need to be created in a Java project, where we create a Java Library and add dependencies

Define your own Detector

There are roughly four steps

  1. We are defining Lint to detect Java code level, so implementDetector.UastScanner
  2. Create an Issue that defines the level, prompt, weight, scope, and so on
  3. Implement the required method override logic (getApplicableUastTypes gets the node, createUastHandler checks)
  4. Those who meet the conditions passcontext.report()Method to report an Issue
Class MyDetector extends Detector implements Detector.UastScanner {//2, ISSUE public Static final Issue Issue = issue.create ("Log Use Error", // Unique ID This ID must be unique "please Use LogUtil", // Simple description "Please use LogUtil!!" , // detail description Category.CORRECTNESS, // CORRECTNESS 6, // Priority of weight, must be between 1 and 10. New Implementation(// this is the bridge between Detector and Scope, where Detector's function is to look for issues, The scope defines the scope within which to find the issue LogDetector. Class, scope.java_file_scope); // This method returns the type of the AST node to be checked. Matching UElements will be checked by the UElementHandler(Visitor) created by createUastHandler(createJavaVisitor). @Nullable @Override public List<Class<? extends UElement>> getApplicableUastTypes() { return Collections.singletonList(UCallExpression.class); } //3, create a uastHandler to check UElement @nullable @Override public UElementHandler createUastHandler(@notnull JavaContext) {//UElementHandler] is similar to [UastVisitor], Return New UElementHandler() {@Override public void visitCallExpression(@Notnull UCallExpression Node) {if (! UastExpressionUtils.isMethodCall(node)) { return; } if (node.getReceiver() ! = null && node.getMethodName() ! = null) { String methodName = node.getMethodName(); / / check if the method name match the if (methodName. Equals (" I ") | | methodName. Equals (" d ") | | methodName. Equals (" e ") | | methodName. Equals (" v ") | | methodName.equals("w") || methodName.equals("wtf")) { PsiMethod resolve = node.resolve(); If (context.geteValuator ().ismemberInclass (resolve, "android.util.log ")) { ISSUE Context. report(ISSUE, node, context.getLocation(node), "Please use LogUtil"); }}}}}; }}Copy the code

Registration of the Detector

public class MyIssueRegistry extends IssueRegistry { @NotNull @Override public List<Issue> getIssues() { return Arrays.asList(MyDetector.ISSUE); }}Copy the code

This is where you can register multiple Detectors. The latest versions of Lint have over 300 detectors built in, all of them in the BuiltinIssueRegistry class, making it a great reference for us to write our own version of Lint. As shown in figure:

Integrate into the project

There are two ways to use custom Lint: jar packages and AAR files

Jar package:

In Terminal in AS, enter: Gradlew: After assembleBUILD SUCCESSFUL, the jar package will be generated in the libs directory. Copy the jar package to the. Android/Lint/directory. If there is no lint directory, create a new one

Enter in CMDlint --listYou can see the configured Lint rule (you need to configure the Lint environment variable in the path)

After the restart, finally, our custom Lint rules will be visible in the code

Use an AAR

In jar mode, because it is global, all projects will detect the rules defined by this project, which will result in other projects using this project’s rules, and other projects will report exceptions that shouldn’t have occurred. If you want to target a single project, you need to use an AAR.

1. Create Android Library in the same project to export AAR

2. Modify the Build. gradle of the Java library that custom Lint rules, notice that implementation is changed to compileOnly.

dependencies { implementation fileTree(dir: 'libs', include: [' *. Jar ']) compileOnly "com. Android. Tools. Lint: lint - API: 27.2.1" compileOnly "com. Android. Tools. Lint: lint - checks: 27.2.1." " }Copy the code

3. Modify the dependency of Android Library

dependencies {
    ...
    lintPublish project(':lintlib')
}
Copy the code

Assemble aar file in Android Library

After BUILD SUCCESSFUL, a new AAR is generated

5. Dependency AAR file (local dependency or upload to remote repository)

Copy aar file to app libs directory and configure Gradle

repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    ...
    implementation (name:'androidlintlib-debug',ext:'aar')
}

Copy the code

Once you’ve finished the rebuild project, you’re ready to use it. Here’s how it looks:

conclusion

Through the above introduction to lint rules and apis, and practical operation, believe that you already have a preliminary knowledge of lint, also found that, in fact, it is not difficult, only the learning process is very painful, always facing you unfamiliar field after a traumatic event, although lint use frequency is not high in our practical work, Probably not, but it will be good for you to learn, not only to expand your technology stack, but also to deepen the Java/Kotlin code compilation process.

References:

  • Developer. The android. Google. Cn/studio/buil…
  • Blog.csdn.net/zhangwenshu…
  • Juejin. Cn/post / 684490…
  • Blog.csdn.net/weixin_3397…
  • Tech.meituan.com/2016/03/21/…