Android自定義Lint的二三事兒

概述

Lint 是 Android studio 提供的一款靜態(tài)代碼檢查工具圃庭,它可以幫助我們檢查 Android 項(xiàng)目源文件是否有潛在的 bug吏颖,以及在正確性、安全性季希、性能褪那、易用性、無(wú)障礙性和國(guó)際化方面是否需要優(yōu)化改進(jìn)式塌。Lint 的好處不言而喻博敬,它能夠在編碼階段就幫我們提前發(fā)現(xiàn)代碼中的“壞味道”,顯著降低線(xiàn)上問(wèn)題出現(xiàn)的概率峰尝;同時(shí)也能有效促進(jìn)團(tuán)隊(duì)的開(kāi)發(fā)規(guī)范的統(tǒng)一偏窝。

關(guān)于執(zhí)行 lint 檢查的幾種方式不多做贅述,接下來(lái)著重來(lái)看下如何實(shí)現(xiàn)自定義 Lint 規(guī)則并應(yīng)用到實(shí)際項(xiàng)目中武学。

自定義 Lint 接入方案

自定義 Lint 規(guī)則最終都會(huì)打成 JAR 包祭往,只需將該輸出 JAR 提供給其他組件使用即可。目前有兩種方式可供選擇:

全局方案

把此 jar 拷貝到 ~/.android/lint/ 目錄中即可劳淆。缺點(diǎn)顯而易見(jiàn):針對(duì)所有工程生效,會(huì)影響同一臺(tái)機(jī)器其他工程的 Lint 檢查默赂。即便觸發(fā)工程時(shí)拷貝過(guò)去沛鸵,執(zhí)行完刪除,但其他進(jìn)程或線(xiàn)程使用 ./gradlew lint 仍可能會(huì)受到影響缆八。

AAR 殼方案

另一種實(shí)現(xiàn)方式是將 jar 置于一個(gè) aar 中曲掰,如果某個(gè)工程想要接入執(zhí)行自定義的 lint 規(guī)則,只需依賴(lài)這個(gè)發(fā)布后的 aar 即可奈辰,如此一來(lái)栏妖,新增的 lint 規(guī)則就可將影響范圍控制在單個(gè)項(xiàng)目?jī)?nèi)了。另外奖恰,該方案也是 Google 目前推薦的方式吊趾,aar 內(nèi)容也支持 lint.jar 條目:

AAR 文件的文件擴(kuò)展名為 .aar宛裕,Maven 工件類(lèi)型應(yīng)該也是 aar。此文件本身是一個(gè) zip 文件论泛。唯一的必需條目是 /AndroidManifest.xml揩尸。AAR 文件可能包含以下一個(gè)或多個(gè)可選條目:
xx.aar
|-/classes.jar
|-/res/
|-/R.txt
|-/public.txt
|-/assets/
|-/libs/name.jar
|-/jni/abi_name/name.so(其中 abi_name 是 Android 支持的 ABI 之一)
|-/proguard.txt
|-/lint.jar
|-/api.jar
|-/prefab/(用于導(dǎo)出原生庫(kù))

具體可參考 Android 官方對(duì)于 aar 的介紹:developer.android.com/studio/proj…

編寫(xiě)自定義 Lint 規(guī)則

接下來(lái)主要從以下幾個(gè)方面來(lái)介紹自定義 Lint 的開(kāi)發(fā)流程。

1. 創(chuàng)建 java-library & 配置 lint 依賴(lài)

自定義的 lint 規(guī)則最終輸出格式為 jar 包屁奏,所以我們只需要?jiǎng)?chuàng)建一個(gè) java-library 即可岩榆,build.gradle 配置如下:

lint-rules/build.gradle

plugins {
   id 'java-library'
   id 'org.jetbrains.kotlin.jvm'
}

