Customization and application of Android code inspection rule Lint

Foreword:

In daily code development, it is believed that each developer has high requirements for code quality and has his own set of code specifications, but we do not fight alone, often we fight as a team, and people are the biggest variable. People are different, how to ensure the code quality and code specification of the team? Does it depend on the developer's self-consciousness? Maybe some teams have a strict CR mechanism, and CR will be carried out in the MR stage. MRs that fail the CR are not allowed to join, but this will make the Reviewer spend more time to verify, then we need to A code detection mechanism is provided during the encoding process.

For example, the expected effect is to display the method of anomaly detection during coding, highlight or mark it in red, and when the mouse hovers over the highlighted code, it will provide a description of the problem and a solution. If you need this effect, you need to customize lint.

image-20220311150938420.png

What is Lint

Lint, a code scanning tool provided in Android Studio, can help developers find code quality problems and suggest improvements without actually executing the application or writing test cases.

The Lint tool checks your Android project source files for potential errors and for optimizations in correctness, security, performance, ease of use, convenience, and internationalization. When using Android Studio, configured Lint and IDE checks run every time you build your app. However, you can run the check manually or run Lint from the command line.

Android studio has many built-in lint rules, but when the built-in lint rules are not suitable for us intuitively, we need to customize lint.

image-20220311151335437.png

Custom Lint Process:

1. Create a new module, select Java or Kotlin Library for the Module type, and temporarily name it lint_tools

2. Introduce lint dependencies in build.gradle

    dependencies {
        compileOnly 'com.android.tools.lint:lint-api:27.2.2'
        compileOnly 'com.android.tools.lint:lint-checks:27.2.2'
    }
   
复制代码

In moudle, we rely on lint-api and lint-checks, where lint-api is the lint-related api, and lint-checks are some lint rules customized in android studio. For our custom lint, you can refer to the writing in lint-checks.

3. Create a resource id naming check rule locally to standardize the unified naming of ids in the project

创建ViewIdDetector,直接继承LayoutDetector(也可以继承ResourceXmlDetector 或者 继承Detector实现的接口是XmlScanner,方式多样)

import com.android.SdkConstants
import com.android.tools.lint.detector.api.*
import com.android.tools.lint.detector.api.Category.Companion.CORRECTNESS
import com.android.tools.lint.detector.api.Scope.Companion.RESOURCE_FILE_SCOPE
import org.w3c.dom.Element
​
class ViewIdDetector : LayoutDetector() {
    
    override fun getApplicableElements(): Collection<String>? {
        return listOf(
            SdkConstants.TEXT_VIEW,
            SdkConstants.IMAGE_VIEW,
            SdkConstants.BUTTON
        )
    }
​
    override fun visitElement(context: XmlContext, element: Element) {
        if (!element.hasAttributeNS(SdkConstants.ANDROID_URI, SdkConstants.ATTR_ID)) {
            return
        }
        val attr = element.getAttributeNodeNS(SdkConstants.ANDROID_URI, SdkConstants.ATTR_ID)
        val value = attr.value
        if (value.startsWith(SdkConstants.NEW_ID_PREFIX)) {
            val idValue = value.substring(SdkConstants.NEW_ID_PREFIX.length)
            var matchRule = true
            var expMsg = ""
            when (element.tagName) {
                SdkConstants.TEXT_VIEW -> {
                    expMsg = "tv"
                    matchRule = idValue.startsWith(expMsg)
                }
                SdkConstants.IMAGE_VIEW -> {
                    expMsg = "iv"
                    matchRule = idValue.startsWith(expMsg)
                }
                SdkConstants.BUTTON -> {
                    expMsg = "btn"
                    matchRule = idValue.startsWith(expMsg)
                }
            }
            if (!matchRule) {
                context.report(
                    ISSUE, 
                    attr, 
                    context.getLocation(attr), 
                    "ViewIdName建议使用view的缩写_xxx; ${element.tagName} 建议使用 `${expMsg}_xxx`"
                )
            }
        }
    }
​
    companion object {
        val ISSUE: Issue = Issue.create(
            "ViewIdCheck",
            "ViewId命名不规范",
            "ViewIdName建议使用 view的缩写加上_xxx,例如tv_xxx, iv_xxx",
            CORRECTNESS,
            5, Severity.ERROR,
            Implementation(
                ViewIdDetector::class.java,
                RESOURCE_FILE_SCOPE
            )
        )
    }
}
复制代码

