什么是 Lint
Android Lint 是 SDK Tools 16(ADT 16)開始引入的一個(gè)代碼掃描工具,通過對(duì)代碼進(jìn)行靜態(tài)分析,可以幫助開發(fā)者發(fā)現(xiàn)代碼質(zhì)量問題和提出一些改進(jìn)建議。除了檢查 Android 項(xiàng)目源碼中潛在的錯(cuò)誤,對(duì)于代碼的正確性、安全性、性能、易用性、便利性和國(guó)際化方面也會(huì)作出檢查。
Android Lint 作為項(xiàng)目的代碼檢測(cè)工具,是因?yàn)樗哂幸韵聨讉€(gè)特性:
- 已經(jīng)被集成到 Android Studio,使用方便。
- 能在編寫代碼時(shí)實(shí)時(shí)反饋出潛在的問題。
- 可以自定義規(guī)則。Android Lint 本身包含大量已經(jīng)封裝好的接口,能提供豐富的代碼信息,開發(fā)者可以基于這些信息進(jìn)行自定義規(guī)則的編寫。
先來(lái)一張神圖
lint后具體信息.png
一、開始使用
Android Lint 的工作過程比較簡(jiǎn)單,一個(gè)基礎(chǔ)的 Lint 過程由 Lint Tool(檢測(cè)工具),Source Files(項(xiàng)目源文件) 和 lint.xml(配置文件) 三個(gè)部分組成,Lint Tool 讀取 Source Files,根據(jù) lint.xml 配置的規(guī)則(issue)輸出結(jié)果(如下圖)。

1.1Android studio使用
Android Studio 中,Android Lint 已經(jīng)被集成,只需要點(diǎn)擊菜單 —— Analyze —— Inspect Code 即可運(yùn)行 Android Lint,在彈出的對(duì)話框中可以設(shè)置執(zhí)行 Lint 的范圍,可以選擇整個(gè)項(xiàng)目,也可以只選擇當(dāng)前的子模塊或者其他自定義的范圍:

檢查完畢后會(huì)彈出 Inspection 的控制臺(tái),并在其中列出詳細(xì)的檢查結(jié)果:

如上圖所展示的,Android Lint 對(duì)檢查的結(jié)果進(jìn)行了分類,同一個(gè)規(guī)則(issue)下的問題會(huì)聚合,其中針對(duì) Android 的規(guī)則類別會(huì)在分類前說(shuō)明是 Android 相關(guān)的,主要是六類:
- Accessibility 無(wú)障礙,例如 ImageView 缺少contentDescription 描述,String 編碼字符串等問題。
- Correctness 正確性
- Internationalization 國(guó)際化,如字符缺少翻譯等問題。
- Performance 性能,例如在 onMeasure、onDraw 中執(zhí)行 new,內(nèi)存泄露,產(chǎn)生了冗余的資源,xml 結(jié)構(gòu)冗余等。
- Security 安全性,例如沒有使用 HTTPS 連接 Gradle,AndroidManifest 中的權(quán)限問題等。
-
Usability 易用性,例如缺少某些倍數(shù)的切圖,重復(fù)圖標(biāo)等。
其他的結(jié)果條目則是針對(duì) Java 語(yǔ)法的問題,另外每一個(gè)問題都有區(qū)分嚴(yán)重程度(severity),從高到底依次是:
Fatal
Error
Warning
Information
Ignore
其中 Fatal 和 Error 都是指錯(cuò)誤,但是 Fatal 類型的錯(cuò)誤會(huì)直接中斷 ADT 導(dǎo)出 APK,更為嚴(yán)重。
在結(jié)果列表中點(diǎn)擊一個(gè)條目,可以看到詳細(xì)的源文件名和位置,以及命中的錯(cuò)誤規(guī)則(issue)、解決方案或者屏蔽提示
除了直接在菜單中運(yùn)行 Lint 外,大部分問題代碼在編寫時(shí) Android Studio 就會(huì)給出提醒:
1.2配置
對(duì)于執(zhí)行 Lint 操作的相關(guān)配置,是定義在 gradle 文件的 lintOptions 中,可定義的選項(xiàng)及其默認(rèn)值
android {
lintOptions {
// 設(shè)置為 true,則當(dāng) Lint 發(fā)現(xiàn)錯(cuò)誤時(shí)停止 Gradle 構(gòu)建
abortOnError false
// 設(shè)置為 true,則當(dāng)有錯(cuò)誤時(shí)會(huì)顯示文件的全路徑或絕對(duì)路徑 (默認(rèn)情況下為true)
absolutePaths true
// 僅檢查指定的問題(根據(jù) id 指定)
check 'NewApi', 'InlinedApi'
// 設(shè)置為 true 則檢查所有的問題,包括默認(rèn)不檢查問題
checkAllWarnings true
// 設(shè)置為 true 后,release 構(gòu)建都會(huì)以 Fatal 的設(shè)置來(lái)運(yùn)行 Lint。
// 如果構(gòu)建時(shí)發(fā)現(xiàn)了致命(Fatal)的問題,會(huì)中止構(gòu)建(具體由 abortOnError 控制)
checkReleaseBuilds true
// 不檢查指定的問題(根據(jù)問題 id 指定)
disable 'TypographyFractions','TypographyQuotes'
// 檢查指定的問題(根據(jù) id 指定)
enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
// 在報(bào)告中是否返回對(duì)應(yīng)的 Lint 說(shuō)明
explainIssues true
// 寫入報(bào)告的路徑,默認(rèn)為構(gòu)建目錄下的 lint-results.html
htmlOutput file("lint-report.html")
// 設(shè)置為 true 則會(huì)生成一個(gè) HTML 格式的報(bào)告
htmlReport true
// 設(shè)置為 true 則只報(bào)告錯(cuò)誤
ignoreWarnings true
// 重新指定 Lint 規(guī)則配置文件
lintConfig file("default-lint.xml")
// 設(shè)置為 true 則錯(cuò)誤報(bào)告中不包括源代碼的行號(hào)
noLines true
// 設(shè)置為 true 時(shí) Lint 將不報(bào)告分析的進(jìn)度
quiet true
// 覆蓋 Lint 規(guī)則的嚴(yán)重程度,例如:
severityOverrides ["MissingTranslation": LintOptions.SEVERITY_WARNING]
// 設(shè)置為 true 則顯示一個(gè)問題所在的所有地方,而不會(huì)截短列表
showAll true
// 配置寫入輸出結(jié)果的位置,格式可以是文件或 stdout
textOutput 'stdout'
// 設(shè)置為 true,則生成純文本報(bào)告(默認(rèn)為 false)
textReport false
// 設(shè)置為 true,則會(huì)把所有警告視為錯(cuò)誤處理
warningsAsErrors true
// 寫入檢查報(bào)告的文件(不指定默認(rèn)為 lint-results.xml)
xmlOutput file("lint-report.xml")
// 設(shè)置為 true 則會(huì)生成一個(gè) XML 報(bào)告
xmlReport false
// 將指定問題(根據(jù) id 指定)的嚴(yán)重級(jí)別(severity)設(shè)置為 Fatal
fatal 'NewApi', 'InlineApi'
// 將指定問題(根據(jù) id 指定)的嚴(yán)重級(jí)別(severity)設(shè)置為 Error
error 'Wakelock', 'TextViewEdits'
// 將指定問題(根據(jù) id 指定)的嚴(yán)重級(jí)別(severity)設(shè)置為 Warning
warning 'ResourceAsColor'
// 將指定問題(根據(jù) id 指定)的嚴(yán)重級(jí)別(severity)設(shè)置為 ignore
ignore 'TypographyQuotes'
}
}
lint.xml 這個(gè)文件則是配置 Lint 需要禁用哪些規(guī)則(issue),以及自定義規(guī)則的嚴(yán)重程度(severity),lint.xml 文件是通過 issue 標(biāo)簽指定對(duì)一個(gè)規(guī)則的控制,在項(xiàng)目根目錄中建立一個(gè) lint.xml 文件后 Android Lint 會(huì)自動(dòng)識(shí)別該文件,在執(zhí)行檢查時(shí)按照 lint.xml 的內(nèi)容進(jìn)行檢查。如上面提到的那樣,開發(fā)者也可以通過 lintOptions 中的 lintConfig 選項(xiàng)來(lái)指定配置文件。一個(gè) lint.xml 示例如下:

issue 標(biāo)簽中使用 id 指定一個(gè)規(guī)則,severity="ignore" 則表明禁用這個(gè)規(guī)則。需要注意的是,某些規(guī)則可以通過 ignore 標(biāo)簽指定僅對(duì)某些屬性禁用,例如上面的 Deprecated,表示檢查是否有使用不推薦的屬性和方法,而在 issue 標(biāo)簽中包裹一個(gè) ignore 標(biāo)簽,在 ignore 標(biāo)簽的 regexp 屬性中使用正則表達(dá)式指定了 singleLine,則表明對(duì) singleLine 這個(gè)屬性屏蔽檢查。
另外開發(fā)者也可以使用 @SuppressLint(issue id) 標(biāo)注針對(duì)某些代碼忽略某些 Lint 檢查,這個(gè)標(biāo)注既可以加到成員變量之前,也可以加到方法聲明和類聲明之前,分別針對(duì)不同范圍進(jìn)行屏蔽。
二、展開敘述
2.2.1Correctness (不是全部,常見的)
Appcompat Custom Widgets
Appcompat自定義小部件一般會(huì)讓你繼承自 android.support.v7.widget.AppCompat...
不要直接擴(kuò)展android.widget類,而應(yīng)該擴(kuò)展android.support.v7.widget.AppCompat中的一個(gè)委托類。Attribute unused on older versions
舊版本未使用的屬性 針對(duì)具有minSdkVersion
這不是一個(gè)錯(cuò)誤; 應(yīng)用程序?qū)⒑?jiǎn)單地忽略該屬性。
可以選擇在layout-vNN文件夾中創(chuàng)建一個(gè)布局的副本 將在API NN或更高版本上使用,您可以利用更新的屬性。Class is not registered in the manifest
類未在清單中注冊(cè)
Activities, services and content providers should be registered in the AndroidManifest.xml fileCombining Ellipsize and Maxlines
Ellipsize和Maxlines相結(jié)合
結(jié)合ellipsize和maxLines = 1可能導(dǎo)致某些設(shè)備崩潰。 早期版本的lint建議用maxLines = 1替換singleLine = true,但在使用ellipsize時(shí)不應(yīng)該這樣做Extraneous text in resource files
資源文件中的無(wú)關(guān)文本Hardcoded reference to /sdcard
硬編碼參考/ SD卡
代碼不應(yīng)該直接引用/ sdcard路徑; 而是使用:Environment.getExternalStorageDirectory().getPath()
不要直接引用/ data / data /路徑; 它可以在多用戶場(chǎng)景中有所不同。
Implied default locale in case conversion
在轉(zhuǎn)換的情況下默認(rèn)的默認(rèn)語(yǔ)言環(huán)境Implied locale in date format
隱含的日期格式的區(qū)域設(shè)置
調(diào)用者都應(yīng)該使用getDateInstance(),getDateTimeInstance()或getTimeInstance()來(lái)獲得適合用戶語(yǔ)言環(huán)境的SimpleDateFormat的現(xiàn)成實(shí)例。Likely cut & paste mistakes
可能剪切和粘貼錯(cuò)誤
剪切和粘貼調(diào)用findViewById但忘記更新R.id字段的情況。 有可能你的代碼只是(冗余)重復(fù)查找字段Mismatched Styleable/Custom View Name
不匹配的樣式/自定義視圖名稱
自定義視圖的慣例是使用名稱與自定義視圖類名稱相匹配的聲明樣式。Missing Permissions
缺少權(quán)限Nested scrolling widgets
嵌套的滾動(dòng)小部件
A scrolling widget such as a ScrollView should not contain any nested scrolling widgets since this has various usability issuesObsolete Gradle Dependency 已過時(shí)的Gradle依賴關(guān)系
Target SDK attribute is not targeting latest version 目標(biāo)SDK屬性未定位到最新版本
Using 'px' dimension 使用“px”維度
Using android.media.ExifInterface 使用android.media.ExifInterface
舊版的有一些漏洞,使用支持庫(kù)中的Using dp instead of sp for text sizes 使用dp代替文本大小的sp
Using Private APIs 使用私有API
Using private resources 使用私人資源
2.2.2Internationalization
Hardcoded text
硬編碼文本
直接在布局文件中對(duì)文本屬性進(jìn)行硬編碼是有缺陷的
should use @string resourceOverlapping items in RelativeLayout
在RelativeLayout中重疊項(xiàng)目
如果相對(duì)布局的文本或按鈕項(xiàng)左右對(duì)齊,則由于本地化的文本擴(kuò)展,它們可以相互重疊,除非它們具有toEndOf / toStartOf之類的相互約束。Padding and margin symmetry
填充和邊緣對(duì)稱
如果您在布局的左側(cè)指定填充或邊距,則應(yīng)該也可以在右側(cè)指定填充(反之亦然),以便從右到左布局對(duì)稱。
TextView Internationalization
TextView國(guó)際化
永遠(yuǎn)不要調(diào)用Number#toString()來(lái)格式化數(shù)字; 它不會(huì)正確處理分?jǐn)?shù)分隔符和區(qū)域特定的數(shù)字
使用具有適當(dāng)格式規(guī)范(%d或%f)的String#格式
不要傳遞字符串(例如“Hello”)來(lái)顯示文本。 硬編碼文本無(wú)法正確翻譯成其他語(yǔ)言,考慮使用Android資源字符串
不要通過連接文本塊來(lái)構(gòu)建消息。 這樣的消息不能被正確翻譯。Using left/right instead of start/end attributes
使用左/右而不是開始/結(jié)束屬性
2.2.3Performance
-
Handler reference leaks
handler導(dǎo)致的泄漏
由于該Handler被聲明為內(nèi)部類,所以可以防止外部類被垃圾收集。 如果處理程序?qū)χ骶€程以外的線程使用Looper或MessageQueue,則不存在問題。 如果處理程序正在使用主線程的Looper或MessageQueue,則需要修復(fù)Handler聲明,
解決:將Handler聲明為靜態(tài)類; 在外部類中,實(shí)例化WeakReference到外部類,并在實(shí)例化Handler時(shí)將此對(duì)象傳遞給Handler; 使用WeakReference對(duì)象來(lái)引用外部類的所有成員。
HashMap can be replaced with SparseArray
HashMap可以用SparseArray替換
對(duì)于鍵類型為integer的映射,使用Android SparseArray API通常效率更高。Inefficient layout weight
低效的布局權(quán)重
當(dāng)LinearLayout中只有一個(gè)控件定義了一個(gè)權(quán)重時(shí),為它指定一個(gè)0dp的寬度/高度會(huì)更有效率,因?yàn)樗鼘⑽账械氖S嗫臻g。 如果聲明的寬度/高度為0dp,則不必首先測(cè)量其自己的大小。Layout has too many views
布局有太多的意見
在單個(gè)布局中使用太多的視圖對(duì)性能不利。 考慮使用復(fù)合繪圖或其他技巧來(lái)減少此布局中的視圖數(shù)量。 最大視圖數(shù)量默認(rèn)為80,但可以使用環(huán)境變量ANDROID_LINT_MAX_VIEW_COUNT進(jìn)行配置。Layout hierarchy is too deep
布局層次太深
嵌套太多的布局對(duì)性能不利。 考慮使用更平坦的布局(比如RelativeLayout或GridLayout)。默認(rèn)的最大深度是10,但可以使用環(huán)境變量ANDROID_LINT_MAX_DEPTH進(jìn)行配置。Memory allocations within drawing code
內(nèi)存分配在繪圖代碼
應(yīng)該避免在繪圖或布局操作中分配對(duì)象。 這些被頻繁地調(diào)用,所以平滑的UI可以被對(duì)象分配造成的垃圾收集暫停中斷。 通常處理的方式是預(yù)先分配所需的對(duì)象,并為每個(gè)繪圖操作重新使用它們。 有些方法代表您分配內(nèi)存(如Bitmap.create),并且應(yīng)該以相同的方式處理這些內(nèi)存。Missing @Keep for Animated Properties
屬性動(dòng)畫缺少@Keep
當(dāng)你使用屬性動(dòng)畫師時(shí),屬性可以通過反射來(lái)訪問。 這些方法應(yīng)該使用@Keep注釋,以確保在發(fā)布構(gòu)建期間,這些方法不會(huì)被視為未被使用和刪除,或者被視為內(nèi)部的,并被重新命名為更短。 這個(gè)檢查還會(huì)標(biāo)記出其他可能遇到的反射問題,比如缺少屬性,錯(cuò)誤的參數(shù)類型等等。Missing baselineAligned attribute
缺少baselineAligned屬性
當(dāng)使用LinearLayout在嵌套布局之間按比例分配空間時(shí),應(yīng)關(guān)閉基線對(duì)齊屬性以使布局計(jì)算速度更快。Node can be replaced by a TextView with compound drawables
節(jié)點(diǎn)可以用復(fù)合可繪制的TextView替換
包含ImageView和TextView的LinearLayout可以更有效地處理為復(fù)合可繪制(單個(gè)TextView,使用drawableTop,drawableLeft,drawableRight和/或drawableBottom屬性在文本旁邊繪制一個(gè)或多個(gè)圖像)。 如果這兩個(gè)小部件彼此之間有空白,則可以用drawablePadding屬性替換。Obsolete layout params
過時(shí)的布局參數(shù)
Obsolete SDK_INT Version Check
已過時(shí)的SDK_INT版本檢查
此檢查標(biāo)志版本檢查不是必需的,因?yàn)閙inSdkVersion(或周圍已知的API級(jí)別)已經(jīng)至少與檢查的版本一樣高。
它還會(huì)在-vNN文件夾中查找資源,如版本限定符小于或等于minSdkVersion的values-v14,其中內(nèi)容應(yīng)合并到最佳文件夾中。Static Field Leaks
靜態(tài)常量--持有fragment及activity的引用
非靜態(tài)內(nèi)部類對(duì)其外部類具有隱式引用。
如果該外部類是例如fragment或activity,如果長(zhǎng)時(shí)間運(yùn)行的處理程序/加載程序/任務(wù)將持有對(duì)該activity的引用,長(zhǎng)時(shí)間沒有被回收掉。Useless parent layout
無(wú)用的父母布局
具有沒有兄弟的孩子的布局不是滾動(dòng)視圖或根布局,并且沒有背景,可以被移除并且其子節(jié)點(diǎn)直接移動(dòng)到父節(jié)點(diǎn)以獲得更平坦和更高效的布局分層結(jié)構(gòu)。View Holder Candidates
查看持有人候選人
Should use View Holder pattern
2.2.4Security
Cipher.getInstance with ECB
Cipher.getInstance與ECB
不應(yīng)使用ECB作為cipher mode或不設(shè)置cipher mode來(lái)調(diào)用Cipher#getInstance,因?yàn)閍ndroid上的默認(rèn)模式是ECB,這是不安全的。(加解密)Content provider does not require permission
內(nèi)容提供者不需要權(quán)限
內(nèi)容提供程序默認(rèn)導(dǎo)出,系統(tǒng)上的任何應(yīng)用程序都可能使用它們來(lái)讀取和寫入數(shù)據(jù)。 如果內(nèi)容提供者提供對(duì)敏感數(shù)據(jù)的訪問,則應(yīng)該通過在清單中指定export = false來(lái)保護(hù)它,或者通過可以授予其他應(yīng)用程序的權(quán)限來(lái)保護(hù)它。Exported service does not require permission
導(dǎo)出的服務(wù)不需要權(quán)限
導(dǎo)出的服務(wù)(設(shè)置了exported = true或者包含intent-filter并且不指定exported = false的服務(wù))應(yīng)該定義一個(gè)實(shí)體為了啟動(dòng)服務(wù)或綁定到服務(wù)而必須擁有的權(quán)限。 沒有這個(gè),任何應(yīng)用程序都可以使用此服務(wù)。Hardware Id Usage
硬件ID使用情況
不建議使用這些設(shè)備標(biāo)識(shí)符,除了高價(jià)值欺詐預(yù)防和高級(jí)電話使用情況。
getLine1Number獲取手機(jī)號(hào),getDeviceId設(shè)備IMEI,getMacAddressMAC地址
Incorrect constant
不正確的常量Insecure TLS/SSL trust manager
不安全的TLS / SSL信任管理器Missing @JavascriptInterface on methods
缺少@JavascriptInterface方法openFileOutput() or similar call passing MODE_WORLD_READABLE
openFileOutput()或類似的調(diào)用傳遞MODE_WORLD_READABLEopenFileOutput() or similar call passing MODE_WORLD_WRITEABLE
openFileOutput()或類似的調(diào)用傳遞MODE_WORLD_WRITEABLE
在某些情況下,應(yīng)用程序可以編寫世界可寫文件,但應(yīng)仔細(xì)檢查這些文件以確保它們不包含私人數(shù)據(jù),并且如果文件被惡意應(yīng)用程序修改,則不會(huì)欺騙或破壞應(yīng)用程序。Receiver does not require permission
接收者不需要許可Using setJavaScriptEnabled 使用setJavaScriptEnabled
如果您不確定您的應(yīng)用程序確實(shí)需要JavaScript支持,那么您的代碼不應(yīng)該調(diào)用setJavaScriptEnabled。
2.2.6Usability
-
Button should be borderless
按鈕應(yīng)該是無(wú)邊界的
兩個(gè) Buttons 放在一個(gè)布局里會(huì)被判斷為按鈕欄,需要添加樣式取消它的邊框
在 Buttons 上添加屬性 style="?android:attr/buttonBarButtonStyle" 。系統(tǒng)提示也可以在按鈕的父布局上添加 style="? android:attr/buttonBarStyle" 屬性
Ellipsis string can be replaced with ellipsis character
省略號(hào)字符串可以用省略號(hào)字符替換
Replace "..." with ellipsis character (…, …) ?Hyphen can be replaced with dash
連字符可以用短劃線代替
Replace "-" with an "en dash" character (–, –) ?Missing View constructors for XML inflation
缺少XML通貨膨脹的視圖構(gòu)造函數(shù)Text size is too small
文字太小
避免使用小于12sp的尺寸。小于12sp的字體會(huì)太小導(dǎo)致用戶看不清
2.2其他類型
Class structure 類結(jié)構(gòu)
Code maturity issues 代碼成熟度問題
Code style issues 代碼樣式問題
Compiler issues 編譯器問題
Control flow issues 控制流量問題
Data flow issues 數(shù)據(jù)流問題
Declaration redundancy 聲明冗余
Error handling 錯(cuò)誤處理
General 一般
Imports 進(jìn)口
J2ME issues J2ME問題
Java 5 Java 5
Java 7 Java 7
Java language level migration aids Java語(yǔ)言級(jí)別的遷移輔助
Javadoc issues Javadoc問題
Naming conventions 命名約定
Numeric issues 數(shù)字問題
Performance issues 性能問題
Probable bugs 可能的錯(cuò)誤
Properties Files 屬性文件
Spelling 拼字
Style 樣式
Verbose or redundant code constructs 詳細(xì)或冗余的代碼結(jié)構(gòu)
XML XML
2.2.1Class structure
Field can be local字段可以是本地的
Parameter can be local參數(shù)可以是本地的
'private' method declared 'final'
'static' method declared 'final''
2.2.2Code maturity issues 代碼成熟度問題
Deprecated API usage不推薦使用API
Deprecated member is still used不推薦使用的成員仍在使用
2.2.3Code style issues 代碼樣式問題
Unnecessary enum modifier不必要的枚舉修飾符
Unnecessary interface modifier不必要的界面修飾符
Unnecessary semicolon不必要的分號(hào)
private public
2.2.4Compiler issues 編譯器問題
Unchecked warning未經(jīng)檢查的警告
2.2.5Control flow issues 控制流問題
Double negation雙重否定
Pointless boolean expression無(wú)意義的布爾表達(dá)式
Redundant 'if' statement冗余“if”語(yǔ)句
Redundant conditional expression冗余的條件表達(dá)式
Simplifiable boolean expression簡(jiǎn)化布爾表達(dá)式
Simplifiable conditional expression簡(jiǎn)化條件表達(dá)式
Unnecessary 'return' statement不必要的“return”聲明
return;
2.2.6Data flow issues 數(shù)據(jù)流問題
Boolean method is always inverted布爾方法總是倒置的
Redundant local variable冗余局部變量
2.2.7Declaration redundancy 聲明冗余
Access static member via instance reference通過實(shí)例引用訪問靜態(tài)成員
this.minsize = this.maxsize;
Actual method parameter is the same constant實(shí)際的方法參數(shù)是相同的常量
Actual value of parameter ''register'' is always ''true''
Declaration access can be weaker聲明訪問權(quán)限可以再弱
Can be private
Declaration can have final modifier宣言可以有最終的修改
Duplicate throws重復(fù)拋出
Empty method空方法
Method can be void方法可以是無(wú)效的
Method returns the same value方法返回相同的值
All implementations of this method always return '3'
Redundant throws clause冗余拋出子句
The declared exception 'UnsupportedEncodingException' is never thrown
Unnecessary module dependency不必要的模塊依賴
Unused declaration未使用的聲明(方法,變量)
2.2.8Error handling 錯(cuò)誤處理
Caught exception is immediately rethrown捕獲到的異常立即被重新拋出
Empty 'catch' block空'catch'塊
'return' inside 'finally' block在'finally'塊中'返回'
'throw' inside 'finally' block在“finally”塊內(nèi)“拋出”
2.2.9General
Annotator注解者
Default File Template Usage默認(rèn)文件模板的用法
2.2.10Imports 導(dǎo)入
Unused import沒有用到的導(dǎo)入
2.2.11J2ME issues J2ME問題
'if'語(yǔ)句可以用&&或||代替 表達(dá)
2.2.12Java 5 Java 5
'for' loop replaceable with 'foreach''for'循環(huán)可替換為'foreach'
'indexOf()' expression is replaceable with 'contains()''indexOf()'表達(dá)式可以用'contains()'來(lái)替換
'StringBuffer' may be 'StringBuilder''StringBuffer'可能是'StringBuilder'
Unnecessary boxing不必要的裝箱
Unnecessary unboxing不必要的拆箱
'while' loop replaceable with 'foreach''while'循環(huán)可以替換'foreach'
2.2.13Java 7 Java 7
Explicit type can be replaced with <>顯式類型可以用<>來(lái)替換
'試試最后'用資源替換'試用'
2.2.14Java language level migration aids Java語(yǔ)言級(jí)別的遷移輔助
'if' replaceable with 'switch'
2.2.15Javadoc issues Javadoc問題
Dangling Javadoc comment 搖搖晃晃的Javadoc評(píng)論
Declaration has Javadoc problems 宣言有Javadoc問題
Declaration has problems in Javadoc 聲明在Javadoc引用中有問題
2.2.16Naming conventions 命名約定
2.2.17Numeric issues 數(shù)字問題
數(shù)字溢出 Numeric overflow
八進(jìn)制整數(shù) Octal integer
無(wú)意義的算術(shù)表達(dá)式 Pointless arithmetic expression
2.2.18Performance issues 性能問題
Redundant 'String.toString()' 冗余'String.toString()'
Redundant 'substring(0)' call 冗余'substring(0)'調(diào)用
Redundant call to 'String.format()' 冗余調(diào)用'String.format()'
String concatenation as argument to 'StringBuffer.append()' call 字符串連接作為“StringBuffer.append()”調(diào)用的參數(shù)
String concatenation in loop 循環(huán)中的字符串連接
'StringBuffer' can be replaced with 'String' 'StringBuffer'可以替換為'String'
2.2.19Probable bugs 可能的錯(cuò)誤
Collection added to self Collection添加到自我
Constant conditions & exceptions 不變的條件和例外
Mismatched query and update of collection 不匹配的查詢和集合更新
Mismatched query and update of StringBuilder 不匹配的查詢和更新的StringBuilder
@NotNull/@Nullable problems @NotNull / @可空問題
Result of method call ignored 方法調(diào)用的結(jié)果被忽略
Statement with empty body 聲明與空的實(shí)現(xiàn)
String comparison using '==', instead of 'equals()' 使用'=='進(jìn)行字符串比較,而不是'equals()'
Suspicious collections method calls 可疑collections方法調(diào)用
Suspicious variable/parameter name combination 可疑變量/參數(shù)名稱組合
Unused assignment 沒用的賦值操作
2.2.20Properties Files 屬性文件
Unused Property未使用的屬性
2.2.21Spelling 拼字
2.2.22Style 樣式
Unnecessary semicolon沒必要的分號(hào)
2.2.23Verbose or redundant code constructs 詳細(xì)或冗余的代碼結(jié)構(gòu)
Redundant array creation創(chuàng)建冗余陣列
Redundant type cast冗余類型轉(zhuǎn)換
2.2.24XML XML
Deprecated API usage in XML 在XML中不推薦使用API
Unbound XML namespace prefix 未綁定的XML名稱空間前綴
Unused XML schema declaration 未使用的XML模式聲明
XML highlighting XML突出顯示
XML tag empty body XML標(biāo)簽為空的正文
三、自定義lint
3.1創(chuàng)建工程
創(chuàng)建自定義 Lint 需要?jiǎng)?chuàng)建一個(gè) Java 項(xiàng)目,項(xiàng)目中需要引入 Android Lint 的包,項(xiàng)目的 build.gradle 如下:
apply plugin: 'java'
configurations {
lintChecks
}
dependencies {
compile "com.android.tools.lint:lint-api:25.1.2"
compile "com.android.tools.lint:lint-checks:25.1.2"
lintChecks files(jar)
}
jar {
manifest {
attributes('Lint-Registry': 'com.qmuiteam.qmui.lint.QMUIIssueRegistry')
}
}
其中 lint-api 是 Android Lint 的官方接口,基于這些接口可以獲取源代碼信息,從而進(jìn)行分析,lint-checks 是官方已有的檢查規(guī)則。Lint-Registry 表示給自定義規(guī)則注冊(cè),以及打包為 jar.
3.2 Detector
Detector 是自定義規(guī)則的核心,它的作用是掃描代碼,從而獲取代碼中的各種信息,然后基于這些信息進(jìn)行提醒和報(bào)告,在本場(chǎng)景中,我們需要掃描 Java 代碼,找到 getDrawable 方法的調(diào)用,然后分析其中傳入的 Drawable 是否為 Vector Drawable,如果是則需要進(jìn)行報(bào)告,完整代碼如下:
/**
* 檢測(cè)是否在 getDrawable 方法中傳入了 Vector Drawable,在 4.0 及以下版本的系統(tǒng)中會(huì)導(dǎo)致 Crash
*/
public class QMUIJavaVectorDrawableDetector extends Detector implements Detector.JavaScanner {
public static final Issue ISSUE_JAVA_VECTOR_DRAWABLE =
Issue.create("QMUIGetVectorDrawableWithWrongFunction",
"Should use the corresponding method to get vector drawable.",
"Using the normal method to get the vector drawable will cause a crash on Android versions below 4.0",
Category.ICONS, 2, Severity.ERROR,
new Implementation(QMUIJavaVectorDrawableDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public List<String> getApplicableMethodNames() {
return Collections.singletonList("getDrawable");
}
@Override
public void visitMethod(@NonNull JavaContext context, AstVisitor visitor, @NonNull MethodInvocation node) {
StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
if (args.isEmpty()) {
return;
}
Project project = context.getProject();
List<File> resourceFolder = project.getResourceFolders();
if (resourceFolder.isEmpty()) {
return;
}
String resourcePath = resourceFolder.get(0).getAbsolutePath();
for (Expression expression : args) {
String input = expression.toString();
if (input != null && input.contains("R.drawable")) {
// 找出 drawable 相關(guān)的參數(shù)
// 獲取 drawable 名字
String drawableName = input.replace("R.drawable.", "");
try {
// 若 drawable 為 Vector Drawable,則文件后綴為 xml,根據(jù) resource 路徑,drawable 名字,文件后綴拼接出完整路徑
FileInputStream fileInputStream = new FileInputStream(resourcePath + "/drawable/" + drawableName + ".xml");
BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream));
String line = reader.readLine();
if (line.contains("vector")) {
// 若文件存在,并且包含首行包含 vector,則為 Vector Drawable,拋出警告
context.report(ISSUE_JAVA_VECTOR_DRAWABLE, node, context.getLocation(node), expression.toString() + " 為 Vector Drawable,請(qǐng)使用 getVectorDrawable 方法獲取,避免 4.0 及以下版本的系統(tǒng)產(chǎn)生 Crash");
}
fileInputStream.close();
} catch (Exception ignored) {
}
}
}
}
}
QMUIJavaVectorDrawableDetector 繼承于 Detector,并實(shí)現(xiàn)了 Detector.JavaScanner 接口,實(shí)現(xiàn)什么接口取決于自定義 Lint 需要掃描什么內(nèi)容,以及希望從掃描的內(nèi)容中獲取何種信息。Android Lint 提供了大量不同范圍的 Detector:
- Detector.BinaryResourceScanner 針對(duì)二進(jìn)制資源,例如 res/raw 等目錄下的各種 Bitmap
- Detector.ClassScanner 相對(duì)于 Detector.JavaScanner,更針對(duì)于類進(jìn)行掃描,可以獲取類的各種信息
- Detector.GradleScanner 針對(duì) Gradle 進(jìn)行掃描
- Detector.JavaScanner 針對(duì) Java 代碼進(jìn)行掃描
- Detector.ResourceFolderScanner 針對(duì)資源目錄進(jìn)行掃描,只會(huì)掃描目錄本身
- Detector.XmlScanner 針對(duì) xml 文件進(jìn)行掃描
- Detector.OtherFileScanner 用于除上面6種情況外的其他文件
不同的接口定義了各種方法,實(shí)現(xiàn)自定義 Lint 實(shí)際上就是實(shí)現(xiàn) Detector 中的各種方法,在上面的例子中,getApplicableMethodNames 的返回值指定了需要被檢查的方法,visitMethod 則可以接收檢查到的方法對(duì)應(yīng)的信息,這個(gè)方法包含三個(gè)參數(shù),其作用分別是:
- context 這里的 context 是一個(gè) JavaContext,主要的功能是獲取主項(xiàng)目的信息,以及進(jìn)行報(bào)告(包括獲取需要被報(bào)告的代碼的位置等)。
- visitor visitor 是一個(gè) ASTVisitor,即 AST(抽象語(yǔ)法樹)的訪問者類,Android Lint 把掃描到的代碼抽象成 AST,方便開發(fā)者以節(jié)點(diǎn) - 屬性的形式獲取信息,visitor 則可以方便地獲取當(dāng)前節(jié)點(diǎn)的相關(guān)節(jié)點(diǎn)。
- node 這是一個(gè) MethodInvocation 實(shí)例,MethodInvocation 是 Android Lint 里的 AST 子類,在上面的例子中,node 表示的是被掃描到的方法,所以我們可以通過節(jié)點(diǎn) - 屬性的形式獲取被掃描的方法的參數(shù)等各種信息。
在例子中我們獲取方法的參數(shù),通過遍歷參數(shù)拿到 Drawable 參數(shù),分解出 Drawable 的文件名,然后通過 context 獲取主項(xiàng)目的資源路徑,配合 Drawable 的文件名拼接文件的實(shí)際路徑,確定文件存在后檢查文件內(nèi)容開頭是否包含 “vector” 這個(gè)字符串,如果是則表示開發(fā)者在普通的 getDrawable 方法中傳入了 Vector Drawable,最后調(diào)用 context 的 report 方法進(jìn)行報(bào)告。
值得注意的是,在例子中我們并沒有直接實(shí)例 Drawable,然后通過 Drawable 的方法判斷是否為 Vector Drawable,而是通過較為繁瑣的步驟檢查文件內(nèi)容,這是因?yàn)?Android Lint 的項(xiàng)目是一個(gè)純 Java 項(xiàng)目,不能使用 android.graphics 等包,因而開發(fā)時(shí)會(huì)比較繁瑣。
3.3 Issue
在檢查出問題需要進(jìn)行報(bào)告時(shí),context.report 方法中傳入了一個(gè) ISSUE_JAVA_VECTOR_DRAWABLE,這里的"issue"是聲明一個(gè)規(guī)則,因此自定義一個(gè) Lint 規(guī)則就需要定義一個(gè) issue。issue 由類方法 Issue.create 創(chuàng)建,參數(shù)如下:
- id:標(biāo)記 issue 的唯一值,語(yǔ)義上要能簡(jiǎn)短描述問題,使用 Java 注解和 XML 屬性屏蔽 Lint 時(shí),就需要使用這個(gè) id。
- summary:概況地描述問題,不需要給出解決辦法。
- explanation:詳細(xì)地描述問題以及給出解決辦法。
- category:?jiǎn)栴}類別,在系統(tǒng)給出的分類中選擇,后面會(huì)詳述。
- priority:1-10 的數(shù)字,表示優(yōu)先級(jí),10 為最嚴(yán)重。
- severity:嚴(yán)重級(jí)別,在 Fatal,Error,Warning,Informational,Ignore 中選擇一個(gè)。
-
Implementation:Detector 與 Issue 的映射關(guān)系,需要傳入當(dāng)前的 Detector 類,以及掃描代碼的范圍,例如 Java 文件、Resource 文件或目錄等范圍。
如下圖,產(chǎn)生問題時(shí),問題的提醒信息就就會(huì)顯示相關(guān)的 Issue 的 id 等信息。
123.png
3.4 Category
Category 用于給 Issue 分類,系統(tǒng)已經(jīng)提供了幾個(gè)常用的分類,系統(tǒng) Issue(即 Android Lint 自帶的檢查規(guī)則)也是使用這個(gè) Category:
- Lint
- Correctness (子分類 Messages)
- Security
- Performance
- Usability (子分類 Typography, Icons)
- A11Y (Accessibility)
- I18N (Internationalization,子分類 Rtl)
如果系統(tǒng)分類不能滿足需求,也可以創(chuàng)建自定義的分類:
public class QMUICategory {
public static final Category UI_SPECIFICATION = Category.create("UI Specification", 105);
}
使用如下:
public static final Issue ISSUE_JAVA_VECTOR_DRAWABLE =
Issue.create("QMUIGetVectorDrawableWithWrongFunction",
"Should use the corresponding method to get vector drawable.",
"Using the normal method to get the vector drawable will cause a crash on Android versions below 4.0",
QMUICategory.UI_SPECIFICATION, 2, Severity.ERROR,
new Implementation(QMUIJavaVectorDrawableDetector.class, Scope.JAVA_FILE_SCOPE));
3.5 Registry
創(chuàng)建自定義 Lint 的最后一步是 “Lint-Registry”,如前面所述,build.gradle 中需要聲明 Regisry 類,打包成 jar:
jar {
manifest {
attributes('Lint-Registry': 'com.qmuiteam.qmui.lint.QMUIIssueRegistry')
}
}
而 registry 類中則是注冊(cè)創(chuàng)建好的 Issue,以 QMUIIssueRegistry 為例:
public final class QMUIIssueRegistry extends IssueRegistry {
@Override public List<Issue> getIssues() {
return Arrays.asList(
QMUIFWordDetector.ISSUE_F_WORD,
QMUIJavaVectorDrawableDetector.ISSUE_JAVA_VECTOR_DRAWABLE,
QMUIXmlVectorDrawableDetector.ISSUE_XML_VECTOR_DRAWABLE,
QMUIImageSizeDetector.ISSUE_IMAGE_SIZE,
QMUIImageScaleDetector.ISSUE_IMAGE_SCALE
);
}
}
QMUIIssueRegistry 繼承與 IssueRegistry,IssueRegistry 中注冊(cè)了 Android Lint 自帶的 Issue,而自定義的 Issue 則可以通過 getIssues 系列方法傳入。
到這一步,這個(gè)用于自定義 Lint 的 Java 項(xiàng)目編寫完畢了。
3.6 接入項(xiàng)目
Google 官方的方案是把 jar 文件放到 ~/.android/lint/,如果本地沒有 lint 目錄可以自行創(chuàng)建,這個(gè)使用方式較為簡(jiǎn)單,但也使得 Android Lint 作用于本地所有的項(xiàng)目,不大靈活。
在主項(xiàng)目中新建一個(gè) Module,打包為 aar,把 jar 文件放到該 aar 中,這樣各個(gè)項(xiàng)目可以以 aar 的方式自行引入自定義 Lint,比較靈活,項(xiàng)目之間不會(huì)造成干擾。
Module 的 build.gradle 內(nèi)容如下(以 QMUI Lint 為例):
apply plugin: 'com.android.library'
configurations {
lintChecks
}
dependencies {
lintChecks project(path: ':qmuilintrule', configuration: 'lintChecks')
}
task copyLintJar(type: Copy) {
from(configurations.lintChecks) {
rename { 'lint.jar' }
}
into 'build/intermediates/lint/'
}
project.afterEvaluate {
def compileLintTask = project.tasks.find { it.name == 'compileLint' }
compileLintTask.dependsOn(copyLintJar)
}
其中 qmuilintrule 是自定義 Lint 規(guī)則的 Module,這樣這個(gè)需要進(jìn)行 aar 打包的 Module 即可獲取到 jar 文件,并放到 build/intermediates/lint/ 這個(gè)路徑中。把 aar 發(fā)布到 Bintray 后,需要用到自定義 Lint 的地方只需要引入 aar 即可,例如:
compile 'com.qmuiteam:qmuilint:1.0.0'
另外需要注意,在編寫自定義規(guī)則的 Lint 代碼時(shí),編寫后重新構(gòu)建 gradle,新代碼也不一定生效,需要重啟 Android Studio 才能確保新代碼已經(jīng)生效。
Android 性能優(yōu)化:使用 Lint 優(yōu)化代碼、去除多余資源
Android Lint 實(shí)踐 —— 簡(jiǎn)介及常見問題分析