dependencies {
// 官方提供的Lint相關(guān)API,并不穩(wěn)定坟瓢,每次AGP升級(jí)都可能會(huì)更改勇边,且并不是向下兼容的
   compileOnly "com.android.tools.lint:lint-api:${rootProject.ext.lintVersion}"
// 目前Android中內(nèi)置的lint檢測(cè)規(guī)則
   compileOnly "com.android.tools.lint:lint-checks:${rootProject.ext.lintVersion}"
   compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"

   testImplementation "junit:junit:4.13.2"
   testImplementation "com.android.tools.lint:lint:$lintVersion"
   testImplementation "com.android.tools.lint:lint-tests:$lintVersion"
}

java {
   sourceCompatibility = JavaVersion.VERSION_1_8
   targetCompatibility = JavaVersion.VERSION_1_8
}

jar {
   manifest {
       // Only use the "-v2" key here if your checks have been updated to the
       // new 3.0 APIs (including UAST)
       attributes('Lint-Registry-V2': 'com.dorck.lint.rules.old.MyCustomIssueRegistry')
  }
}

configurations {
   lintJarOutput
}
dependencies {
   lintJarOutput files(jar)
}
defaultTasks 'assemble'

配置期間如果發(fā)現(xiàn)如下問(wèn)題:

需要將 java 閉包中的 sourceCompatibilitytargetCompatibility 改為 1.8。

此外折联,如果你創(chuàng)建 module 時(shí)選擇的是 kotlin 語(yǔ)言粒褒,還可能會(huì)遇到以下這個(gè)坑:

只需要將 kotlin 標(biāo)準(zhǔn)庫(kù)依賴(lài)方式改為 compileOnly 即可:

compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"

2. 編寫(xiě) lint-rules

平時(shí)經(jīng)常使用 kotlin 開(kāi)發(fā)項(xiàng)目的同學(xué)應(yīng)該都遇到過(guò)這種情況:一旦我們希望類(lèi) A 實(shí)現(xiàn)一個(gè)接口 B,那么通過(guò) AS 快捷鍵 option+ enter 選擇 implement members 后就會(huì)為我們的類(lèi) A 自動(dòng)實(shí)現(xiàn) B 中接口崭庸,并加了一堆 TODO 方法:

目前編碼環(huán)境并不會(huì)提示任何錯(cuò)誤怀浆,然而,如果我們粗心忘記去掉上面接口實(shí)現(xiàn)中的 TODO 方法怕享,一旦我們其他類(lèi)調(diào)用到這個(gè)類(lèi) SomethingNew执赡,程序就立馬拋出一個(gè) NotImplementedError 異常。顯然函筋,如果前置靜態(tài)代碼檢查階段沒(méi)有攔住這個(gè)問(wèn)題進(jìn)而跑到了線(xiàn)上沙合,那么就只能祈禱別人不會(huì)去調(diào)用了,否則故障在所難免了跌帐。好了首懈,既然需求過(guò)來(lái)了,我就來(lái)嘗試通過(guò)自定義 Lint 幫助團(tuán)隊(duì)其他成員在編碼階段就發(fā)現(xiàn)問(wèn)題并強(qiáng)制處理谨敛。

首先究履,在上一步中,我們?cè)?lint-rules/build.gradle 中指定了自定義的 MyCustomIssueRegistry脸狸,現(xiàn)在里面空空如也最仑,我們需要先創(chuàng)建一個(gè) Detector 用于檢測(cè) Standard.kt 中的 TODO() 方法:

@Suppress("UnstableApiUsage")
class KotlinTodoDetector : Detector(), Detector.UastScanner {

   override fun getApplicableMethodNames(): List<String> {
       return listOf("TODO")
  }