自定义Detector可以实现一个或多个Scanner接口,选择实现哪种接口取决于你想要的扫描范围。
Lint API 中内置了很多 Scanner:

Scanner 类型 Desc
UastScanner 扫描 Java、Kotlin 源文件
XmlScanner 扫描 XML 文件
ResourceFolderScanner 扫描资源文件夹
ClassScanner 扫描 Class 文件
BinaryResourceScanner 扫描二进制资源文件
GradleScanner 扫描Gradle脚本

4. 实现IssueRegistry并添加对应的自定义Issue:

class IMockIssueRegistry: IssueRegistry() {
    override val issues: List<Issue>
        get() = listOf(
            ViewIdDetector.ISSUE
        )
​
}
复制代码

5. 在module(lint_tools)中对应的build.gradle中配置如下信息:

jar {
    manifest {
        attributes("Lint-registry-v2": "com.imock.lint.IMockIssueRegistry")
    }
}
​
复制代码

6. 在需要进行lint检查的module中或者app目录现的build.gradle中引用对应的lint_tools即可使用。

dependencies {
    lintChecks project(path: ':lint-tools')
}
复制代码

至此你可以试着自己自定义Lint了,相关语法api都可参考lint-checks中提供的Detector实现,来实现自己的Lint检查规则。

拓展一下:

1. 针对Issue.create参数了解一下:

companion object {
    /**
     * Creates a new issue. The description strings can use some simple markup;
     * see the [TextFormat.RAW] documentation
     * for details.
     *
     * @param id the fixed id of the issue
     * @param briefDescription short summary (typically 5-6 words or less), typically
     * describing the **problem** rather than the **fix**
     * (e.g. "Missing minSdkVersion")
     * @param explanation a full explanation of the issue, with suggestions for
     * how to fix it
     * @param category the associated category, if any
     * @param priority the priority, a number from 1 to 10 with 10 being most
     * important/severe
     * @param severity the default severity of the issue
     * @param implementation the default implementation for this issue
     * @return a new [Issue]
     */
    @JvmStatic
    fun create(
        id: String,
        briefDescription: String,
        explanation: String,
        category: Category,
        priority: Int,
        severity: Severity,
        implementation: Implementation
    ): Issue {
        val platforms = computePlatforms(null, implementation)
        return Issue(
            id, briefDescription, explanation, category, priority,
            severity, platforms, null, implementation
        )
    }
}
复制代码
  • 参数id 唯一的id,简要表面当前提示的问题。
  • 参数briefDescription 简单描述当前问题
  • 参数explanation 详细解释当前问题和修复建议
  • 参数category 问题类别
  • 参数priority 优先级,从1到10,10最重要
  • 参数Severity 严重程度:FATAL(奔溃), ERROR(错误), WARNING(警告),INFORMATIONAL(信息性),IGNORE(可忽略)
  • The parameter Implementation Issue is bound to which Detector, and the scope of the declaration inspection. Scope has the following options: RESOURCE_FILE (resource file), BINARY_RESOURCE_FILE (binary resource file), RESOURCE_FOLDER (resource folder), ALL_RESOURCE_FILES (all resource files), JAVA_FILE (Java files), ALL_JAVA_FILES (all Java files), CLASS_FILE (class files) ), ALL_CLASS_FILES (all class files), MANIFEST (configuration manifest file), PROGUARD_FILE (obfuscation file), JAVA_LIBRARIES (Java library), GRADLE_FILE (Gradle file), PROPERTY_FILE (property file), TEST_SOURCES (test resource), OTHER (other) ;

Guess you like

Origin juejin.im/post/7084996427662917640