一、Lint是什么
Lint是ADT 16引入的靜態代碼檢測工具(位於sdk/tools/bin/lint ),可以對Android工程的源文件進行掃描,找出在正確性、安全性、性能、易用性、無障礙性以及國際化等方面可能存在的bug和可優化提升的地方。
Lint默認包括幾百個檢測項,主要分為以下六類:
Valid issue categories: Correctness //編碼錯誤或不完美,比如"WrongThread"表示可能在非UI線程調用widgets方法 Security //不安全的編碼,比如“SetJavaScriptEnabled”表示WebView支持JavaScript Performance //對性能有影響的編碼,比如"Wakelock"表示未正確釋放屏幕鎖,導致功耗增加 Usability //有更好的可用選項,比如"SmallSp"表示使用了小於12sp的尺寸 Accessibility //輔助選項,比如"ContentDescription"表示Image缺少ContentDescription屬性 Internationalization //國際化,比如"RtlEnabled"表示使用了RTL屬性但沒有在manifext文件中設置android:supportsRtl為true
可以通過lint --list命令查看所有支持的檢測項:
$lint --list
也可以通過lint --show <id> 命令查看某一檢測項的詳細介紹:
$ lint --show RtlEnabled RtlEnabled ---------- Summary: Using RTL attributes without enabling RTL support Priority: 3 / 10 Severity: Warning Category: Internationalization:Bidirectional Text To enable right-to-left support, when running on API 17 and higher, you must set the android:supportsRtl attribute in the manifest <application> element. If you have started adding RTL attributes, but have not yet finished the migration, you can set the attribute to false to satisfy this lint check.
除此之外,google還提供了自定義Lint檢查規則的方法。
二、如何啟動Lint檢測
使用Android Studio時,無論何時構建應用,都會自動運行配置的Lint和IDE檢查。但更多的時候,我們需要主動運行這些檢測,並獲取檢測報告。
我們有兩種方式啟動Lint檢測:
(1)命令行檢測
我們可以在項目根目錄下通過如下命令啟動Lint檢測:
./gradlew lint
檢測完畢后可以看到類似如下結果:
> Task :lockscreen:lint Ran lint on variant debug: 335 issues found Ran lint on variant release: 335 issues found Wrote HTML report to file:///home/kevin/kevin/project/sysq/SystemUI/lockscreen/build/reports/lint-results.html Wrote XML report to file:///home/kevin/kevin/project/sysq/SystemUI/lockscreen/build/reports/lint-results.xml
檢測報告被同時保存在lint-results.html和lint-results.xml兩個文件中,打開后可以看到每個問題的詳細描述和定位,以及修改建議:
<issue id="WrongConstant" severity="Error" message="Must be one of: View.LAYER_TYPE_NONE, View.LAYER_TYPE_SOFTWARE, View.LAYER_TYPE_HARDWARE" category="Correctness" priority="6" summary="Incorrect constant" explanation="Ensures that when parameter in a method only allows a specific set of constants, calls obey those rules." errorLine1=" ViewCompat.setLayerType(getChildAt(i), layerType, null);" errorLine2=" ~~~~~~~~~"> <location file="/home/kevin/kevin/project/sysq/SystemUI/lockscreen/src/main/java/com/lenovo/artlock/LeatherViewPager.java" line="1793" column="52"/> </issue>
注:lint在jdk 9 和 jdk 11會報錯,替換其他版本可以解決;另外,如果是window,則直接使用
gradlew lint
(2)手動運行檢測
以Android Studio為例:
1.打開一個工程,選擇要分析的項目、文件夾或文件
2.在菜單欄中依次選擇Analyze > Inspect Code
3.查看檢測范圍和配置文件
4.點擊ok以運行檢查
5.查看檢測結果
注:通過Custom scop可以自定義檢測范文,Inspection profile可以指定或修改內置的lint配置文件,下面會詳細介紹。
三、Lint配置文件
如果希望忽略一些檢測項或調整其嚴重等級,可以通過修改配置文件lint.xml的方式。
Lint的工作流程如下:
修改配置文件同樣有兩種方式:
(1)手動創建
如果是手動創建lint.xml文件,則將其放在Android項目的根目錄下,其格式如下
<?xml version="1.0" encoding="UTF-8"?> <lint> <!-- Disable the given check in this project --> <issue id="IconMissingDensityFolder" severity="ignore" /> <!-- Ignore the ObsoleteLayoutParam issue in the specified files --> <issue id="ObsoleteLayoutParam"> <ignore path="res/layout/activation.xml" /> <ignore path="res/layout-xlarge/activation.xml" /> </issue> <!-- Ignore the UselessLeaf issue in the specified file --> <issue id="UselessLeaf"> <ignore path="res/layout/main.xml" /> </issue> <!-- Change the severity of hardcoded strings to "error" --> <issue id="HardcodedText" severity="error" /> </lint>
在lint.xml定義的id會覆蓋默認配置,未涉及的id則保持默認配置。
(2)Android Studio內置Lint配置文件
Android Studio內置了Lint和一些其他檢查配置文件,通過第二部分中提到的Inspection profile選項可以修改。
其中打√代表打開,Severity 代表嚴重等級,可以根據自己的需要修改。
Gradle中的lintOptions
除了上面修改單個issue的開關和嚴重等級,Gradle也提供了針對Lint運行方式的配置,可以在build.gradle中修改。
android { // 移除lint檢查的error,可以避免由於編譯條件太過嚴格而編譯不過的問題 lintOptions { // 如果為 true,則當lint發現錯誤時停止 gradle構建 abortOnError false // 如果為 true,則只報告錯誤 ignoreWarnings true // 不檢查給定的問題id InvalidPackage: Package not included in Android disable 'InvalidPackage' // 不檢查給定的問題id 資源類型錯誤 disable "ResourceType" // 忽略因MissingTranslation導致Build Failed錯誤 "app_name" disable 'MissingTranslation' // 檢查給定的問題 id enable 'RtlHardcoded','RtlCompat', 'RtlEnabled' // * 僅 * 檢查給定的問題 id check 'NewApi', 'InlinedApi' // 配置寫入輸出結果的位置;它可以是一個文件或 “stdout”(標准輸出) textOutput 'stdout' // 如果為真,會生成一個XML報告,以給Jenkins之類的使用 xmlReport false // 用於寫入報告的文件(如果不指定,默認為lint-results.xml) xmlOutput file("lint-report.xml") // 如果為真,會生成一個HTML報告(包括問題的解釋,存在此問題的源碼,等等) htmlReport true // 寫入報告的路徑,它是可選的(默認為構建目錄下的 lint-results.html ) htmlOutput file("lint-report.html") // 設置為 true, 將使所有release 構建都以issus的嚴重性級別為fatal //(severity=false)的設置來運行lint // 並且,如果發現了致命(fatal)的問題,將會中止構建 //(由上面提到的 abortOnError 控制) checkReleaseBuilds true // 設置給定問題的嚴重級別(severity)為fatal (這意味着他們將會 // 在release構建的期間檢查 (即使 lint 要檢查的問題沒有包含在代碼中) fatal 'NewApi', 'InlineApi' // 設置給定問題的嚴重級別為error error 'Wakelock', 'TextViewEdits' // 設置給定問題的嚴重級別為warning warning 'ResourceAsColor' // 設置給定問題的嚴重級別(severity)為ignore (和不檢查這個問題一樣) ignore 'TypographyQuotes' // 如果為 true,則檢查所有的問題,包括默認不檢查問題 checkAllWarnings true // 重置 lint 配置(使用默認的嚴重性等設置)。 lintConfig file("default-lint.xml") // 設置為 true,則當有錯誤時會顯示文件的全路徑或絕對路徑 (默認情況下為true) absolutePaths true } }
注:如果只想對某以特定問題進行檢查,可以使用check命令,check命令會覆蓋disable和enable命令;同時abortOnError設置為false是必要的,這樣可以防止lint檢查提前結束。
四、常見問題處理
首先我們需要知道,規則是死的,代碼是活的,並非所有的Lint警告都需要通過修改代碼來解決,有些警告可以被當成善意的提醒,我們可以通過一些標注來忽略它。
如果是java代碼,可以通過注解@SuppressLint("警告名稱"),如
@SuppressLint(“HandlerLeak”) @SuppressLint(“all”) //如果不清楚警告名稱,可以直接忽略all
如果是xml文件,可以通過屬性tools:ignore="警告名稱",如
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="TextFields,HardcodedText,UselessParent"> <!--如果不清楚警告名稱,可以直接忽略all --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="all">
下面列舉一些常見的警告,以及推薦的處理方式:
(1)[Accessibility] Missing contentDescription attribute on image
原因:為了照顧一些視力不好的小伙伴,我們在使用Image相關組件(如ImageView或ImageButton)時,需要添加contentDescription屬性,以便通過聲音等途徑告知圖片中的內容。
方案:添加contentDescription屬性
(2)To get local formatting use getDateInstance(), getDateTimeInstance(), or getTimeInstance(), or use new SimpleDateFormat(String template, Locale locale) with for example Locale.US for ASCII dates.
(3)Consider using apply() instead; commit writes its data to persistent storage immediately, whereas apply will handle it in the background
(4)This <FrameLayout> can be replaced with a <merge> tag
(5)This Handler class should be static or leaks might occur (anonymous android.os.Handler)
(6)Use new SparseIntArray(...) instead for better performance
(7)Use a layout_height of 0dp instead of fill_parent for better performance
(8)This Cursor should be freed up after use with #close()
(9)This tag and its children can be replaced by one <TextView/> and a compound drawable
(10)android:singleLine is deprecated: Use maxLines="1" instead
(11)Must be one of: Context.POWER_SERVICE, Context.WINDOW_SERVICE, Context.LAYOUT_INFLATER_SERVICE, Context.ACCOUNT_SERVICE, Context.ACTIVITY_SERVICE
.getSystemService("window");
方案:改為常量
.getSystemService(Context.WINDOW_SERVICE);
(12)@id/message_name can overlap @id/cancel_button if @id/message_name grows due to localized text expansion
android:maxEms="6"
(13)the resource 'R.color.aqua' appears to be unused
原因:無用資源
方案:刪除
(14)Avoid object allocations during draw/layout operations (preallocate and reuse instead)
原因:在draw和layout和onMeasure方法中最好不要創建對象,因為這些方法在一次顯示過程中會根據父布局的需要不止一次的調用。
方案:將其new方法提取出來。
(15)Avoid using sizes smaller than 12sp: 8sp
原因:避免使用尺寸小於12 sp:8 sp,其實就是想告訴你小於12sp真的太小啦,系統建議你改成12sp,因為他覺得12sp已經很小了,不建議比12sp還小
方案:不要小於12sp
(16)Field can be converted to a local variable
原因:字段可以轉換為一個局部變量
方案:轉換為一個局部變量