   override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
       println("KotlinTodoDetector >>> matched TODO in [${method.parent.containingFile.toString()}]")
       if (context.evaluator.isMemberInClass(method, "kotlin.StandardKt__StandardKt")) {
           val deleteFix = fix().name("Delete this TODO method")
              .replace().all().with("").build()
           context.report(
               ISSUE,
               context.getLocation(node),
               "You must fix `TODO()` first.", deleteFix)
      }
  }

   companion object {
       private const val ISSUE_ID = "KotlinTodo"
       val ISSUE = Issue.create(
           ISSUE_ID,
           "Detecting `TODO()` method from kotlin/Standard.kt.",
           """
               You have unimplemented method or undo work marked by `TODO()`,
               please implement it or remove dangerous TODO.
               """,
           category = Category.CORRECTNESS,
           priority = 9,
           severity = Severity.ERROR,
           implementation = Implementation(KotlinTodoDetector::class.java, Scope.JAVA_FILE_SCOPE),
      )
  }
}

此處我們需要檢測(cè)的對(duì)象是 Java 源文件,這里只需要繼承自 Detector 并實(shí)現(xiàn) Detector.UastScanner 接口即可炊甲。當(dāng)然泥彤,我們也可以選擇按組合方式實(shí)現(xiàn)更多其他 Scanner,這取決于我們希望掃描的文件范圍卿啡。目前支持的掃描范圍有:

  • UastScanner:掃描 Java 或者 kotlin 源文件
  • ClassScanner:掃描字節(jié)碼或編譯的類(lèi)文件
  • BinaryResourceScanner:掃描二進(jìn)制資源文件(res/raw/bitmap等)
  • ResourceFolderScanner:掃描資源文件夾
  • XmlScanner:掃描 xml 格式文件
  • GradleScanner:掃描 Gradle 格式文件
  • OtherFileScanner:其他類(lèi)型文件

檢測(cè) Java 源文件吟吝,可以通過(guò) getApplicableMethodNames 指定掃描的方法名,其他還有類(lèi)名颈娜、文件名剑逃、屬性名等等浙宜,并通過(guò) visitMethodCall 接受檢測(cè)到的方法。這里我們只需要檢測(cè) Kotlin 標(biāo)準(zhǔn)庫(kù)中的 Standard.kt 中的 TODO 方法炕贵,匹配到后通過(guò) context.report 來(lái)報(bào)告具體問(wèn)題梆奈,這里需要指定一個(gè) Issue 對(duì)象來(lái)描述問(wèn)題具體信息,相關(guān)字段如下:

  • id : 唯一值称开,應(yīng)該能簡(jiǎn)短描述當(dāng)前問(wèn)題亩钟。利用 Java 注解或者 XML 屬性進(jìn)行屏蔽時(shí),使用的就是這個(gè) id鳖轰。
  • summary : 簡(jiǎn)短的總結(jié)清酥,通常5-6個(gè)字符,描述問(wèn)題而不是修復(fù)措施蕴侣。
  • explanation : 完整的問(wèn)題解釋和修復(fù)建議焰轻。
  • category : 問(wèn)題類(lèi)別。常見(jiàn)的有:CORRECTNESS昆雀、SECURITY辱志、COMPLIANCE、USABILITY狞膘、LINT等等揩懒。
  • priority : 優(yōu)先級(jí)。1-10 的數(shù)字挽封,10 為最重要/最嚴(yán)重已球。
  • severity : 嚴(yán)重級(jí)別:Fatal, Error, Warning, Informational, Ignore。
  • Implementation : 為 Issue 和 Detector 提供映射關(guān)系辅愿,Detector 就是當(dāng)前 Detector智亮。聲明掃描檢測(cè)的范圍Scope,Scope 用來(lái)描述 Detector 需要分析時(shí)需要考慮的文件集点待,包括:Resource文件或目錄阔蛉、Java文件、Class文件等癞埠。

此外状原,我們還可以設(shè)置出現(xiàn)該 issue 上報(bào)時(shí)的默認(rèn)解決方案 fix,這里我們創(chuàng)建了一個(gè) deleteFix 實(shí)現(xiàn)開(kāi)發(fā)者快速移除報(bào)錯(cuò)位置的 TODO 代碼燕差。

最后遭笋,只需要自定義一個(gè) Registry 聲明自己需要檢測(cè)的 Issues 即可:

