之前對下面的問題,個人態度是,不報錯就是沒問題,報錯就用快捷鍵,根據Android Studio提示修復問題,歷來不去問個爲何?如今代碼潔癖症愈來愈嚴重的我,忍不住想看清什麼東西在搞鬼。html
認真看完本文,必定能夠學到最新的知識。就算看不下去,也要點個贊收藏,絕對不虧。本文並非吐槽Lint的很差,而是在學習Lint過程碰到問題,心態是奔潰的,以及解決每一個問題帶來的喜感。java
不知道你們有沒有注意項目中黃色代碼塊的提示,以下圖所示: node
Android Studio 提供一個名爲Lint的靜態代碼掃描工具,能夠發現並糾正代碼結構中的質量問題,而無需實際執行該應用,也沒必要編寫測試用例。 Lint 工具可檢查您的 Android 項目源文件是否包含潛在錯誤,以及在正確性、安全性、性能、易用性、便利性和國際化方面是否須要優化改進。android
也就是說,經過Lint工具,咱們能夠寫出更高質量的代碼和代碼潛在的問題,媽媽不再用擔憂個人同事用中文命名了。也能夠經過定製Lint相關配置,提升開發效率。git
因爲Android Studio內置了Lint工具,好像不須要咱們幹嗎。但是呀,我有強迫症,看着上面的黃色塊,超級不爽的。因此咱們得了解如何配置Lint,讓它爲咱們服務,而不是爲Google服務。github
本文開始的紅色錯誤能夠經過註解來消除(通常建議是根據提示進行修正,除非明白本身在作什麼),能夠在類或該代碼所在的方法添加@SuppressLint
。api
@SuppressLint(all)
。 對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
關鍵字。安全
tools:ignore="all" 複製代碼
咱們也能夠經過配置項目的Gradle文件來禁止檢查。bash
例如禁止Lint檢查項目AndroidManifest.xml文件的GoogleAppIndexingWarning問題。在項目對應組件工程的Gradle文件添加以下配置,這樣就不會有黃色提醒了。markdown
defaultConfig{ lintOptions { disable 'GoogleAppIndexingWarning' } } 複製代碼
那麼,能夠禁止lint工具檢查什麼問題?
在上文中經過註解和在xml使用屬性來禁止Lint工具檢查相關問題,其實已是對Lint的配置了。Lint將多個問題歸爲一個issue(規則),例以下圖右邊的的六大規則。
經過Lint工具的工做流程瞭解到,能夠在lint.xml文件配置一些信息。通常新建項目都是沒有lint.xml文件的,在項目的根目錄建立lint.xml文件。格式以下:
<?xml version="1.0" encoding="UTF-8"?> <lint> <!-- list of issues to configure --> </lint> 複製代碼
那麼有哪些Issues(規則)呢?
在Android主要有以下六大類:
其餘更多Issues,能夠通將命令行切換到../Android/sdk/tools/bin目錄下,而後輸入lint --list
。例如在Mac下: cd /Users/gitcode8/Library/Android/sdk/tools/bin
輸入./lint --list
結果以下:
例如官網提供的參考例子:
<?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> 複製代碼
學習Lint工具僅僅是爲了安撫個人強迫症?不,還不知道Lint真正用來幹嗎呢?
很差容易開發了個APP,準備開始上班摸魚了。還讓代碼自查?那就經過Lint來看看代碼質量如何吧。
爲何要自定義呢?已有規則不符合本身或團隊開發需求,或者以爲Lint存在一些缺陷。在網上大多數文章千篇一概,都是經過將Log打印來舉例,看着都好累哦。因爲沒有相關官方文檔和第三方教程(可能因爲lint的api更新太快,沒人願意作這種吃力不討好的工做),也這就只有這樣了。本文經過自定義命名規範規則來說解整個過程。
先學習相關api,能夠快速理解一些概念,能夠粗略看過,下結實踐再回來看。
Issue如上文所說,表示lint 工具檢查的一個規則,一個規則包含若干問題。常在Detector中建立。下文是建立一個Issue的例子。
private static final Issue ISSUE = Issue.create("NamingConventionWarning", "命名規範錯誤", "使用駝峯命名法,方法命名開頭小寫,類大寫字母開頭", Category.USABILITY, 5, Severity.WARNING, new Implementation(NamingConventionDetecor.class, EnumSet.of(Scope.JAVA_FILE))); 複製代碼
這樣就能很清楚的定義一個規則,上文只定義了檢查命名規範的規則。
用於註冊要檢查的Issue(規則),只有註冊了Issue,該Issue才能被使用。例如註冊上文的命名規範規則。
public class Register extends IssueRegistry { @NotNull @Override public List<Issue> getIssues() { return Arrays.asList(NamingConventionDetector.ISSUE); } } 複製代碼
查找指定的Issue,一個Issue對應一個Detector。自定義Lint 規則的過程也就是重寫Detector類相關方法的過程。具體看下小結實踐。
掃描並發現代碼中的Issue,Detector須要實現Scaner,能夠繼承一個到多個。
舊版本的JavaScanner、JavaPsiScanner隨着版本的更新已經被UastScanner替代了。
經過實現命名規範Issue來熟悉和運用上小節相關的api。自定義規則須要在Java工程中建立,這裏經過Android Studio來建立一個Java Library。
步驟:File->New->New Mudle->Java Library
這裏Library Name爲lib。
定義類NamingConventionDetector,並繼承自Detector。由於這裏是檢測Java文件類名和方法是否符合規則,因此實現Detector.UastScanner接口。
public class NamingConventionDetector
extends Detector
implements Detector.UastScanner {
}
複製代碼
在NamingConventionDetector類內定義上文的Issue:
public class NamingConventionDetector extends Detector implements Detector.UastScanner { public static final Issue ISSUE = Issue.create("NamingConventionWarning", "命名規範錯誤", "使用駝峯命名法,方法命名開頭小寫", Category.USABILITY, 5, Severity.WARNING, new Implementation(NamingConventionDetector.class, EnumSet.of(Scope.JAVA_FILE))); } 複製代碼
重寫Detector的createUastHandler方法,實現咱們本身的處理類。
public class NamingConventionDetector extends Detector implements Detector.UastScanner { //定義命名規範規則 public static final Issue ISSUE = Issue.create("NamingConventionWarning", "命名規範錯誤", "使用駝峯命名法,方法命名開頭小寫", 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); } //重寫該方法,建立本身的處理器 @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) { //獲取當前類名 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表示觸碰規則,lint提示該問題;false則不觸碰 return true; } return super.visitClass(node); } @Override public boolean visitMethod(@NotNull UMethod node) { //當前方法不是構造方法 if (!node.isConstructor()) { char beginChar = node.getName().charAt(0); int code = beginChar; //當前方法首字母是大寫字母,則報Issue if (65 < code && code < 90) { context.report(ISSUE, context.getLocation(node), "the method must start with lowercase:" + node.getName()); //返回true表示觸碰規則,lint提示該問題;false則不觸碰 return true; } } return super.visitMethod(node); } } } 複製代碼
上文NamingConventionDetector類,已是所有代碼,只檢查類名和方法名是否符合駝峯命名法,能夠根據具體需求,重寫抽象類AbstractUastVisitor的visitXXX方法。
若是處理特定的方法或者其餘,也可使用默認的處理器。重寫Scanner相關方法。例如:
@Override public List<String> getApplicableMethodNames() { return Arrays.asList("e","v"); } 複製代碼
表示e(),v()方法會被檢測到,並調用visitMethod()方法,實現本身的邏輯。
@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文件添加相關代碼:
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自定義規則就搞定了,接着是使用和肯定規則是否正確。
使用自定義Lint規則有兩種形式:jar包和AAR文件。
在Android Studio的Terminal輸入下面命令:
./gradlew lib:assemble
複製代碼
看到BUILD SUCCESSFUL
則表示生成jar包成功,能夠在下面路徑找到:
lib->build->libs
複製代碼
如圖:
~/.android/lint/
複製代碼
若是lint文件夾不存在,則建立。經過命令行輸入lint --list。滑到最後能夠看到配置的規則,如圖:
在同個工程新建一個Android Library,名爲lintLibrary,修改相關配置。
修改自定義lint規則的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" 複製代碼
Android Library主要用來輸出AAR文件,要注意到Android Studio新特性的變動(在這裏踩了大坑)。
dependencies { ...... lintPublish project(':lib') } 複製代碼
在Android Studio 3.4+,lintChecks project(':lib')
:lint檢查只在當前工程生效,也就是Android Library,並不會打包到AAR文件中。lintPublish project(':lib')
纔會將lint檢查包含AAR文件中。
此時跟輸出普通的AAR文件沒什麼區別,但爲了手把手教會第一個自定義Issue,我寫!
步驟:
菜單欄:View->Tool Windows->Gradle
複製代碼
此時Android Studio在右邊會打開以下窗口:
BUILD SUCCESSFUL
,則可在下面目錄找到AAR文件。
lintLibrary->build->outputs->aar
複製代碼
這一小節的步驟也能夠經過命令行執行。
有本地依賴或者上傳遠程倉庫,這裏只介紹本地依賴。將上小結生成的AAR文件拷貝在app的libs文件夾。並配置app組件的build.gradle
repositories { flatDir { dirs 'libs' } } dependencies { implementation (name:'lintlibrary-release', ext:'aar') } 複製代碼
到這裏,就能使用自定義的lint規則了,效果和上面使用jar包是一致的。若是不生效,重啓Android Studio看看。
這是由於在輸出AAR文件中,參考其餘人的文章。沒有將Java Library的依賴改成compileOnly
。並且Android Library中使用lintChecks
。
不知道爲何,Linkedin的參考文章沒有生效,多是Android Studio版本的問題。
另外使用lintChecks輸出AAR不生效,Android Studio 3.4+新特性變動,採用lintPublish(AGP 3.4+)。
花了好長好長的時間寫本文,差點就放棄了。由於本身Android Studio看不了lint的源碼,只能從網上找,網上又找不到最新的doc。過濾太多雷同文章,差點想哭,一些最新的文章也跟不上相關技術的更新。。。
可是一切都值得,由於能幫助到想學習Android Studio lint工具的同窗,一塊兒嚮往美好的生活。
點個贊行不
寫此文找到的一些具備參考意義的文章:
另外:本文沒有demo,demo的代碼已經貼在文章裏了。