前言
以前對下面的問題罗丰,我的態(tài)度是,不報錯就是沒問題再姑,報錯就用快捷鍵萌抵,根據(jù)Android Studio提示修復(fù)問題,從來不去問個為什么元镀?現(xiàn)在代碼潔癖癥越來越嚴重的我绍填,忍不住想看搞清什么東西在·搞·鬼·。
認真看完本文栖疑,一定可以學(xué)到最新的知識讨永。就算看不下去,也要點個贊收藏遇革,絕對不虧卿闹。
不知道大家有沒有注意項目中·黃·色·代碼塊的提示揭糕,如下圖所示:
或者紅色標(biāo)記的代碼(并沒有任何錯誤),如下圖所示:
上文黃色的提醒和紅色警告锻霎,都是來自Android Studio內(nèi)置的Lint工具檢查我們的代碼后而作出的動作著角。
通過配置Lint,也可以消除上面的提醒。例如旋恼,我開發(fā)系統(tǒng)APK,根本不需要考慮用戶是否授權(quán)吏口。
那么Lint是什么呢?
Lint
Android Studio 提供一個名為Lint的靜態(tài)代碼掃描工具蚌铜,可以發(fā)現(xiàn)并糾正代碼結(jié)構(gòu)中的質(zhì)量問題锨侯,而無需實際執(zhí)行該應(yīng)用嫩海,也不必編寫測試用例冬殃。
Lint 工具可檢查您的 Android 項目源文件是否包含潛在錯誤,以及在正確性叁怪、安全性审葬、性能、易用性奕谭、便利性和國際化方面是否需要優(yōu)化改進涣觉。
也就是說,通過Lint工具血柳,我們可以寫出更高質(zhì)量的代碼和代碼潛在的問題官册,媽媽再也不用擔(dān)心我的同事用中文命名了。也可以通過定制Lint相關(guān)配置难捌,提高開發(fā)效率膝宁。
Lint禁止檢查
由于Android Studio內(nèi)置了Lint工具,好像不需要我們干嘛根吁≡币可是呀,我有強迫癥击敌,看著上面的黃色塊介返,超級不爽的。所以我們得了解如何配置Lint沃斤,讓它為我們服務(wù)圣蝎,而不是為Google服務(wù)。
本文開始的紅色錯誤可以通過注解來消除(一般建議是根據(jù)提示進行修正衡瓶,除非明白自己在做什么)徘公,可以在類或該代碼所在的方法添加@SuppressLint
。
上圖中是禁止Lint檢查特定的問題檢查鞍陨,如果要禁止該Java文件所有的Lint問題步淹,可以在類前添加如下注解:
@SuppressLint(all)
从隆。對XMl文件的禁止,則可以采用如下形式:
- 在lint.xml聲明命名空間
namespace xmlns:tools="http://schemas.android.com/tools"
- 在布局中使用:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="UnusedResources" >
<TextView
android:text="@string/auto_update_prompt" />
</LinearLayout>
父容器聲明了ignore屬性缭裆,那么子視圖會繼承該屬性键闺。例如上文LinearLayout中聲明了禁止Lint檢查LinearLayout的UnusedResources問題,TextView自然也禁止檢查該問題澈驼。禁止檢查多個問題辛燥,問題之間用逗號隔開;禁止檢查所有問題則使用all
關(guān)鍵字缝其。
tools:ignore="all"
我們也可以通過配置項目的Gradle文件來禁止檢查挎塌。
例如禁止Lint檢查項目AndroidManifest.xml文件的GoogleAppIndexingWarning問題。在項目對應(yīng)組件工程的Gradle文件添加如下配置,這樣就不會有黃色提醒了内边。
defaultConfig{
lintOptions {
disable 'GoogleAppIndexingWarning'
}
}
那么榴都,可以禁止lint工具檢查什么問題?
配置Lint
在上文中通過注解和在xml使用屬性來禁止Lint工具檢查相關(guān)問題,其實已經(jīng)是對Lint的配置了漠其。Lint將多個問題歸為一個issue(規(guī)則)嘴高,例如下圖右邊的的六大規(guī)則。
上圖是Lint工具的工作流程和屎,下面了解相關(guān)概念拴驮。
App Source Files
源文件包含組成 Android 項目的文件,包括 Java 和 XML 文件柴信、圖標(biāo)和 ProGuard 配置文件等套啤。
lint.xml 文件
此配置文件可用于指定您希望排除的任何 Lint 檢查以及自定義問題嚴重級別。
lint Tool
我們可以通過Android Studio 對 Android 項目運行此靜態(tài)代碼掃描工具随常。也可以手動運行潜沦。Lint 工具檢查可能影響 Android 應(yīng)用質(zhì)量和性能的代碼結(jié)構(gòu)問題。
Lint 檢查結(jié)果
我們可以在控制臺(命令行運行)或 Android Studio 的 Inspection Results 窗口中查看 Lint 檢查結(jié)果线罕。
通過Lint工具的工作流程了解到止潮,可以在lint.xml文件配置一些信息。一般新建項目都是沒有l(wèi)int.xml文件的钞楼,在項目的根目錄創(chuàng)建lint.xml文件喇闸。格式如下:
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- list of issues to configure -->
</lint>
那么有哪些Issues(規(guī)則)呢?
在Android主要有如下六大類:
- Security 安全性询件。在AndroidManifest.xml中沒有配置相關(guān)權(quán)限等燃乍。
- Usability 易用性。重復(fù)圖標(biāo)宛琅;上文開始黃色警告也屬于該規(guī)則等刻蟹。
- Performance 性能。內(nèi)存泄漏嘿辟,xml結(jié)構(gòu)冗余等舆瘪。
- Correctness 正確性片效。超版本調(diào)用API,設(shè)置不正確的屬性值等英古。
- Accessibility 無障礙淀衣。單詞拼寫錯誤等。
- Internationalization國際化召调。字符串缺少翻譯等膨桥。
其他更多Issues,可以通將命令行切換到../Android/sdk/tools/bin目錄下,然后輸入lint --list
唠叛。例如在Mac下:
cd /Users/gitcode8/Library/Android/sdk/tools/bin
輸入./lint --list
結(jié)果如下:
例如官網(wǎng)提供的參考例子:
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- 忽略整個工程目錄下指定問題的檢查 -->
<issue id="IconMissingDensityFolder" severity="ignore" />
<!-- 忽略對指定文件指定問題的檢查 -->
<issue id="ObsoleteLayoutParam">
<ignore path="res/layout/activation.xml" />
<ignore path="res/layout-xlarge/activation.xml" />
</issue>
<!-- 更改檢查問題歸屬的嚴重性 -->
<issue id="HardcodedText" severity="error" />
</lint>
學(xué)習(xí)Lint工具僅僅是為了安撫我的強迫癥只嚣?不,還不知道Lint真正用來干嘛呢艺沼?
檢查項目質(zhì)量
不好容易開發(fā)了個APP,準(zhǔn)備開始上班摸魚了册舞。還讓代碼自查?那就通過Lint來看看代碼質(zhì)量如何吧澳厢。
1环础、Android Studio 檢查
- 通過Android Studio 的菜單欄Analyze選項下拉選擇第一個選項Inspect Code.
2囚似、在彈出框根據(jù)自己需要選擇lint工具的檢查范圍剩拢,這里選擇整個項目。檢查時間也是根據(jù)項目大小來定的饶唤。
3徐伐、等待一段時間后,會列出檢查結(jié)果募狂。從下圖看到办素,不僅會檢查Android存在的問題,也會檢查Java等其他問題祸穷。通過單擊問題性穿,可以從右邊提示框看到問題發(fā)生的地方和相關(guān)建議。
到這里雷滚,就開始對項目修修補補吧需曾。
自定義規(guī)則
為什么要自定義呢?已有規(guī)則不符合自己或團隊開發(fā)需求祈远,或者覺得Lint存在一些缺陷呆万。在網(wǎng)上大多數(shù)文章千篇一律,都是通過將Log打印來舉例车份,看著都好累哦谋减。由于沒有相關(guān)官方文檔和第三方教程(可能由于lint的api更新太快,沒人愿意做這種吃力不討好的工作)扫沼,也這就只有這樣了出爹。本文通過自定義命名規(guī)范規(guī)則來講解整個過程庄吼。
Lint中重點的API
先學(xué)習(xí)相關(guān)api,可以快速理解一些概念严就,可以粗略看過霸褒,下結(jié)實踐再回來看。
1盈蛮、Issue
Issue如上文所說废菱,表示lint 工具檢查的一個規(guī)則,一個規(guī)則包含若干問題抖誉。常在Detector中創(chuàng)建殊轴。下文是創(chuàng)建一個Issue的例子。
private static final Issue ISSUE = Issue.create("NamingConventionWarning",
"命名規(guī)范錯誤",
"使用駝峰命名法袒炉,方法命名開頭小寫旁理,類大寫字母開頭",
Category.USABILITY,
5,
Severity.WARNING,
new Implementation(NamingConventionDetecor.class,
EnumSet.of(Scope.JAVA_FILE)));
- 第一個參數(shù)id 唯一的id,簡要表面當(dāng)前提示的問題。
- 第二個參數(shù)briefDescription 簡單描述當(dāng)前問題
- 第三個參數(shù)explanation 詳細解釋當(dāng)前問題和修復(fù)建議
- 第四個參數(shù)category 問題類別我磁,例如上文講到的Security孽文、Usability等等。
- 第五個參數(shù)priority 優(yōu)先級夺艰,從1到10芋哭,10最重要
- 第六個參數(shù)Severity 嚴重程度:FATAL(奔潰), ERROR(錯誤), WARNING(警告),INFORMATIONAL(信息性),IGNORE(可忽略)
-
第七個參數(shù)Implementation Issue和哪個Detector綁定,以及聲明檢查的范圍郁副。Scope有如下選擇范圍:
RESOURCE_FILE(資源文件),BINARY_RESOURCE_FILE(二進制資源文件),RESOURCE_FOLDER(資源文件夾),ALL_RESOURCE_FILES(所有資源文件),JAVA_FILE(Java文件), ALL_JAVA_FILES(所有Java文件),CLASS_FILE(class文件), ALL_CLASS_FILES(所有class文件),MANIFEST(配置清單文件), PROGUARD_FILE(混淆文件),JAVA_LIBRARIES(Java庫), GRADLE_FILE(Gradle文件),PROPERTY_FILE(屬性文件),TEST_SOURCES(測試資源),OTHER(其他);
這樣就能很清楚的定義一個規(guī)則减牺,上文只定義了檢查命名規(guī)范的規(guī)則。
2存谎、IssueRegistry
用于注冊要檢查的Issue(規(guī)則)拔疚,只有注冊了Issue,該Issue才能被使用。例如注冊上文的命名規(guī)范規(guī)則既荚。
public class Register extends IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues() {
return Arrays.asList(NamingConventionDetector.ISSUE);
}
}
4稚失、Detector
查找指定的Issue,一個Issue對應(yīng)一個Detector恰聘。自定義Lint 規(guī)則的過程也就是重寫Detector類相關(guān)方法的過程句各。具體看下小結(jié)實踐。
5憨琳、Scanner
掃描并發(fā)現(xiàn)代碼中的Issue,Detector需要實現(xiàn)Scaner,可以繼承一個到多個诫钓。
- UastScanner 掃描Java文件和Kotlin文件
- ClassScanner 掃描Class文件
- XmlScanner 掃描Xml文件
- ResourceFolderScanner 掃描資源文件夾
- BinaryResourceScanner 掃描二進制資源文件
- OtherFileScanner 掃描其他文件
- GradleScanner 掃描Gradle腳本
舊版本的JavaScanner、JavaPsiScanner隨著版本的更新已經(jīng)被UastScanner替代了篙螟。
自定義Lint規(guī)則實踐
通過實現(xiàn)命名規(guī)范Issue來熟悉和運用上小節(jié)相關(guān)的api菌湃。自定義規(guī)則需要在Java工程中創(chuàng)建,這里通過Android Studio來創(chuàng)建一個Java Library遍略。
步驟:File->New->New Mudle->Java Library
這里L(fēng)ibrary Name為lib惧所。
定義類NamingConventionDetector,并繼承自Detector骤坐。因為這里是檢測Java文件類名和方法是否符合規(guī)則,所以實現(xiàn)Detector.UastScanner接口下愈。
public class NamingConventionDetector
extends Detector
implements Detector.UastScanner {
}
在NamingConventionDetector類內(nèi)定義上文的Issue:
public class NamingConventionDetector
extends Detector
implements Detector.UastScanner {
public static final Issue ISSUE = Issue.create("NamingConventionWarning",
"命名規(guī)范錯誤",
"使用駝峰命名法纽绍,方法命名開頭小寫",
Category.USABILITY,
5,
Severity.WARNING,
new Implementation(NamingConventionDetector.class,
EnumSet.of(Scope.JAVA_FILE)));
}
重寫Detector的createUastHandler方法,實現(xiàn)我們自己的處理類势似。
public class NamingConventionDetector extends Detector implements Detector.UastScanner {
//定義命名規(guī)范規(guī)則
public static final Issue ISSUE = Issue.create("NamingConventionWarning",
"命名規(guī)范錯誤",
"使用駝峰命名法拌夏,方法命名開頭小寫",
Category.USABILITY,
5,
Severity.WARNING,
new Implementation(NamingConventionDetector.class,
EnumSet.of(Scope.JAVA_FILE)));
//返回我們所有感興趣的類,即返回的類都被會檢查
@Nullable
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.<Class<? extends UElement>>singletonList(UClass.class);
}
//重寫該方法履因,創(chuàng)建自己的處理器
@Nullable
@Override
public UElementHandler createUastHandler(@NotNull final JavaContext context) {
return new UElementHandler() {
@Override
public void visitClass(@NotNull UClass node) {
node.accept(new NamingConventionVisitor(context, node));
}
};
}
//定義一個繼承自AbstractUastVisitor的訪問器障簿,用來處理感興趣的問題
public static class NamingConventionVisitor extends AbstractUastVisitor {
JavaContext context;
UClass uClass;
public NamingConventionVisitor(JavaContext context, UClass uClass) {
this.context = context;
this.uClass = uClass;
}
@Override
public boolean visitClass(@org.jetbrains.annotations.NotNull UClass node) {
//獲取當(dāng)前類名
char beginChar = node.getName().charAt(0);
int code = beginChar;
//如果類名不是大寫字母,則觸碰Issue栅迄,lint工具提示問題
if (97 < code && code < 122) {
context.report(ISSUE,context.getNameLocation(node),
"the name of class must start with uppercase:" + node.getName());
//返回true表示觸碰規(guī)則站故,lint提示該問題;false則不觸碰
return true;
}
return super.visitClass(node);
}
@Override
public boolean visitMethod(@NotNull UMethod node) {
//當(dāng)前方法不是構(gòu)造方法
if (!node.isConstructor()) {
char beginChar = node.getName().charAt(0);
int code = beginChar;
//當(dāng)前方法首字母是大寫字母毅舆,則報Issue
if (65 < code && code < 90) {
context.report(ISSUE, context.getLocation(node),
"the method must start with lowercase:" + node.getName());
//返回true表示觸碰規(guī)則西篓,lint提示該問題;false則不觸碰
return true;
}
}
return super.visitMethod(node);
}
}
}
上文NamingConventionDetector類憋活,已經(jīng)是全部代碼岂津,只檢查類名和方法名是否符合駝峰命名法,可以根據(jù)具體需求,重寫抽象類AbstractUastVisitor的visitXXX方法余掖。
如果處理特定的方法或者其他寸爆,也可以使用默認的處理器。重寫Scanner相關(guān)方法盐欺。例如:
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("e","v");
}
表示e(),v()方法會被檢測到,并調(diào)用visitMethod()方法仅醇,實現(xiàn)自己的邏輯冗美。
@Override
public void visitMethod JavaContext context, JavaElementVisitor visitor, PsiMethodCallExpression call, PsiMethod method) {
//todo something
super.visitMethod(context, visitor, call, method);
}
接下來就是注冊自定義的Issue:
public class Register extends IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues() {
return Arrays.asList(NamingConventionDetector.ISSUE);
}
}
在lib項目的build.gradle文件添加相關(guān)代碼:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.tools.lint:lint-api:26.4.2'
implementation 'com.android.tools.lint:lint-checks:26.4.2'
}
//添加如下代碼
jar {
manifest {
attributes 'Lint-Registry': 'com.gitcode.lib.Register'
}
}
sourceCompatibility = "7"
targetCompatibility = "7"
到這里就自定義Lint自定義規(guī)則就搞定了,接著是使用和確定規(guī)則是否正確析二。
使用自定Lint規(guī)則
使用自定義Lint規(guī)則有兩種形式:jar包和AAR文件粉洼。
jar形式使用
在Android Studio的Terminal輸入下面命令:
./gradlew lib:assemble
看到BUILD SUCCESSFUL
則表示生成jar包成功,可以在下面路徑找到:
lib->build->libs
如圖:
將lib.jar拷貝下面目錄:
~/.android/lint/
如果lint文件夾不存在叶摄,則創(chuàng)建属韧。通過命令行輸入lint --list「蛳牛滑到最后可以看到配置的規(guī)則宵喂,如圖:
重啟Android Studio,讓規(guī)則生效。
檢測到方法大寫会傲,不符合命名規(guī)范锅棕,報導(dǎo)該問題拙泽。
類名不符合規(guī)范:
從上文可以看到,放在目錄下的jar包對所有工程都是有效的裸燎。如果要針對單個工程顾瞻,那么就需要需要AAR形式了。
AAR形式
在同個工程新建一個Android Library德绿,名為lintLibrary荷荤,修改相關(guān)配置。
1移稳、修改Java工程的依賴
修改自定義lint規(guī)則的Java庫的build.gradle(這里是上文的Java lib庫)梅猿,注意到要將implementation改為compileOnly。
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//將implementation改為compileOnly秒裕,不然報錯
compileOnly 'com.android.tools.lint:lint-api:26.4.2'
compileOnly 'com.android.tools.lint:lint-checks:26.4.2'
}
jar {
manifest {
attributes 'Lint-Registry-v2': 'com.gitcode.lib.Register'
}
}
sourceCompatibility = "7"
targetCompatibility = "7"
2袱蚓、修改Android Library依賴
Android Library主要用來輸出AAR文件,要注意到Android Studio新特性的變更(在這里踩了大坑)几蜻。
dependencies {
......
lintPublish project(':lib')
}
在Android Studio 3.4+喇潘,lintChecks project(':lib')
:lint檢查只在當(dāng)前工程生效,也就是Android Library,并不會打包到AAR文件中梭稚。lintPublish project(':lib')
才會將lint檢查包含AAR文件中颖低。
3、輸出AAR文件
此時跟輸出普通的AAR文件沒什么區(qū)別弧烤,但為了手把手教會第一個自定義Issue忱屑,我寫!
步驟:
菜單欄:View->Tool Windows->Gradle
此時Android Studio在右邊會打開如下窗口:
根據(jù)上圖操作暇昂,雙擊assemble,稍等一會莺戒,在控制臺看
BUILD SUCCESSFUL
,則可在下面目錄找到AAR文件。
lintLibrary->build->outputs->aar
這一小節(jié)的步驟也可以通過命令行執(zhí)行急波。
4从铲、使用AAR文件
有本地依賴或者上傳遠程倉庫,這里只介紹本地依賴澄暮。將上小結(jié)生成的AAR文件拷貝在app的libs文件夾名段。并配置app組件的build.gradle
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation (name:'lintlibrary-release', ext:'aar')
}
到這里,就能使用自定義的lint規(guī)則了泣懊,效果和上面使用jar包是一致的伸辟。如果不生效,重啟Android Studio看看馍刮。
采坑記
1信夫、Found more than one jar in the 'lintChecks' configuration. Only one file is supported
這是因為在輸出AAR文件中,參考其他人的文章。沒有將Java Library的依賴改為compileOnly
忙迁。而且Android Library中使用lintChecks
脐彩。
2、輸出AAR文件沒有生效
不知道為什么姊扔,Linkedin的參考文章沒有生效,可能是Android Studio版本的問題惠奸。
另外使用lintChecks輸出AAR不生效,Android Studio 3.4+新特性變更恰梢,采用lintPublish佛南。
總結(jié)
花了好長好長的時間寫本文,差點就放棄了嵌言。因為自己Android Studio看不了lint的源碼嗅回,只能從網(wǎng)上找,網(wǎng)上又找不到最新的doc摧茴。過濾太多雷同文章绵载,差點想哭,一些最新的文章也跟不上相關(guān)技術(shù)的更新苛白。娃豹。。
但是一切都值得购裙,因為能幫助到想學(xué)習(xí)Android Studio lint工具的同學(xué)懂版,一起向往美好的生活。
點個贊行不
寫此文找到的一些具有參考意義的文章:
另外:本文沒有demo躏率,demo的代碼已經(jīng)貼在文章里了躯畴。