@Suppress("UnstableApiUsage")
class MyCustomIssueRegistry : IssueRegistry() {
   init {
       println("MyCustomIssueRegistry, run...")
  }

   override val issues: List<Issue>
       get() = listOf(
           JcenterDetector.ISSUE,
           KotlinTodoDetector.ISSUE,
      )

   override val minApi: Int
       get() = 8 // works with Studio 4.1 or later; see com.android.tools.lint.detector.api.Api / ApiKt

   override val api: Int
       get() = CURRENT_API

   override val vendor: Vendor
       get() = Vendor(
           vendorName = "Dorck",
           contact = "xxx@gmail.com"
      )

}

更多關(guān)于 AST 相關(guān)類(lèi)及語(yǔ)法介紹可參考官方指導(dǎo)文檔或者 Lint 源碼坝冕,此處不多做介紹徒探,這里很難一言以蔽之。

3. Lint 發(fā)布&接入

文章開(kāi)頭部分已經(jīng)介紹了 Lint 的相關(guān)接入方案喂窟,出于靈活性和可用性角度考慮自然選擇 aar 殼的方式测暗。經(jīng)過(guò)這幾年 lint 的發(fā)展央串,實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單:只需要?jiǎng)?chuàng)建一個(gè) Android-Library module,然后稍微配置下 gradle 即可:

lint-aar:

plugins {
   id 'com.android.library'
   id 'org.jetbrains.kotlin.android'
}
dependencies {
   lintPublish project(':checks')
   // other dependencies
}

就是這么簡(jiǎn)單碗啄,此處的 lintPublish 配置允許我們引用另一個(gè) module质和,它會(huì)獲取該組件輸出的 jar 并將其打包為 lint.jar 然后放到自身的 AAR 中。

最后稚字,我們?cè)?app 模塊中依賴(lài)一下 lint-aar 這個(gè)組件饲宿,并編寫(xiě)以下測(cè)試代碼:

interface SimpleInterface {
   fun initialize()
   fun doSomething()
}

class SomethingNew : SimpleInterface {
       override fun initialize() {
           TODO("Not yet implemented")
      }

       override fun doSomething() {
           TODO("Not yet implemented")
      }

  }

接下來(lái)執(zhí)行一下 ./gradlew :app:lint 即可看到控制臺(tái)輸出以下內(nèi)容:

我們也可以點(diǎn)擊 Lint 輸出的測(cè)試報(bào)告鏈接去查看詳細(xì)信息:

Note:AGP 7.0 開(kāi)始,執(zhí)行 ./gradlew :app:lint 只會(huì)作用于默認(rèn)變體 lint 任務(wù)上胆描,而不是諸如此前的執(zhí)行所有變體 lint 任務(wù)瘫想。

例如:我們此前執(zhí)行 ./gradlew :app:lint 可能會(huì)導(dǎo)致 debugLintreleaseLint昌讲、releaseChinaLint 等諸多變體 lint 任務(wù)的執(zhí)行国夜,嚴(yán)重拖慢了編譯速度,所以一般要指定特定變體的 lint 任務(wù)來(lái)執(zhí)行:./gradlew :app:lintDebug短绸。而7.0開(kāi)始將無(wú)需如此麻煩车吹,盡管放心使用 ./gradlew :app:lint 即可。

最后醋闭,在 Android studio 中我們也可以看到編譯器給我們的代碼警告了:

并且我們上面設(shè)置的 deleteFix 也生效了窄驹,即點(diǎn)擊 Delete this TODO method 就可以輕松移除 TODO() 方法,快速解決問(wèn)題目尖。

4. 編寫(xiě)測(cè)試代碼

