# 自定義Lint
在實際開發中,我們會根據需要,制定一些編碼規范、封裝公共庫、編碼最佳實踐。在持續迭代的過程中會面對以下問題:
* 團隊有了新加入的開發者,我們需要一種低成本方式去幫助他,從原來的編碼習慣中迅速調整過來,了解、使用我們的命名慣例、公共庫、最佳實踐。
* 重構老代碼時,我們也需要一種低成本的方式將老代碼中與我們現有規范有出入的地方,識別出來進行重構,盡可能快的統一規范。
基于以上考慮,我們開始靜態代碼檢測方面的調研工作。Google在這方面提供了Lint工具,跟著谷爹走,無疑是一條捷徑。
> 相關代碼托管在[Git倉庫](https://git.youxinpai.com/AndroidSdk/LintX)
## 一、Lint SDK
### 1.1、Lint相關api
Android SDK中涉及Lint的主要有下面幾個包,均包含在Android Gradle插件com.android.tools.build:gradle中。
* **com.android.tools.lint:lint-api**<br>
這個包提供了Lint的API,包括Context、Project、Detector、Issue、IssueRegistry等。
* **com.android.tools.lint:lint-checks**<br>
這個包實現了Android原生Lint規則。在25.3.3版本中,BuiltinIssueRegistry中共包含兩百多條Lint規則。
* **com.android.tools.lint:lint**<br>
這個包用于運行Lint檢查,提供:
* **com.android.tools.lint.XxxReporter**<br>
檢查結果報告,包括純文本、XML、HTML格式等
* **com.android.tools.lint.LintCliClient**<br>
用于在命令行中執行Lint
* **com.android.tools.lint.Main**<br>
這個類是命令行版本Lint的Java入口(Command line driver),主要是解析參數、輸出結果
* **com.android.tools.build:gradle-core**<br>
這個包提供Gradle插件核心功能,其中與Lint相關的主要有:
* **com.android.build.gradle.internal.LintGradleProject**<br>
繼承自lint-api中的Project類。Gradle執行Lint檢查時使用的Project對象,可獲取Manifest、依賴等信息。其中又包含了AppGradleProject和LibraryProject兩個內部類。
* **com.android.build.gradle.internal.LintGradleClient**<br>
用于在Gradle中執行Lint,繼承自LintCliClient
* **com.android.build.gradle.tasks.Lint**<br>
Gradle中Lint任務的實現
> 如果對lint工具源碼實現有興趣的同學,可以查看這三篇文章:[第一篇](https://hujiaweibujidao.github.io/blog/2016/12/01/builtin-lint-detectors-1/)、[第二篇](https://hujiaweibujidao.github.io/blog/2016/11/18/lint-tool-analysis-2/)、[第三篇](https://hujiaweibujidao.github.io/blog/2016/11/19/lint-tool-analysis-3/)。
### 1.2、Lint命令行實現
Lint可執行文件位于`<android-home>/tools/lint`,是一個Shell腳本,配置相關參數并執行Java調用`com.android.tools.lint.Main`進行檢查。
### 1.3、IDEA中的實現
在Android Studio或裝有Android插件的IDEA環境下,Inspections中的Lint檢查是通過Android插件實現的,代碼實現主要在`org.jetbrains.android.inspections.lint`包中。
[IDEA Android插件中Lint部分的實現](https://github.com/JetBrains/android/blob/master/android/src/org/jetbrains/android/inspections/lint)
### 1、4、Lint兼容性
自從ADT 16第一次引入Android Lint(以下簡稱:Lint)以來,Lint便成為Android平臺上最重要的靜態代碼掃描工具。與早期基于XPath的靜態掃描工具不同,Lint基于AST(Abstract Syntax Tree)進行分析,可以用來定制很復雜的掃描規則。
Lint從第一個版本就選擇了**lombok-ast**作為自己的AST Parser,并且用了很久。但是Java語言本身在不斷更新,Android也在不斷迭代出新,lombok-ast慢慢跟不上發展,所以Lint在25.2.0版增加了IntelliJ的**PSI**(Program Structure Interface)作為新的AST Parser。但是PSI于IntelliJ、于Lint也只是個過渡性方案,事實上IntelliJ早已開始了新一代AST Parser,**UAST**(Unified AST)的開發,而Lint在25.4.0版后已經將PSI更新為UAST。
在Lint兼容性方面(這里只說代碼文件),25.4.0之前版本中,Detector在提供`JavaPsiScanner`的同時保留了舊版的`JavaScanner`接口。但在25.4.0后的版本中完全移除了JavaScanner、JavaPsiScanner,推薦使用新的UastScanner。
UAST是JetBrains在IDEA新版本中用于替換PSI的API。UAST更加語言無關,除了支持Java,還可以支持Kotlin。
因此,在自定義Lint規則時,需要考慮Scanner的接口兼容性
* JavaPsiScanner<br>
* `com.android.tools.build:gradle`:2.2+到2.3.3版本
* `com.android.tools.lint:lint-api`:25.3.0及以下版本
* uastscanner<br>
* `com.android.tools.build:gradle`3.0.0及以上
* `com.android.tools.lint:lint-api`25.4.0及以上版本
## 二、自定義Lint
在開始自定義lint前,先對相關API進行簡單介紹:
自定義Lint開發需要調用Lint提供的API,最主要的幾個API如下:
* **Issue**<br>
表示一個Lint規則。例如調用Toast.makeText()方法后,沒有調用Toast.show()方法將其顯示。
* **Detector**<br>
用于檢測并報告代碼中的Issue。每個Issue包含一個Detector。
* **Scope**<br>
聲明Detector要掃描的代碼范圍,例如Java源文件、XML資源文件、Gradle文件等。每個Issue可包含多個Scope。
* **Scanner**<br>
用于掃描并發現代碼中的Issue。每個Detector可以實現一到多個Scanner。自定義Lint開發過程中最主要的工作就是實現Scanner。
* **IssueRegistry**<br>
用于注冊要檢查的Issue列表。自定義Lint需要生成一個jar文件,其Manifest指向IssueRegistry類。
以上主要幾個API出Scanner,在實現自定義Lint Rules都是相對固定的寫法,相對復雜的則是Scanner重載方法的實現上。
下面通過一個簡單的自定義示例來進行說明:
```java
/**
* Log規范Detector
* <p>
* 使用 內部日志庫Logx 替代 常規Log\println
* <p>
* 實際效果:
* ban:android.util.Log、System.out.println
* pick:com.xin.support.Logx
* <p>
* 檢測代碼中存在 使用android.util.Log、System.out.println 來輸出日志時 ,提醒使用內部庫Logx
*/
public class LogDetector extends Detector implements Detector.JavaPsiScanner {
private static final Class<? extends Detector> DETECTOR_CLASS = LogDetector.class;
private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;
private static final String ISSUE_ID = "LintX__LogUseError";
private static final String ISSUE_DESCRIPTION = "LintX__應該使用公共組件庫 LogX來輸出日志信息{com.xin.support.LogX}";
private static final String ISSUE_EXPLANATION = "LintX__為了能夠更好的控制Log打印的開關,你不能直接使用{android.util.Log}直接打印日志,應該使用公共組件庫LogX{com.xin.support.LogX}";
private static final Category ISSUE_CATEGORY = LintXCategory.BASELIBS;
private static final int ISSUE_PRIORITY = 9;
private static final Severity ISSUE_SEVERITY = Severity.WARNING;
private static final String CHECK_PACKAGE = "android.util.Log";
//固定區域
private static final Implementation IMPLEMENTATION = new Implementation(
DETECTOR_CLASS,
DETECTOR_SCOPE
);
public static final Issue ISSUE = Issue.create(
ISSUE_ID,
ISSUE_DESCRIPTION,
ISSUE_EXPLANATION,
ISSUE_CATEGORY,
ISSUE_PRIORITY,
ISSUE_SEVERITY,
IMPLEMENTATION
);
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e");
}
public void visitMethod(JavaContext context, JavaElementVisitor visitor, PsiMethodCallExpression node, PsiMethod method) {
JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isMemberInClass(method, CHECK_PACKAGE)) {
context.report(ISSUE, node, context.getLocation(node), ISSUE_DESCRIPTION);
}
}
}
```
### 2.1、創建工程
```groovy
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.tools.lint:lint-api:24.5.0'
compile 'com.android.tools.lint:lint-checks:24.5.0'
}
```
### 2.2、創建ISSUE
Lint提供了一個`create()`的靜態工程方法來創建Issue。
```java
public static Issue create(
@NonNull String id,
@NonNull String briefDescription,
@NonNull String explanation,
@NonNull Category category,
int priority,
@NonNull Severity severity,
@NonNull Implementation implementation)
```
需要以下幾個參數:
* **ID**<br>
唯一值,應該能簡短描述當前問題。利用Java注解或者XML屬性進行屏蔽時,使用的就是這個id
* **briefDescription**<br>
簡短的總結,通常5-6個字符,描述問題而不是修復措施
* **explanation**<br>
完整的問題解釋和修復建議。
* **category**<br>
問題類別。除了lint官方自帶的幾個類別,還可以進行自定義。
* **priority**<br>
優先級。1-10的數字,10為最重要/最嚴重
* **severity**<br>
嚴重級別:Fatal, Error, Warning, Informational, Ignore。
* **Implementation**<br>
為Issue和Detector提供映射關系,Detector就是當前Detector。聲明掃描檢測的范圍Scope,Scope用來描述Detector需要分析時需要考慮的文件集,包括:Resource文件或目錄、Java文件、Class文件。
以上幾項都很簡單,除了自定義category和Scope外,沒有太多需要額外說明的地方。
##### Category
系統現在已有的類別如下:
* Lint
* Correctness (incl. Messages)
* Security
* Performance
* Usability (incl. Icons, Typography)
* Accessibility
* Internationalization
* Bi-directional text
在實際開發中,為了讓我們檢測出的問題跟其他問題區分開來,讓開發者更有針對性的篩選處理問題,我們都會進行自定義Category

lint同樣提供了一個靜態工廠方法用來自定義Category:
```java
public class LintXCategory {
public static final Category STANDARD = Category.create("編碼規范", 107);
public static final Category BASELIBS = Category.create("公共庫推廣", 105);
}
```
##### Scope
Scope主要用來劃定Lint 目標檢查對象的,目前提供以下枚舉值:
* **ALL_CLASS_FILES**<br>
將所有Java類文件一起 檢測
* **ALL_JAVA_FILES**<br>
將所有Java源文件一起檢測
* **ALL_RESOURCE_FILES**<br>
檢測所有資源文件
* **BINARY_RESOURCE_FILE**<br>
只檢測單個二進制(通常是位圖)資源文件
* **CLASS_FILE**<br>
只檢測一個Java類文件
* **GRADLE_FILE**<br>
檢測Gradle構建文件
* **JAVA_FILE**<br>
只檢測單個Java源文件
* **JAVA_LIBRARIES**<br>
檢測該項目的庫中的classes文件
* **MANIFEST**<br>
檢測清單文件
* **PROGUARD_FILE**<br>
檢測Proguard配置文件
* **PROPERTY_FILE**<br>
檢測Java屬性文件
* **RESOURCE_FILE**<br>
只檢測單個XML資源文件
* **RESOURCE_FOLDER**<br>
檢測資源文件夾(還包括資產文件夾
* **TEST_SOURCES**<br>
檢測test源代碼
* **OTHER**<br>
其他文件
根據自定義的需求,選擇以上枚舉值即可。
### 2.3、重載Scanner相關方法
自定義很重要的一步是繼承Detector,并實現XXXScanner的相關方法。
自定義Detector可以實現一個或多個Scanner接口,選擇實現哪種接口取決于你想要的掃描范圍。目前提供了以下接口:
* Detector.XmlScanner
* Detector.JavaScanner
* Detector.ClassScanner
* Detector.BinaryResourceScanner
* Detector.ResourceFolderScanner
* Detector.GradleScanner
* Detector.OtherFileScanner
一般來說,Scanner的方法都是成對出現:
* 一個決定什么樣的類型能被檢測到,返回值通常都是一個集合
* 另一個方法接收上一個方法的返回,并進行具體的邏輯判斷就行。如果符合Issue預期條件,調用Context的report方法報告問題即可。
用于報告問題的report方法相對簡單,這里先說明下。
```java
context.report(ISSUE, node, context.getLocation(node), ISSUE_DESCRIPTION);
```
第一個參數就是上面提到Issue;
第二個參數是單點的節點;
第三個參數Location會返回當前的位置信息,便于在報告中顯示定位;
最后的字符串用來為警告添加解釋,通常使用Issue中的簡述即可;

這里還需要說明report會自動處理被suppress(`suppressLint`)/ignore(`tools:ignore`)的警告。所以發現問題直接調用`report`就可以,不用擔心其他問題。
難點來了,Scanner以及Psi AST相關文檔信息和具體實現都不多,這里先簡單提一下主要方法,后續會有單獨文章進行說明。
| Psi | Psi成對方法 | 釋義 |
| :-- | :-- | :-- |
| getApplicablePsiTypes | createPsiVisitor(JavaContext context) | 此方法返回需要檢查的AST節點的類型,類型匹配的UElement將會被createUastHandler(createJavaVisitor)創建的UElementHandler(Visitor)檢查 |
| getApplicableMethodNames | visitMethod(JavaContext,JavaElementVisitor, PsiMethodCallExpression, PsiMethod) | 返回你所需要檢查的方法名稱列表,或者返回null,相匹配的方法將通過visitMethod方法被檢查 |
| getApplicableConstructorTypes | visitConstructor(JavaContext, JavaElementVisitor,PsiNewExpression,PsiMethod) | 返回需要檢查的構造函數類型列表,類型匹配的方法將通過visitConstructor被檢查 |
| getApplicableReferenceNames | visitReference(JavaContext,JavaElementVisitor,PsiJavaCodeReferenceElement,PsiElement) | 返回需要檢查的引用路徑名,匹配的引用將通過visitReference被檢查 |
| applicableSuperClasses | checkClass(JavaContext, PsiClass) | 返回需要檢查的父類名列表,此處需要類的全路徑名,visitClass會檢查applicableSuperClasses返回的類 |
順便貼一下Uast的相關重載方法,便于后續的升級重構,命名很相似。
| Uast | Uast成對方法 | 釋義 |
| :-- | :-- | :-- |
| getApplicableUastTypes() | createUastHandler(JavaContext) | 同Psi |
| getApplicableMethodNames() |visitMethod(JavaContext, UCallExpression, PsiMethod) | 同Psi|
| getApplicableConstructorTypes() | visitConstructor(JavaContext, UCallExpression, PsiMethod) | 同Psi |
| getApplicableReferenceNames() | visitReference(JavaContext, UReferenceExpression, PsiElement) | 同Psi |
| applicableSuperClasses() | visitClass(JavaContext, UClass) | 同Psi |
更多關于Psi、Uast的用法可以[看這里](https://github.com/ouyangpeng/android-lint-checks-studio3.0)
### 2.4、注冊ISSUE
將Issue注冊只需要繼承IssueRegistry,實現getIssues方法即可。
示例如下:
```java
/**
* IssueRegistry 自定義實現類
* 在getIssues()返回 需要自定義檢測的類
*/
public class LintXIssueRegistry extends IssueRegistry {
private final static String VERSION_LINTX = "0.0.12";
static {
//用我們的修改版 替換一些 自帶的lint檢查
List<Issue> systemIssues = new BuiltinIssueRegistry().getIssues();
List<Issue> systemIssuesNew = new ArrayList<>(systemIssues.size());
systemIssuesNew.addAll(systemIssues);
//替換ResourcePrefixDetector (僅改動Category,便于分類)
systemIssuesNew.remove(ResourcePrefixDetector.ISSUE);
systemIssuesNew.add(ResourcePrefixProDetector.ISSUE);
try {
Field field = BuiltinIssueRegistry.class.getDeclaredField("sIssues");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, systemIssuesNew);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public synchronized List<Issue> getIssues() {
System.out.println("==== lintx start-" + VERSION_LINTX + " ====");
return Arrays.asList(
LogDetector.ISSUE
, ToastUtilsDetector.ISSUE
, ResourcePrefixProDetector.ISSUE
);
}
}
```
并且設置將jar包的manifest制定為全類名即可。
這里附上Rules jar包的全部build.gradle代碼
```groovy
apply plugin: 'java'
def lint_version = "25.3.0"
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.tools.lint:lint-api:' + lint_version
compile 'com.android.tools.lint:lint-checks:' + lint_version
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:3.0.0'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'com.android.tools.lint:lint:' + lint_version
testCompile 'com.android.tools.lint:lint-tests:' + lint_version
testCompile 'com.android.tools:testutils:' + lint_version
}
jar {
manifest {
attributes("Lint-Registry": "com.xin.support.lintx.rules.LintXIssueRegistry")
}
}
defaultTasks 'assemble'
/*
* rules for providing "lint.jar"
*/
configurations {
lintJarOutput
}
dependencies {
lintJarOutput files(jar)
}
```
好啦,通過以上的工作,現在我們就能生成一個包含簡單自定義規則的lint.jar
## 三、AAR包裝
上一節中自定義lint規則會生成一個**lint.jar**,Google提供的使用方式**將jar拷貝到~/.android/lint中**。
以上這種方式,推廣實現的時候比較麻煩,需要開發者手動進行配置。還有一個缺點:
針對所有工程,會影響同一臺機器其他工程的Lint檢查。即便觸發工程時拷貝過去,執行完刪除,但其他進程或線程使用./gradlew lint仍可能會受到影響。
翻閱美團等團隊的lint方案時,發現都參考借鑒了**LinkedIn**提供的思路:**將lint.jar放入一個aar中,需要檢測的工程依賴該aar。
想要看LinkedIn的同學可以看博文:[Writing Custom Lint Checks with Gradle](https://engineering.linkedin.com/android/writing-custom-lint-checks-gradle)
下面我們簡單說說 如何將lint.jar包裝成aar。
整個module僅僅將上面生成的jar文件 包裝成一個aar即可,整個module只需要一個build.gradle。
```groovy
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
buildToolsVersion "27.0.2"
defaultConfig {
minSdkVersion 9
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
/*
* 定義方法lintJarImport,引入上一步生成的LintXRules.jar
*
* rules for including "lint.jar" in aar
*/
configurations {
lintJarImport
}
// 鏈接到lintJar中的lintJarOutput方法,調用jar方法,并獲得jar包
dependencies {
//其引用了模塊 “:LintXRules”的 Gradle configuration “lintJarOutput”。
lintJarImport project(path: ":LintXRules", configuration: "lintJarOutput")
}
//創建一個copyLintJar的task:將得到的JAR包復制到目錄build/intermediates/lint/下,并且重命名為 lint.jar
task copyLintJar(type: Copy) {
from (configurations.lintJarImport) {
rename {
String fileName ->
'lint.jar'
}
}
into 'build/intermediates/lint/'
}
// 當項目build到compileLint這一步時執行copyLintJar方法
project.afterEvaluate {
def compileLintTask = project.tasks.find { it.name == 'compileLint' }
//對內置的Gradle task “compileLint”做了修改,讓其依賴于我們定義的一個task “copyLintJar”。
compileLintTask.dependsOn(copyLintJar)
}
```
這里說明下該mudule進行的工作:
1. 鏈接到lintJar中的lintJarOutput方法,調用jar方法,并獲得jar包
2. 將得到的JAR包復制到目錄build/intermediates/lint/下,并且重命名為 lint.jar
3. 當項目build到compileLint這一步時執行copyLintJar方法,這樣的話就可以調用到我們自定義的Lint規則
4. 生成AAR方便項目調用
我們把該aar上傳到maven私服上,公司的所有團隊就能進行依賴,配置完lint.xml、lintOptions后,運行指令`./gradlew lint`,就能夠獲取lint-report報告了。
## 四、Lint plugin
相比最開始的拷貝lint.jar,依賴aar相對簡單一些。但依然會存在一些問題,不便于團隊規范的統一:
* 配置繁瑣。每個項目都需要依賴aar,自行配置lint.xml、lintOptions,接入成本大。
* 規范不統一。每個項目都是自行定義的lintOptions,隨著版本迭代,各個項目之間的lint規則,會越來越不一致,難以達到已開始“統一規范”的初心。
基于以上考慮,并參考其他開源項目的實踐情況,這里會創建一個plugin,開發團隊只需要依賴該插件即可。
這里簡述以下處理邏輯,相關實現可以查閱具體的代碼:
* 開始lint操作時,會將plugin內置的lint.xml拷貝到指定目錄,執行完lint操作后會刪除該文件,避免影響其他操作;
* 使用plugin 自帶的lintOption替換默認的配置;
* 自動下載依賴maven上最新的aar,保持實時更新;
通過這個插件:
* 接入簡單。一行代碼就可以完成接入;
* 規范統一。所有團隊的配置和規則都會是最新的;
這里是主體功能實現,完整代碼可以在gitlab上查看:
```groovy
class LintXPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
applyTask(project, getAndroidVariants(project))
}
private static final String sPluginMisConfiguredErrorMessage = "Plugin requires the 'android' or 'android-library' plugin to be configured.";
/**
* 獲取project 項目 中 android項目 或者library項目 的 variant 列表
* @param project 要編譯的項目
* @return variants列表
*/
private static DomainObjectCollection<BaseVariant> getAndroidVariants(Project project) {
if (project.getPlugins().hasPlugin(AppPlugin)) {
return project.getPlugins().getPlugin(AppPlugin).extension.applicationVariants
}
if (project.getPlugins().hasPlugin(LibraryPlugin)) {
return project.getPlugins().getPlugin(LibraryPlugin).extension.libraryVariants
}
throw new ProjectConfigurationException(sPluginMisConfiguredErrorMessage, null)
}
/**
* 插件的實際應用:統一管理lint.xml和lintOptions,自動添加aar。
* @param project 項目
* @param variants 項目的variants
*/
private void applyTask(Project project, DomainObjectCollection<BaseVariant> variants) {
//========================== 統一 自動添加AAR 開始=============================================//
//配置project的dependencies配置,默認都自動加上 自定義lint檢測的AAR包
project.dependencies {
if(project.getPlugins().hasPlugin('com.android.application')){
compile('com.xin.support:lintx:+') {
// compile('com.xin.support:lintx:latest.release') {
force = true;
}
} else {
provided('com.xin.support:lintx:+') {
// provided('com.xin.support:lintx:latest.release') {
force = true;
}
}
}
//去除gradle緩存的配置
project.configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
}
//========================== 統一 自動添加AAR 結束=============================================//
def archonTaskExists = false
variants.all { variant ->
//獲取Lint Task
def variantName = variant.name.capitalize()
Lint lintTask = project.tasks.getByName("lint" + variantName) as Lint
//Lint 會把project下的lint.xml和lintConfig指定的lint.xml進行合并,為了確保只執行插件中的規則,采取此策略
File lintFile = project.file("lint.xml")
File lintOldFile = null
//========================== 統一 lintOptions 開始=============================================//
/*
lintOptions {
lintConfig file("lint.xml")
warningsAsErrors true
abortOnError true
htmlReport true
htmlOutput file("lint-report/lint-report.html")
xmlReport false
}
*/
def newOptions = new LintOptions()
newOptions.lintConfig = lintFile
newOptions.warningsAsErrors = true
newOptions.abortOnError = false
newOptions.htmlReport = true
//不放在build下,防止被clean掉
newOptions.htmlOutput = project.file("${project.projectDir}/lint-report/lint-report.html")
newOptions.xmlReport = false
newOptions.xmlOutput = project.file("${project.projectDir}/lint-report/lint-report.xml")
lintTask.lintOptions = newOptions
//========================== 統一 lintOptions 結束=============================================//
lintTask.doFirst {
//如果 lint.xml 存在,則改名為 lintOld.xml
if (lintFile.exists()) {
lintOldFile = project.file("lintOld.xml")
lintFile.renameTo(lintOldFile)
}
//進行 將plugin內置的lint.xml文件和項目下面的lint.xml進行復制合并操作
def isLintXmlReady = copyLintXml(project, lintFile)
if (!isLintXmlReady) {
if (lintOldFile != null) {
lintOldFile.renameTo(lintFile)
}
throw new GradleException("lint.xml不存在")
}
}
//lint任務執行后,刪除lint.xml
project.gradle.taskGraph.afterTask { task, TaskState state ->
if (task == lintTask) {
lintFile.delete()
if (lintOldFile != null) {
lintOldFile.renameTo(lintFile)
}
}
}
//========================== 統一 lint.xml 結束=============================================//
//========================== 在終端 執行命令 gradlew lintx 的配置 開始=============================================//
// 在終端 執行命令 gradlew lintForXTC 的時候,則會應用 lintTask
if (!archonTaskExists) {
archonTaskExists = true
//創建一個task 名為 lintx
project.task("lintx").dependsOn lintTask
}
//========================== 在終端 執行命令 gradlew lintx 的配置 結束=============================================//
}
}
}
```
## 參考文獻
* [Android Lint:自定義Lint調試與開發](http://www.paincker.com/android-lint-2-implements)
* [美團:Android自定義Lint實踐](https://tech.meituan.com/android_custom_lint.html)
* [Android自定義Lint實踐](https://blog.csdn.net/ouyang_peng/article/details/80374867)
* [LinkedIn:Writing Custom Lint Checks with Gradle](https://engineering.linkedin.com/android/writing-custom-lint-checks-gradle)
* [Android工具:被你忽視的Lint](https://blog.csdn.net/p106786860/article/details/54187138)
* [lint-dev](https://groups.google.com/forum/#!forum/lint-dev)
* [Android Lint:基本使用與配置](https://hk.saowen.com/a/ccfc72ee25ec59ffd6d4c976aa1f10aef3b46f53403b1cf50f20c8b33d90e445)
* [Android Lint增量掃描實戰紀要](https://blog.csdn.net/H176Nhx7/article/details/78870470)
* [google Lint源代碼地址](https://android.googlesource.com/platform/tools/base/+/studio-3.0/lint/)
- 計算機基礎
- 簡答1
- 簡答2
- 專案
- 淺談0與1
- 淺談TCP_IP
- 淺談HTTP
- 淺談HTTPS
- 數據結構與算法
- 常見數據結構簡介
- 常用算法分析
- 常見排序算法
- Java數據結構類問題簡答
- 專案
- HashMap
- 淺談二叉樹
- 算法題
- 算法001_TopN問題
- 算法002_漢諾塔
- 編程思想
- 雜說
- 觀點_優秀程序設計的18大原則
- 設計模式_創建型
- 1_
- 2_
- 設計模式_結構型
- 1_
- 2_
- 設計模式_行為型
- 1_
- 2_
- Java相關
- 簡答1
- 簡答2
- 專案
- 淺談String
- 淺談Java泛型
- 淺談Java異常
- 淺談動態代理
- 淺談AOP編程
- 淺談ThreadLocal
- 淺談Volatile
- 淺談內存模型
- 淺談類加載
- 專案_數據結構
- 淺談SpareArray
- Android相關
- Android面試題
- 專案
- 推送原理解析
- Lint
- 自定義Lint
- Lint使用
- 優化案
- Apk體積優化
- Kotlin相關
- 簡答1
- 簡答2
- 三方框架相
- Okhttp3源碼分析
- ButterKnife源碼分析
- Glide4源碼分析
- Retrofit源碼分析
- RxJava源碼分析
- ARouter源碼分析
- LeakCanary源碼分析
- WMRouter源碼分析
- 跨平臺相關
- ReactNative
- Flutter
- Hybrid
- 優質源
- 資訊源
- 組件源
- 推薦