android自定義Lint實現程式碼檢測
0.前言
最近在專案中發現了一個問題,伺服器端下發了一個比較大的開屏頁面,客戶端在載入開屏圖片的時候使用了BitmapFactory建立Bitmap,而且是在主執行緒做的,平時圖片小,可能沒出現什麼問題,結果這次服務端放的圖片大了,就造成了一大面積的Crash。這其實就是一個程式碼質量與程式碼規範的問題,怎麼保證這個問題不在發生?靠開發者的自覺嗎?我覺得整個程式碼世界最大的變數其實就是人,誰都有個馬高鐙短,喜怒哀樂,我們需要在編碼過程中提供一種檢測機制,用以避免這樣的問題。
1.分析
我們想要的就是在編碼時可以提供異常檢測的方法,高亮或者加紅,這在咱們平時編碼時是很常見的。

image
當滑鼠懸停在高亮的程式碼上時,會提供問題的描述和解決方法。需要這種效果,就需要自定義lint了
2.Lint介紹
看一下官方文件的介紹:
Android Studio 提供一個名為 Lint 的程式碼掃描工具,可幫助您發現並糾正程式碼結構質量的問題,而無需實際執行該應用,也不必編寫測試用例。該工具會報告其檢測到的每個問題並提供該問題的描述訊息和嚴重級別,以便您可以快速確定需要優先進行哪些關鍵改進。此外,您可以調低問題的嚴重級別,忽略與專案無關的問題,也可以調高嚴重級別,以突出特定問題。
Lint 工具可檢查您的 Android 專案原始檔是否包含潛在錯誤,以及在正確性、安全性、效能、易用性、便利性和國際化方面是否需要優化改進。在使用 Android Studio 時,配置的 Lint 和 IDE 檢查會在您每次構建應用時執行。不過,您可以手動執行檢查或從命令列執行 Lint。
android studio內建了上百個lint規則,但是我們需要根據專案進行我們自己相關特色的規則定義,說白了,內建的lint規則無法滿足需求時,就需要我們自定義lint了
3.自定義Lint流程
關於lint的文件其實還挺少的,而且居然沒有相關的api文件,這就有些蛋疼了,那怎麼搞?開壇,做法,上github,從github上google sample中下載自定義customlint的專案 (專案地址) ,開啟工程,
compileOnly "com.android.tools.lint:lint-api:$lintVersion" compileOnly "com.android.tools.lint:lint-checks:$lintVersion"
依賴了兩個maven庫,lint-api還有lint-checks,其中lint-api就是lint相關的api,lint-checks就是android studio裡自定義的一些lint規則,因為沒有什麼相關api文件,可以參考lint-check裡面的寫法
3.1建立Detector
Detector負責掃描程式碼,發現問題並報告。
class BitmapFactoryDetector:Detector(),Detector.UastScanner { companion object { val ISSUE=Issue.create( "BitmapFactoryReplace", "BitmapFactoryReplace", "使用Glide或其他第三方框架代替BitmapFactory建立Bitmap", Category.CORRECTNESS, 7, Severity.WARNING, Implementation(BitmapFactoryDetector::class.java,Scope.JAVA_FILE_SCOPE) ) } override fun getApplicableMethodNames(): List<String>? { return Arrays.asList("decodeResource","decodeFile","decodeResourceStream","decodeByteArray","decodeStream", "decodeFileDescriptor") } override fun getApplicableCallNames(): List<String>? { return Arrays.asList("decodeResource","decodeFile","decodeResourceStream","decodeByteArray","decodeStream", "decodeFileDescriptor") } override fun visitMethod(context: JavaContext, node: UCallExpression, method: PsiMethod) { if(context.evaluator.isMemberInClass(method, "android.graphics.BitmapFactory")){ context.report(ISSUE,context.getLocation(node),"使用Glide或其他第三方框架代替BitmapFactory建立Bitmap") } } override fun visitMethod( context: JavaContext, visitor: JavaElementVisitor?, call: PsiMethodCallExpression, method: PsiMethod ) { if(context.evaluator.isMemberInClass(method, "android.graphics.BitmapFactory")){ context.report(ISSUE,context.getLocation(call),"使用Glide或其他第三方框架代替BitmapFactory建立Bitmap") } } override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { if(context.evaluator.isMemberInClass(method, "android.graphics.BitmapFactory")){ context.report(ISSUE,context.getLocation(node),"使用Glide或其他第三方框架代替BitmapFactory建立Bitmap") } } }
這個應該是自定義Lint的核心了可以看到這個Detector繼承Detector類,然後實現Scanner介面。
自定義Detector可以實現一個或多個Scanner介面,選擇實現哪種介面取決於你想要的掃描範圍
自定義Detector需要繼承自Detector並實現 Detector.UastScanner 介面,25.2.0及之前版本的Detector.JavaPsiScanner已被棄用,UastScanner相比於JavaPsiScanner以及更老的JavaScanner,主要提供了對Kotlin支援,API更加簡單,特點是成對存在(滿足條件 -> visitor)此外可以lint-checks-version.jar中的各型別Detector原始碼可以學習其用法。
UastScanner包含13個回撥方法,下面介紹常用的幾個:
1.getApplicableUastTypes
此方法返回需要檢查的AST節點的型別,型別匹配的UElement將會被createUastHandler(createJavaVisitor)建立的UElementHandler(Visitor)檢查。
2.createUastHandler
建立一個UastHandler來檢查需要檢查的UElement,對應於getApplicableUastTypes
3.getApplicableMethodNames
返回你所需要檢查的方法名稱列表,或者返回null,相匹配的方法將通過visitMethod方法被檢查
4.visitMethod
檢查與getApplicableMethodNames相匹配的方法
5.getApplicableConstructorTypes
返回需要檢查的建構函式型別列表,型別匹配的方法將通過visitConstructor被檢查
6.visitConstructor
檢查與getApplicableConstructorTypes相匹配的構造方法
7.getApplicableReferenceNames
返回需要檢查的引用路徑名,匹配的引用將通過visitReference被檢查
8.visitReference
檢查與getApplicableReferenceNames匹配的引用
9.appliesToResourceRefs
返回需要檢查的資源引用,匹配的引用將通過visitResourceReference被檢查
10.visitResourceReference
檢查與appliesToResourceRefs匹配的資源引用
11.applicableSuperClasses
返回需要檢查的父類名列表,此處需要類的全路徑名
11.visitClass
檢查applicableSuperClasses返回的類
這個BitmapFactoryDetector就是用來檢測你在程式碼中是否使用BitmapFactory中的方法建立Bitmap,如果有的話就會在程式碼處高亮,進行一個提示"使用Glide或其他第三方框架代替BitmapFactory建立Bitmap"。
3.2建立Issue
val ISSUE=Issue.create( "BitmapFactoryReplace", "BitmapFactoryReplace", "使用Glide或其他第三方框架代替BitmapFactory建立Bitmap", Category.CORRECTNESS, 7, Severity.WARNING, Implementation(BitmapFactoryDetector::class.java,Scope.JAVA_FILE_SCOPE) )
宣告為final class,由靜態工廠方法建立。對應引數解釋如下:
id : 唯一值,應該能簡短描述當前問題。利用Java註解或者XML屬性進行遮蔽時,使用的就是這個id。
summary : 簡短的總結,通常5-6個字元,描述問題而不是修復措施。
explanation : 完整的問題解釋和修復建議。
category : 問題類別。詳見下文詳述部分。
priority : 優先順序。1-10的數字,10為最重要/最嚴重。
severity : 嚴重級別:Fatal, Error, Warning, Informational, Ignore。
Implementation : 為Issue和Detector提供對映關係,Detector就是當前Detector。宣告掃描檢測的範圍Scope,Scope用來描述Detector需要分析時需要考慮的檔案集,包括:Resource檔案或目錄、Java檔案、Class,Gradle檔案。
3.3報告Issue
定義IssueRegistry,返回一個IssueList
class EleIssueRegistry: IssueRegistry() { override val issues: List<Issue> get() = Arrays.asList(HttpFormatDetector.HTTP_FORMAT_ISSUE,HttpFormatDetector.TEST_HOST_CHANGED_ISSUE, BitmapFactoryDetector.ISSUE, ImageSizeDetector.ISSUE, DependenciesDetector.ISSUE) }
在gradle檔案中進行註冊
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.skateboard.lintchecker.registry.EleIssueRegistry") } }
3.4建立LibraryModule
建立LibraryModule後再gradle檔案中引用lintchecker
dependencies { lintChecks project(':lintchecker') }
編譯建立的librarymodule生成aar檔案
3.5在專案中使用
將生成的aar檔案複製到工程的libs目錄下
repositories { flatDir { dirs 'libs' } } L dependencies { implementation(name: 'lintcheckeraar-release', ext: 'aar') implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }
這樣自定義Lint的過程就基本完成了。
4.AST與PSI
如果大家熟悉自定義idea外掛,那麼大家應該可能會了解psi,psi就代表一個檔案的內容,推薦一個外掛,psiviewer這個外掛,可以看到檔案的psi構造,而ast代表抽象語法樹,關於這兩個,以後單獨抽出來講一下。
5.參考資料
Android Studio 工具:Lint 程式碼掃描工具(含自定義lint)

image
關注我的公眾號