TTD(Test-Driven Development)是一個(gè)不錯(cuò)的習(xí)慣馒吴,很多時(shí)候作為開(kāi)發(fā)人員大多時(shí)候無(wú)需關(guān)心最新編寫(xiě)的 lint 組件發(fā)布狀態(tài),因?yàn)椴粩喟l(fā)布和集成到示例代碼中測(cè)試是一個(gè)比較糟糕的體驗(yàn)瑟曲,嚴(yán)重消耗我們的精力饮戳。如此一來(lái),我們就不得不了解下 lint 規(guī)則編碼時(shí)的單測(cè)流程了洞拨,我相信能夠顯著提升你的開(kāi)發(fā)效率扯罐。

首先,我們需要依賴(lài) Lint 的單測(cè)組件:

testImplementation "com.android.tools.lint:lint-tests:$lintVersion"

接著烦衣,在 lint-rules 模塊中創(chuàng)建單測(cè)文件用于驗(yàn)證我們之前的 KotlinTodo 規(guī)則:

最后來(lái)看下 KotlinTodoDetectorTest 如何實(shí)現(xiàn)的:

package com.dorck.lint.examples

import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
import com.dorck.lint.rules.old.issues.KotlinTodoDetector
import org.junit.Test

@Suppress("UnstableApiUsage")
class KotlinTodoDetectorTest {

   @Test
   fun sampleTest() {
       lint().files(
           kotlin(
               """
                   package test.pkg
                   class SimpleInterfaceImpl : SimpleInterface {

                       override fun doSomething(){
                           TODO("Not yet implemented")
                       }
                   }
                   interface SimpleInterface {
                       fun doSomething()
                   }
               """.trimIndent()
          ))
          .issues(KotlinTodoDetector.ISSUE)
          .run()
          .expect(
               """
                   src/test/pkg/SimpleInterfaceImpl.kt:5: Error: You must fix TODO() first. [KotlinTodo]
                           TODO("Not yet implemented")
                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~
                   1 errors, 0 warnings
               """.trimIndent()
          )
     .expectFixDiffs(
               """
                   Fix for src/test/pkg/SimpleInterfaceImpl.kt line 5: Delete this TODO method:
                   @@ -5 +5
                   -         TODO("Not yet implemented")
               """.trimIndent()
          )
  }
}

其實(shí)也很簡(jiǎn)單歹河,只需要模擬創(chuàng)建一個(gè) Java/kotlin/Gradle/xml 等格式的源文件,然后在 java()koltin() 方法參數(shù)里面寫(xiě)上測(cè)試代碼花吟,并指定要驗(yàn)證的 Issue 以及期待的反饋內(nèi)容秸歧。當(dāng)然,expect() 中期待的輸出檢查結(jié)果我們是無(wú)法知曉的衅澈,我們只需要先設(shè)置為空字符串键菱,然后先跑一下測(cè)試用例,預(yù)期肯定會(huì)失敗今布,如此经备,我們只需要將終端輸出的實(shí)際錯(cuò)誤信息 copy 到 expect() 中即可:

最后拭抬,重新 run 一下單測(cè),就會(huì)發(fā)現(xiàn)能夠正常通過(guò)測(cè)試了侵蒙。更多關(guān)于 Lint 單元測(cè)試的用法可以參考:Lint unit testing

5. 忽略某些規(guī)則檢查

某些情況下造虎,我們希望忽略某些 Lint 規(guī)則的檢查或者更改 Lint 規(guī)則的嚴(yán)重級(jí)別,那么纷闺,我們可以選擇增加一個(gè) Lint 配置文件算凿,用于解決上述問(wèn)題。我們可以手動(dòng)在項(xiàng)目 app/根目錄下創(chuàng)建一個(gè)名為 lint.xml 的文件:

<?xml version="1.0" encoding="UTF-8"?>
<lint>
   <!-- list of issues to configure -->
   <issue id="DefaultLocale" severity="ignore"/>
   <issue id="DeprecatedProvider" severity="ignore"/>
   <issue id="ObsoleteLayoutParam">
       <!-- The <ignore> tag has two possible attributes: path and regexp (see below) -->
       <ignore path="res/layout-xlarge/activation.xml" />
       <!-- You can use globbing patterns in the path strings -->
       <ignore path="**/layout-x*/onclick.xml" />
       <ignore path="res/**/activation.xml" />
   </issue>

