如何搞定Android同事所有不規範代碼,我用了這招...

前言

以前對下面的問題,我的態度是,不報錯就是沒問題,報錯就用快捷鍵,根據Android Studio提示修復問題,從來不去問個爲什麼?現在代碼潔癖症越來越嚴重的我,忍不住想看清什麼東西在搞鬼。

認真看完本文,一定可以學到最新的知識。就算看不下去,也要點個贊收藏,絕對不虧。本文並不是吐槽Lint的不好,而是在學習Lint過程碰到問題,心態是奔潰的,以及解決每個問題帶來的喜感。

不知道大家有沒有注意項目中黃代碼塊的提示,如下圖所示:

如何搞定Android同事所有不規範代碼,我用了這招...

或者紅色標記的代碼(並沒有任何錯誤),如下圖所示:

如何搞定Android同事所有不規範代碼,我用了這招...

上文黃的提醒和紅色警告,都是來自Android Studio內置的Lint工具檢查我們的代碼後而作出的動作。通過配置Lint,也可以消除上面的提醒。例如,我開發系統APK,根本不需要考慮用戶是否授權。那麼Lint是什麼呢?

Lint

Android Studio 提供一個名爲Lint的靜態代碼掃描工具,可以發現並糾正代碼結構中的質量問題,而無需實際執行該應用,也不必編寫測試用例。Lint 工具可檢查您的 Android 項目源文件是否包含潛在錯誤,以及在正確性、安全性、性能、易用性、便利性和國際化方面是否需要優化改進。

也就是說,通過Lint工具,我們可以寫出更高質量的代碼和代碼潛在的問題,媽媽再也不用擔心我的同事用中文命名了。也可以通過定製Lint相關配置,提高開發效率。

Lint禁止檢查

由於Android Studio內置了Lint工具,好像不需要我們幹嘛。可是呀,我有強迫症,看着上面的黃塊,超級不爽的。所以我們得了解如何配置Lint,讓它爲我們服務,而不是爲Google服務。本文開始的紅色錯誤可以通過註解來消除(一般建議是根據提示進行修正,除非明白自己在做什麼),可以在類或該代碼所在的方法添加@SuppressLint

如何搞定Android同事所有不規範代碼,我用了這招...

上圖中是禁止Lint檢查特定的問題檢查,如果要禁止該Java文件所有的Lint問題,可以在類前添加如下註解:@SuppressLint(all)。對XMl文件的禁止,則可以採用如下形式:

1. 在lint.xml聲明命名空間

namespace xmlns:tools="http://schemas.android.com/tools"

2. 在佈局中使用:

<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文件來禁止檢查。

例如禁止Lint檢查項目AndroidManifest.xml文件的GoogleAppIndexingWarning問題。在項目對應組件工程的Gradle文件添加如下配置,這樣就不會有黃提醒了。

defaultConfig{
 lintOptions {
  disable 'GoogleAppIndexingWarning'
 }
}

那麼,可以禁止lint工具檢查什麼問題?

配置Lint

在上文中通過註解和在xml使用屬性來禁止Lint工具檢查相關問題,其實已經是對Lint的配置了。Lint將多個問題歸爲一個issue(規則),例如下圖右邊的的六大規則。

如何搞定Android同事所有不規範代碼,我用了這招...

上圖是Lint工具的工作流程,下面瞭解相關概念。App Source Files源文件包含組成 Android 項目的文件,包括 Java 和 XML 文件、圖標和 ProGuard 配置文件等。lint.xml 文件此配置文件可用於指定您希望排除的任何 Lint 檢查以及自定義問題嚴重級別。lint Tool我們可以通過Android Studio 對 Android 項目運行此靜態代碼掃描工具。也可以手動運行。Lint 工具檢查可能影響 Android 應用質量和性能的代碼結構問題。Lint 檢查結果我們可以在控制檯(命令行運行)或 Android Studio 的 Inspection Results 窗口中查看 Lint 檢查結果。通過Lint工具的工作流程瞭解到,可以在lint.xml文件配置一些信息。一般新建項目都是沒有lint.xml文件的,在項目的根目錄創建lint.xml文件。格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- list of issues to configure -->
</lint>

那麼有哪些Issues(規則)呢?
在Android主要有如下六大類:

  • Security 安全性。在AndroidManifest.xml中沒有配置相關權限等。
  • Usability 易用性。重複圖標;上文開始黃警告也屬於該規則等。
  • Performance 性能。內存泄漏,xml結構冗餘等。
  • Correctness 正確性。超版本調用API,設置不正確的屬性值等。
  • Accessibility 無障礙。單詞拼寫錯誤等。
  • Internationalization國際化。字符串缺少翻譯等。

其他更多Issues,可以通將命令行切換到../Android/sdk/tools/bin目錄下,然後輸入lint --list。例如在Mac下:cd /Users/gitcode8/Library/Android/sdk/tools/bin輸入./lint --list結果如下:

如何搞定Android同事所有不規範代碼,我用了這招...

例如官網提供的參考例子:

<?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來看看代碼質量如何吧。

  1. 通過Android Studio 的菜單欄Analyze選項下拉選擇第一個選項Inspect Code.

如何搞定Android同事所有不規範代碼,我用了這招...

2、在彈出框根據自己需要選擇lint工具的檢查範圍,這裏選擇整個項目。檢查時間也是根據項目大小來定的。

如何搞定Android同事所有不規範代碼,我用了這招...

3、等待一段時間後,會列出檢查結果。從下圖看到,不僅會檢查Android存在的問題,也會檢查Java等其他問題。通過單擊問題,可以從右邊提示框看到問題發生的地方和相關建議。

