代码洁癖症的我,学习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文件添加以下配置,这样就不会有黄色提醒了。markdown

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的代码已经贴在文章里了。

相关文章
相关标签/搜索