上個月,筆者在巴黎 Droidcon 的 BarCamp 研討會上聆聽了 Matthew Compton 關於編寫本身的 Lint 規則的講話。深受啓發以後,筆者想就此話題作進一步的探索。html
若是你是安卓開發者,那你必定已經知道 Lint 的定義。java
Lint 是一款靜態代碼分析工具,能檢查安卓項目的源文件,從而查找潛在的程序錯誤以及優化提高的方案。android
當你忘記在Toast
上調用show()
時,Lint 就會提醒你。它也會確保你的ImageView
中添加了contentDescription
,以支持可用性。相似的例子還有成千上萬個。誠然,Lint 能在諸多方面提供幫助,包括:正確性,安全,性能,易用性,可用性,國際化等等。git
Lint 易於使用,經過簡單的 Gradle 任務:./gradlew lint
就能在任意安卓項目上運行。它會生成一份報告,指出它的發現並按照種類、優先級和嚴重程度對問題進行分類。這份報告能確保代碼質量,防止 app 中出現代碼錯誤,所以應該時刻進行監控。github
在簡單的介紹以後,筆者但願你們能達成共識:Lint 是理解一些安卓 API 框架使用狀況的好幫手。算法
大多數開發者可能都不知道:你能夠本身寫 Lint 規則。其實,在不少使用案例中,自定義的 Lint 規則每每大有用處:api
若是你在寫一個代碼庫/SDK,你想幫助開發者正確地使用它,Lint 規則就能派上用場。有了 Lint,你能夠輕易地提醒他們忽略或作錯的事情。安全
若是你的團隊有了新加入的開發者,Lint 能夠幫助他快速瞭解團隊的最佳實踐,或命名慣例。性能優化
你可能知道,筆者最近加入了 CaptainTrain 安卓團隊。下面的例子基於筆者爲本身的 app 建立的兩條 Lint 規則,這些規則完美地展現了 Lint 確保開發者遵循項目編碼實踐的妙用。網絡
自定義的 Lint 規則必須實如今一個新的模塊中。如下是一個 build.gradle
例子:
apply plugin: 'java' targetCompatibility = JavaVersion.VERSION_1_7 sourceCompatibility = JavaVersion.VERSION_1_7 configurations { lintChecks } dependencies { compile 'com.android.tools.lint:lint-api:24.3.1' compile 'com.android.tools.lint:lint-checks:24.3.1' lintChecks files(jar) } jar { manifest { attributes('Lint-Registry': 'com.captaintrain.android.lint.CaptainRegistry') } } defaultTasks 'assemble' task install(type: Copy, dependsOn: build) { from configurations.lintChecks into System.getProperty('user.home') + '/.android/lint/' }
如你所見,爲了實現自定義 Lint 規則,須要兩個編譯依賴關係。此外,還須要確切的 Lint-Registry,後文會介紹這是什麼,如今只需記住這是強制要求。最後,建立一個小任務來快速安裝新的 Lint 規則。
接着,使用../gradlew clean install
編譯並部署該模塊。
配置好模塊以後,讓咱們來看看如何編寫第一條規則。
在 CaptainTrain 項目中,咱們都會在屬性前面添加ct
前綴,從而避免與其餘代碼庫發生衝突。新的開發者很容易忘記這一點,所以筆者寫了以下規則:
public class AttrPrefixDetector extends ResourceXmlDetector { public static final Issue ISSUE = Issue.create("AttrNotPrefixed", "You must prefix your custom attr by `ct`", "We prefix all our attrs to avoid clashes.", Category.TYPOGRAPHY, 5, Severity.WARNING, new Implementation(AttrPrefixDetector.class, Scope.RESOURCE_FILE_SCOPE)); // Only XML files @Override public boolean appliesTo(@NonNull Context context, @NonNull File file) { return LintUtils.isXmlFile(file); } // Only values folder @Override public boolean appliesTo(ResourceFolderType folderType) { return ResourceFolderType.VALUES == folderType; } // Only attr tag @Override public Collection<String> getApplicableElements() { return Collections.singletonList(TAG_ATTR); } // Only name attribute @Override public Collection<String> getApplicableAttributes() { return Collections.singletonList(ATTR_NAME); } @Override public void visitElement(XmlContext context, Element element) { final Attr attributeNode = element.getAttributeNode(ATTR_NAME); if (attributeNode != null) { final String val = attributeNode.getValue(); if (!val.startsWith("android:") && !val.startsWith("ct")) { context.report(ISSUE, attributeNode, context.getLocation(attributeNode), "You must prefix your custom attr by `ct`"); } } } }
如你所見,咱們繼承了ResourceXmlDetector
類。Detector
類容許咱們發現問題,並報告Issue
。首先,咱們必須明確尋找什麼:
第一個appliesTo
方法會只保留 XML 文件。
第二個appliesTo
方法會只保留資源文件夾中的values
。
getApplicableElements
方法會只保留attr
XML 元素。
getApplicableAttributes
方法會只保留name
XML 屬性。
過濾以後,咱們使用簡單的算法實現visitElement
方法。一旦發現某個attr
XML 標記的name
屬性不源自安卓也不以ct
前綴,咱們就報告一個Issue
。該Issue
按照以下方式聲明在類的頭部:
public static final Issue ISSUE = Issue.create("AttrNotPrefixed", "You must prefix your custom attr by `ct`", "To avoid clashes, we prefixed all our attrs.", Category.TYPOGRAPHY, 5, Severity.WARNING, new Implementation(AttrPrefixDetector.class, Scope.RESOURCE_FILE_SCOPE));
其中,每一個參數都很重要,並且是強制性參數。
AttrNotPrefixed 是 Lint 規則的 id,必須是惟一的。
You must prefix your custom attr by ct
(必須以 ct 做爲自定義屬性的前綴)是簡述。
To avoid clashes, we prefixed all our attrs.
(爲避免衝突,全部屬性均添加前綴。)是更爲詳細的解釋。
5
是優先級係數。必須是1到10之間的某個值。
WARNING
是嚴重程度。此處咱們只選擇WARNING
,這樣即使存在該問題,代碼也能安全運行。
Implementation
是Detector
間的橋樑,用於發現問題。Scope
則用於分析問題。在本例中,咱們必須處於資源文件層面才能分析前綴問題。
你可能也發現了,其實所需的代碼很是簡單易懂。你只需當心所用的範圍以及爲Issue
輸入的值便可。
Lint 報告可能得出的結果以下:
在 CaptainTrain 應用中,咱們將全部Log
調用都包裝到一個新的類裏。因爲在生產環境下,日誌有可能妨礙應用性能與用戶數據的安全,該類旨在BuildConfig.DEBUG
爲非時禁用日誌。此外,該類還能幫助日誌排版,以及提供一些其餘特性。舉例以下:
public class LoggerUsageDetector extends Detector implements Detector.ClassScanner { public static final Issue ISSUE = Issue.create("LogUtilsNotUsed", "You must use our `LogUtils`", "Logging should be avoided in production for security and performance reasons. Therefore, we created a LogUtils that wraps all our calls to Logger and disable them for release flavor.", Category.MESSAGES, 9, Severity.ERROR, new Implementation(LoggerUsageDetector.class, Scope.CLASS_FILE_SCOPE)); @Override public List<String> getApplicableCallNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public List<String> getApplicableMethodNames() { return Arrays.asList("v", "d", "i", "w", "e", "wtf"); } @Override public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode, @NonNull MethodNode method, @NonNull MethodInsnNode call) { String owner = call.owner; if (owner.startsWith("android/util/Log")) { context.report(ISSUE, method, call, context.getLocation(call), "You must use our `LogUtils`"); } } }
如你所見,規則二的模式與規則一相同。方法getApplicableCallNames
與getApplicableMethodNames
用於明確尋找的目標。以後,咱們找出問題並建立之。惟一的不一樣在於,咱們再也不繼承XmlResourceDetector
類,而是僅繼承Detector
類,並實現ClassScanner
接口以處理 Java 類檢查。因此,實際上,規則二的變化沒有不少。若是仔細查看XmlResourceDetector
類,會發現它只是實現XmlScanner
的Detector
類。所以,全部規則都適用的總結以下:咱們只需繼承Detector
並實現合適的Scanner
接口便可。
最後,改變Issue
的範圍並關閉CLASS_FILE_SCOPE
。此處,要想找到問題,只需分析一個 Java 類文件便可。有時,你須要分析多個 Java 類文件才能發現問題,因此你須要使用ALL_CLASS_FILES
。範圍的選擇很是重要,所以請當心謹慎。點擊此處可查看所有範圍。
雖然問題描述可能不很清楚,但一個Detector
能夠發現多個問題。此外,經過一次運行就能處理全部問題,所以能夠有效提升應用性能。
規則二的 Lint 報告結果舉例以下:
此處,咱們遺漏了一項重要的事情:登記!咱們須要將新建立的問題登記到全部處理過的 lint 檢查列表中:
public final class CaptainRegistry extends IssueRegistry { @Override public List<Issue> getIssues() { return Arrays.asList(LoggerUsageDetector.ISSUE, AttrPrefixDetector.ISSUE); } }
如你所見,登記過程也很是簡單。咱們只需繼承IssueRegistry
類並實現getIssues
方法,從而返回咱們的自定義問題。該類必須與早前在build.gradle
中聲明的類保持一致。
雖然只展現了兩個簡單的例子,但筆者但願你們能知道:Lint 是很是強大的。只是你要編寫適合本身的規則。
本文只展現了兩種類型(Detector/Scanner
),還有許多其餘類型:GradleScanner
,OtherFileScanner
等着你發現。多多嘗試,找到最適合你的類。
筆者建議,在編寫自定義規則以前,首先閱讀系統 Lint 規則,從而幫助你理解其用處及用法。其源碼能夠在此處下載。
最後,Lint 能幫助你解決開發中的錯誤,請必定要用哦!
Find below all materials that helped me:
如下爲筆者的參考資料:
原文地址:http://jeremie-martinez.com/2015/12/15/custom-lint-rules/
OneAPM Mobile Insight ,監控網絡請求及網絡錯誤,提高用戶留存。訪問 OneAPM 官方網站感覺更多應用性能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術博客。
本文轉自 OneAPM 官方博客