如何搞定Android同事所有不規範代碼,我用了這招...

到這裏,就開始對項目修修補補吧。

在正式開刷之前不得不提到:自定義規則。爲什麼要自定義呢?已有規則不符合自己或團隊開發需求,或者覺得Lint存在一些缺陷。在網上大多數文章千篇一律,都是通過將Log打印來舉例,看着都好累哦。由於沒有相關官方文檔和第三方教程(可能由於lint的api更新太快,沒人願意做這種吃力不討好的工作),也這就只有這樣了。本文通過自定義命名規範規則來講解整個過程。

Lint中重點的API

先學習相關api,可以快速理解一些概念,可以粗略看過,下面結實踐再回來看。

1、Issue

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)));
  • 第一個參數id 唯一的id,簡要表面當前提示的問題。
  • 第二個參數briefDescription 簡單描述當前問題
  • 第三個參數explanation 詳細解釋當前問題和修復建議
  • 第四個參數category 問題類別,例如上文講到的Security、Usability等等。
  • 第五個參數priority 優先級,從1到10,10最重要
  • 第六個參數Severity 嚴重程度:FATAL(奔潰), ERROR(錯誤), WARNING(警告),INFORMATIONAL(信息性),IGNORE(可忽略)
  • 第七個參數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(其他);

這樣就能很清楚的定義一個規則,上文只定義了檢查命名規範的規則。

2、IssueRegistry

用於註冊要檢查的Issue(規則),只有註冊了Issue,該Issue才能被使用。例如註冊上文的命名規範規則。

public class Register extends IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues() {
  return Arrays.asList(NamingConventionDetector.ISSUE);
 }
}

4、Detector

查找指定的Issue,一個Issue對應一個Detector。自定義Lint 規則的過程也就是重寫Detector類相關方法的過程。具體看下小結實踐。

5、Scanner

掃描並發現代碼中的Issue,Detector需要實現Scaner,可以繼承一個到多個。

  • UastScanner 掃描Java文件和Kotlin文件
  • ClassScanner 掃描Class文件
  • XmlScanner 掃描Xml文件
  • ResourceFolderScanner 掃描資源文件夾
  • BinaryResourceScanner 掃描二進制資源文件
  • OtherFileScanner 掃描其他文件
  • GradleScanner 掃描Gradle腳本

舊版本的JavaScanner、JavaPsiScanner隨着版本的更新已經被UastScanner替代了。

自定義Lint規則實踐

通過實現命名規範Issue來熟悉和運用上小節相關的api。自定義規則需要在Java工程中創建,這裏通過Android Studio來創建一個Java Library。

步驟:File-&gt;New-&gt;New Mudle-&gt;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規則

使用自定義Lint規則有兩種形式:jar包和AAR文件。

jar形式使用

在Android Studio的Terminal輸入下面命令:

./gradlew lib:assemble

看到BUILD SUCCESSFUL則表示生成jar包成功,可以在下面路徑找到:

lib->build->libs

如圖:

如何搞定Android同事所有不規範代碼,我用了這招...

將lib.jar拷貝下面目錄:

~/.android/lint/

如果lint文件夾不存在,則創建。通過命令行輸入lint --list。滑到最後可以看到配置的規則,如圖:

如何搞定Android同事所有不規範代碼,我用了這招...

重啓Android Studio,讓規則生效。檢測到方法大寫,不符合命名規範,報導該問題。

如何搞定Android同事所有不規範代碼,我用了這招...

類名不符合規範:

如何搞定Android同事所有不規範代碼,我用了這招...

從上文可以看到,放在目錄下的jar包對所有工程都是有效的。如果要針對單個工程,那麼就需要需要AAR形式了。

AAR形式

在同個工程新建一個Android Library,名爲lintLibrary,修改相關配置。

1、修改Java工程的依賴

修改自定義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"
2、修改Android Library依賴

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文件中。

3、輸出AAR文件

此時跟輸出普通的AAR文件沒什麼區別,但爲了手把手教會第一個自定義Issue,我寫!

步驟:

菜單欄:View->Tool Windows->Gradle

此時Android Studio在右邊會打開如下窗口:

如何搞定Android同事所有不規範代碼,我用了這招...

根據上圖操作,雙擊assemble,稍等一會,在控制檯看BUILD SUCCESSFUL,則可在下面目錄找到AAR文件。

lintLibrary->build->outputs->aar

這一小節的步驟也可以通過命令行執行。

4、使用AAR文件

有本地依賴或者上傳遠程倉庫,這裏只介紹本地依賴。將上小結生成的AAR文件拷貝在app的libs文件夾。並配置app組件的build.gradle

repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation (name:'lintlibrary-release', ext:'aar')
}

到這裏,就能使用自定義的lint規則了,效果和上面使用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的參考文章沒有生效(https://engineering.linkedin.com/android/writing-custom-lint-checks-gradle),可能是Android Studio版本的問題。

另外使用lintChecks輸出AAR不生效,Android Studio 3.4+新特性變更,採用lintPublish(AGP 3.4+)。

總結

花了好長好長的時間寫本文,差點就放棄了。因爲自己Android Studio看不了lint的源碼,只能從網上找,網上又找不到最新的doc。過濾太多雷同文章,差點想哭,一些最新的文章也跟不上相關技術的更新。。。

但是一切都值得,因爲能幫助到想學習Android Studio lint工具的同學,一起嚮往美好的生活。

好了,文章到這裏就結束瞭如果你覺得文章還算有用的話,不妨把它們推薦給你的朋友。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章