android自定義Lint實現代碼檢測

0.前言

最近在項目中發現了一個問題,服務器端下發了一個比較大的開屏頁面,客戶端在加載開屏圖片的時候使用了BitmapFactory建立Bitmap,並且是在主線程作的,平時圖片小,可能沒出現什麼問題,結果此次服務端放的圖片大了,就形成了一大面積的Crash。這其實就是一個代碼質量與代碼規範的問題,怎麼保證這個問題不在發生?靠開發者的自覺嗎?我以爲整個代碼世界最大的變量其實就是人,誰都有個馬高鐙短,喜怒哀樂,咱們須要在編碼過程當中提供一種檢測機制,用以免這樣的問題。java

1.分析

咱們想要的就是在編碼時能夠提供異常檢測的方法,高亮或者加紅,這在我們平時編碼時是很常見的。node

當鼠標懸停在高亮的代碼上時,會提供問題的描述和解決方法。須要這種效果,就須要自定義lint了

2.Lint介紹

看一下官方文檔的介紹: Android Studio 提供一個名爲 Lint 的代碼掃描工具,可幫助您發現並糾正代碼結構質量的問題,而無需實際執行該應用,也沒必要編寫測試用例。該工具會報告其檢測到的每一個問題並提供該問題的描述消息和嚴重級別,以便您能夠快速肯定須要優先進行哪些關鍵改進。此外,您能夠調低問題的嚴重級別,忽略與項目無關的問題,也能夠調高嚴重級別,以突出特定問題。android

Lint 工具可檢查您的 Android 項目源文件是否包含潛在錯誤,以及在正確性、安全性、性能、易用性、便利性和國際化方面是否須要優化改進。在使用 Android Studio 時,配置的 Lint 和 IDE 檢查會在您每次構建應用時運行。不過,您能夠手動運行檢查或從命令行運行 Lint。git

android studio內置了上百個lint規則,可是咱們須要根據項目進行咱們本身相關特點的規則定義,說白了,內置的lint規則沒法知足需求時,就須要咱們自定義lint了github

3.自定義Lint流程

關於lint的文檔其實還挺少的,並且竟然沒有相關的api文檔,這就有些蛋疼了,那怎麼搞?開壇,作法,上github,從github上google sample中下載自定義customlint的項目(項目地址),打開工程,api

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負責掃描代碼,發現問題並報告。bash

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)檢查。app

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)

Android自定義Lint實踐

關注個人公衆號
相關文章
相關標籤/搜索