<issue id="MissingTranslation" severity="ignore"/>
<issue id="KotlinTodo" severity="ignore"/>
</lint>

在 lint.xml 中我們可以選擇更改某條規(guī)則的嚴(yán)重級(jí)別犁功,使原本不受重視的規(guī)則更加引人注意或者放寬其他規(guī)則的級(jí)別澎媒。當(dāng)然,我們也可以指定某條規(guī)則在特定匹配路徑下被忽略波桩,這將取決于我們自己設(shè)定的 regex 匹配規(guī)則戒努。

Note:如果我們創(chuàng)建了 lint.xml (文件名強(qiáng)約定),并且 build.gradlelintOptions 中沒(méi)有自定義設(shè)定 lint 配置文件的名稱(chēng)和路徑镐躲,則 AGP自動(dòng)在臨近目錄中尋找名為 lint.xml 的配置文件储玫。

詳細(xì)參考:Configure by lint xml

此外,我們也可以通過(guò)在 build.gradle >> lintOptions DSL 中設(shè)置開(kāi)啟或者關(guān)閉某些特定規(guī)則萤皂,當(dāng)然也可以配置報(bào)告的輸出格式以及路徑:

lintOptions {
   textReport false
   lintConfig file('default-lint.xml') // At `app/default-lint.xml`
   disable 'KotlinTodo', 'MissingTranslation'
   xmlOutput file("lint-report.xml")
}

更多關(guān)于 LintOptions DSl 的配置可查看官方文檔:LintOptions-dsl

Note:如果你項(xiàng)目中使用了 lint plugin撒穷,那么可以參考 lint DSL的相關(guān)釋義:AGP-lint-dsl

其他的設(shè)置 lint 配置的方式還有手動(dòng)在 Android studio 的工具欄 Analyze > Inspect Code > Specify Inspection Scope 中或者通過(guò) Lint 命令行工具來(lái)配置,這兩種方式就不具體介紹了裆熙,感興趣的朋友可以去看下官方文檔的介紹端礼。

版本迭代過(guò)程

AGP 4.0開(kāi)始,Android studio 支持了獨(dú)立的 com.android.lint 插件入录,進(jìn)一步降低了自定義 lint 的成本蛤奥。借助此插件,在上述 lint-rules/build.gradle 中通過(guò)在 manifest 中注冊(cè)自定義 Registry 改為通過(guò)服務(wù)表單注冊(cè)(當(dāng)然僚稿,以前的方式目前還是可以用的)凡桥。以下是基于官方最新推薦的方式來(lái)配置和注冊(cè)自定義規(guī)則的:

plugins {
   id 'java-library'
   id 'org.jetbrains.kotlin.jvm'
   id 'com.android.lint'
}

dependencies {
   // 官方提供的Lint相關(guān)API,并不穩(wěn)定蚀同,每次AGP升級(jí)都可能會(huì)更改缅刽,且并不是向下兼容的
   compileOnly "com.android.tools.lint:lint-api:${rootProject.ext.lintVersion}"
   // 目前Android中內(nèi)置的lint檢測(cè)規(guī)則
   compileOnly "com.android.tools.lint:lint-checks:${rootProject.ext.lintVersion}"
   compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"

   testImplementation "junit:junit:4.13.2"
   testImplementation "com.android.tools.lint:lint:$lintVersion"
   testImplementation "com.android.tools.lint:lint-tests:$lintVersion"
}

java {
   sourceCompatibility = JavaVersion.VERSION_1_8
   targetCompatibility = JavaVersion.VERSION_1_8
}

可以看到,以此插件方式蠢络,我們需要關(guān)注的額外配置更少了衰猛,很大程度上降低了接入成本。

下面再來(lái)談?wù)?AGP-7.0 開(kāi)始的改動(dòng)刹孔。其一變動(dòng)是上面談及過(guò)的執(zhí)行 ./gradlew lint 只會(huì)作用于默認(rèn)變體的 Lint 任務(wù)上啡省,而不是以前的所有變體任務(wù)。

