代碼潔癖症的我,學習Lint學到心態爆炸

前言

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

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

不知道你們有沒有注意項目中黃色代碼塊的提示,以下圖所示: node

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

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

Lint

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

也就是說,經過Lint工具,咱們能夠寫出更高質量的代碼和代碼潛在的問題,媽媽不再用擔憂個人同事用中文命名了。也能夠經過定製Lint相關配置,提升開發效率。git

Lint禁止檢查

因爲Android Studio內置了Lint工具,好像不須要咱們幹嗎。但是呀,我有強迫症,看着上面的黃色塊,超級不爽的。因此咱們得了解如何配置Lint,讓它爲咱們服務,而不是爲Google服務。github

本文開始的紅色錯誤能夠經過註解來消除(通常建議是根據提示進行修正,除非明白本身在作什麼),能夠在類或該代碼所在的方法添加@SuppressLintapi

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

  1. 在lint.xml聲明命名空間
namespace xmlns:tools="http://schemas.android.com/tools"
複製代碼
  1. 在佈局中使用:
<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文件添加以下配置,這樣就不會有黃色提醒了。併發

defaultConfig{
    lintOptions {
        disable 'GoogleAppIndexingWarning'
    }
}
複製代碼

那麼,能夠禁止lint工具檢查什麼問題?

配置Lint

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

上圖是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 結果以下:

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

<?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.

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

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

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

自定義規則

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

Lint中重點的API

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

一、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(其餘);

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

二、IssueRegistry

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

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

四、Detector

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

五、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->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規則

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

jar形式使用

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

./gradlew lib:assemble
複製代碼

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

lib->build->libs
複製代碼

如圖:

將lib.jar拷貝下面目錄:

~/.android/lint/
複製代碼

若是lint文件夾不存在,則建立。經過命令行輸入lint --list。滑到最後能夠看到配置的規則,如圖:

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

從上文能夠看到,放在目錄下的jar包對全部工程都是有效的。若是要針對單個工程,那麼就須要須要AAR形式了。

AAR形式

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

一、修改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"
複製代碼
二、修改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文件中。

三、輸出AAR文件

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

步驟:

菜單欄:View->Tool Windows->Gradle
複製代碼

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

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

lintLibrary->build->outputs->aar
複製代碼

這一小節的步驟也能夠經過命令行執行。

四、使用AAR文件

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

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

到這裏,就能使用自定義的lint規則了,效果和上面使用jar包是一致的。若是不生效,重啓Android Studio看看。

採坑記

一、Found more than one jar in the 'lintChecks' configuration. Only one file is supported

這是由於在輸出AAR文件中,參考其餘人的文章。沒有將Java Library的依賴改成compileOnly。並且Android Library中使用lintChecks

二、輸出AAR文件沒有生效

不知道爲何,Linkedin的參考文章沒有生效,多是Android Studio版本的問題。

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

總結

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

可是一切都值得,由於能幫助到想學習Android Studio lint工具的同窗,一塊兒嚮往美好的生活。

GitHub

點個贊行不

寫此文找到的一些具備參考意義的文章:

Android 官方指導

Linkedin 指導

美團->Android自定義Lint實踐

lint-custom-rules

另外:本文沒有demo,demo的代碼已經貼在文章裏了。

相關文章
相關標籤/搜索