Android Lint
Lint, a static code scanning tool, allows us to find bugs in code that can be optimized without performing application or unit tests.
As we write code, Android Studio sometimes highlights the code we have a problem with in yellow and prompts us to fix it. Some companies will also run Lint before submitting your code to check if there is a problem with your code or if it conforms to the specification.
Why
Using Lint you can:
- Identify problems early and solve them
- Avoid repeating mistakes
- Normalized code
- Lint is included in the SDK to prevent users from using it carelessly and to point out errors in advance
How
Use Lint with Android Studio
Inspect Code
We can execute Lint directly by clicking Analyze -> Inspect Code in Android Studio.
We can customize the scope that Lint executes.
To see what is being scanned, click on the location in the image to see what is being checked.
Once the scan is complete, the results of the scan are displayed below, and you can now make changes to the code as prompted.
Gradle Command
We can also execute the following commands from the command line
# input
./gradlew lintDebug
Copy the code
After the scan, HTML and XML documents are generated.
# output
> Task :app:lintDebug
Wrote HTML report to file://***/code/JLint/app/build/reports/lint-results-debug.html
Wrote XML report to file://***/code/JLint/app/build/reports/lint-results-debug.xml
BUILD SUCCESSFUL in 15s
27 actionable tasks: 2 executed, 25 up-to-date
Copy the code
The link is opened with a browser to view the Lint report, and we can then make changes to the code based on the report.
Custom Lint rules
Although the default Lint already comes with a lot of useful rules. But it may not be applicable to the needs of our own or team development, so we need to customize it ourselves.
The Lint API is currently in Beta, so keep an eye on the official documentation and demos
Create a project
It is recommended that all rules be contained in one Module for easy use by different projects. File > new > new module > Java or Kotlin Library
First we will create a Module of type Java or Kotlin LIbrary. Build. Gradle and introduce the dependencies that Lint needs.
compileOnly "Com. Android. Tools. Lint: lint - API: 27.1.2"
compileOnly "Com. Android. Tools. Lint: lint - checks: 27.1.2"
compileOnly "Org. Jetbrains. Kotlin: kotlin stdlib - jdk8:1.4.30"
testImplementation "Junit: junit: 4.13.1"
testImplementation "Com. Android. Tools. Lint: lint: 27.1.2"
testImplementation "Com. Android. Tools. Lint: lint - tests: 27.1.2"
testImplementation "Com. Android. The tools: testutils: 27.1.2"
Copy the code
Write rules
Here is an example of a rule to check that the logging tool is being used correctly.
The main object
Let’s start with a few basic classes.
Issue
Used to describe potential problems in an application. Through the internal static method create(…) Build, the main member variables are:
id
, Issue Unique identifierbriefDescription
, a short description of the problemexplanation
, a general description of the problem, including suggestions for changecategory
The category of Issue yesLINT
.SECURITY
.CORRCTNESS
等priority
1 to 10, 10 being the most importantseverity
, severity,ERROR
It has to be corrected,WARNING
It will be highlighted in yellowImplememtation
The implementation of Issue is associationDetector
And specify the scan range.
Detector
Can be used to find a specific problem or set of related problems. Each Issue is uniquely marked as an Issue
The Detector is called in a specific order:
- Manifest
- Resource (Scan alphabetically, eg, scan first
layout
And then scanvalues
) - Java source
- Java classes
- Gradle
- Generic
- Proguard file
- Property file
Specific scan rules can be defined using Scanner.
Scanner
Different files are analyzed by different Scanner
UastScanner
, Java or Kotlin filesClassScanner
, bytecode or compiled class fileBinaryResourceScanner
Binary resource filesResourceFolderScanner
XmlScanner
GradleScanner
OtherFileScanner
IssueRegistry
An Issue needs to be registered in IssueRegistry to be executed when Lint is executed.
checkLog
Tool use
Use the encapsulated HLog instead of using the system Log to output logs.
createDetector
Since we are checking the contents of the code file, we also implement UastScanner
class LogDetector : Detector(), Detector.UastScanner {
...
}
Copy the code
createIssue
Write an Issue that describes the problem we are examining.
class LogDetector : Detector(), Detector.UastScanner {
...
companion object {
val ISSUE: Issue = Issue.create(
id = "LogChecker",
briefDescription = "Check the use of Log.",
explanation = "Use HLog instead of Log.",
category = Category.CORRECTNESS,
priority = 1,
severity = Severity.WARNING,
implementation = Implementation(
LogDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
}
}
Copy the code
supplementDetector
Scan code of
We just need to find the Log method using code, so rewrite getApplicableMethodNames, return to the Log list of methods, such as scanning to these methods, will the callback to the visitMethodCall. This helps us focus on the pieces of code that might be problematic and need to be fixed.
class LogDetector : Detector(), Detector.UastScanner {
override fun getApplicableMethodNames(a): List<String>? {
return listOf("i"."d"."e"."w"."v")}}Copy the code
You then override the visitMethodCall method to further determine if there is a problem with the code.
class LogDetector : Detector(), Detector.UastScanner {
override fun getApplicableMethodNames(a): List<String>? {... }override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
super.visitMethodCall(context, node, method)
// check whether the caller is Log
if(! context.evaluator.isMemberInClass(method,"android.util.Log")) {
return
}
// There is a problem with the code, throw Issue
context.report(
ISSUE,
method,
context.getCallLocation(node, includeReceiver = true, includeArguments = true),
"Use HLog instead of Log"// Mouse over the problem code hint is this)}Copy the code
registeredIssue
Inherit IssueRegistry and register an Issue
class JIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = arrayListOf(
LogDetector.ISSUE
)
override val api: Int
get() = CURRENT_API
}
Copy the code
Add to the module of the module
jar {
manifest {
attributes("Lint-Registry-V2": "xyz.juncat.jlintrules.JIssueRegistry")
}
}
Copy the code
The use of Lint
Add it to build.gradle(:app) as you use it
dependencies {
lintChecks project(':JLintRules')}Copy the code
You’ll notice that the :app code that uses the native Log method is highlighted in yellow.
Rebuild every time you modify Lint code
Modifying problem code
When writing code, if there is no import, press Alt+Enter on the corresponding class name to automatically import us. Custom Lint can also directly fix problems in code.
Just add LintFix where the Detector throws errors
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
super.visitMethodCall(context, node, method)
if(! context.evaluator.isMemberInClass(method,"android.util.Log")) {
return
}
// Get the list of parameters in the method
val args = node.valueArguments
val fix = LintFix.create()
.name("replace Log to HLog")
.replace()// Replace it with the following code
.with("HLog.${node.methodName}(${args[0].asSourceString()})")
.build()
context.report(
ISSUE,
method,
context.getCallLocation(node, includeReceiver = true, includeArguments = true),
"Use HLog instead of Log",
fix
)
}
Copy the code
Rebuild code, and then look at the use of Log, will be prompted to modify the method. Simply press Option+Shift+Enter(MAC) and Alt+Enter(Linux, Window) to replace it with our own method.
Debug Lint
You can Debug to check whether Lint is executing or to see the specific values of some arguments in Lint. There are two ways to do this.
Modify Gradle execution parameters
In a project that references custom Lint rules, write code that does not conform to the rules and add breakpoints to Detector.
Execute Lint with the following command
./gradlew --no-daemon -Dorg.gradle.debug=true lintDebug
Copy the code
Gradle pauses execution until you are connected to the debugger
Double-click Shift, type Attach Debugger to Process, press Enter, and Lint will continue executing and the program will correctly stop at your breakpoint.
Note that Attach Debugger to Android Process is not selected
Unit Testing
- inheritance
LintDetectorTest()
- rewrite
getDetector()
和getIssues()
- Write test code snippets, which can also be code files
- call
lint()
Test, if the test code uses classes from the Android SDK, addrequireCompileSdk()
和sdkHome()
Method, specifying the SDK. - Debug Runs the test method
class LogDetectorTest : LintDetectorTest() {
private var sdkDir = ""
override fun setUp(a) {
super.setUp()
val p = Properties(System.getProperties())
p.load(FileInputStream(".. /local.properties"))
sdkDir = p.getProperty("sdk.dir")}private val inCorrectMethodCallKt = """ package xyz.juncat.jlint import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.i("TAG", "onCreate: ") } } """.trimIndent()
private val correctMethodCallKt = """ package xyz.juncat.jlint class Test { fun test() { HLog.i("TAG","test"); }} "" ".trimIndent()
override fun getDetector(a): Detector = LogDetector()
override fun getIssues(a): MutableList<Issue> = mutableListOf(LogDetector.ISSUE)
@Test
fun testInCorrectLogCall(a) {
lint().requireCompileSdk()
.sdkHome(File(sdkDir))
.files(kotlin(inCorrectMethodCallKt).indented())
.run()
.expectWarningCount(1)}@Test
fun testCorrectLogCall(a){ lint().requireCompileSdk() .sdkHome(File(sdkDir)) .files(kotlin(correctMethodCallKt).indented()) .run() .expectClean() }}Copy the code
packaging
We can also package projects as AArs for easy reuse
First, you need to create a new Android Module, such as JLintAAR
Then add it in its build.gradle
dependencies {
lintPublish project(':JLintRules')
}
Copy the code
This way, as soon as we build the JLintAAR, we can generate the AAR package, which other projects can reference directly.
The last
Here is just the basic method, there are still many functions to explore, so if there is any mistake, I hope you point out. Any good usage, also welcome to leave a message.
There are also many built-in detectors under Lint-checks, you can learn about them, but there may be some API differences.
Here is my Demo for your reference.
Lint’s code is all in Beta. If there are any differences, it’s best to look at the source code or documentation
The resources
- Developer.android.com/studio/writ…
- Github.com/googlesampl…
- Mp.weixin.qq.com/s/B9p4EUIaF…
- Stackoverflow.com/questions/3…
- Proandroiddev.com/testing-you…
- medium.com/@intelia/bu…
- Javadoc. IO/doc/com. And…