另外一項(xiàng)是在 7.0 中,lint 最終將能夠跨模塊增量運(yùn)行冕杠,這意味著如果我們只更改一個(gè)模塊中的代碼,lint 只需在該模塊下游的模塊上重新運(yùn)行分析檢測(cè)酸茴。對(duì)于具有許多模塊的大型項(xiàng)目分预,這應(yīng)該是一項(xiàng)重大的改進(jìn)。

開(kāi)發(fā)技巧

1. 借助 Psi 工具查看 AST 語(yǔ)法樹(shù)

Lint 檢查的實(shí)質(zhì)是對(duì)代碼的 AST(Abstract Syntax Tree薪捍,即抽象語(yǔ)法樹(shù))數(shù)據(jù)進(jìn)行檢查分析笼痹,故而會(huì)用到大量 AST 與 lombok.ast 開(kāi)源庫(kù)相關(guān)知識(shí)。閱讀源碼是一種不錯(cuò)的分析語(yǔ)法樹(shù)方式酪穿,不過(guò)我們可以借助 AS 的一些插件幫我們快速便捷解析類(lèi)的節(jié)點(diǎn)樹(shù)并加以解讀凳干。

利用 PsiViewer 就可以查看類(lèi)的 AST 構(gòu)造,如此一來(lái)我就可以另辟蹊徑找到特定的屬性來(lái)匹配特定代碼了被济。值得注意的是救赐,上面的 AST viewer 插件對(duì) kotlin 代碼支持不是很好,如果有需要只磷,建議先將 kotlin 反編譯為 java 再分析经磅。

2. 參考 Android 內(nèi)置 Lint 規(guī)則

我發(fā)現(xiàn)官方近期對(duì)于 Lint 的技術(shù)推進(jìn)很上心,各路文檔和 FAQ 陸續(xù)補(bǔ)齊了钮追。關(guān)于內(nèi)置規(guī)則预厌,Android官方團(tuán)隊(duì)也對(duì)每條做了詳細(xì)說(shuō)明和用法指導(dǎo),詳細(xì)參考:googlesamples.github.io/android-cus…

相關(guān)參考

最后

后續(xù)將持續(xù)更新lint實(shí)戰(zhàn)篇元媚,敬請(qǐng)期待轧叽。本文涉及相關(guān)源碼可以查看此處傳送:github.com/Moosphan/Mi…

作者:道可訴
鏈接:https://juejin.cn/post/7130410607534145544

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市刊棕,隨后出現(xiàn)的幾起案子炭晒,更是在濱河造成了極大的恐慌,老刑警劉巖甥角,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腰埂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蜈膨,警方通過(guò)查閱死者的電腦和手機(jī)屿笼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翁巍,“玉大人驴一,你說(shuō)我怎么就攤上這事≡詈” “怎么了肝断?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我胸懈,道長(zhǎng)担扑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任趣钱,我火速辦了婚禮涌献,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘首有。我一直安慰自己燕垃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布井联。 她就那樣靜靜地躺著卜壕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烙常。 梳的紋絲不亂的頭發(fā)上轴捎,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音蚕脏,去河邊找鬼轮蜕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蝗锥,可吹牛的內(nèi)容都是我干的跃洛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼终议,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼汇竭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起穴张,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤细燎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后皂甘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體玻驻,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年偿枕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了璧瞬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渐夸,死狀恐怖嗤锉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情墓塌,我是刑警寧澤瘟忱,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布奥额,位于F島的核電站,受9級(jí)特大地震影響访诱,放射性物質(zhì)發(fā)生泄漏垫挨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一触菜、第九天 我趴在偏房一處隱蔽的房頂上張望九榔。 院中可真熱鬧,春花似錦玫氢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至喻旷,卻和暖如春生逸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背且预。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工槽袄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锋谐。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓遍尺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涮拗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乾戏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容