之前对下面的问题,个人态度是,不报错就是没问题,报错就用快捷键,根据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的代码已经贴在文章里了。