Google对这块儿还是比较重视的,从频繁更新可见一斑。
- AndroidStudio2.0+需要手动把lint.jar拷贝到.android/lint/下;
- AndroidStudio3.0+支持 lintChecks project(’:checks’)的形式添加自定义规则。
新建一个Java module(这里名字记做checks),在build.gradle中配置依赖:
dependencies {
// 注意这里必须是compileOnly
compileOnly "com.android.tools.lint:lint-api:$lintVersion"
compileOnly "com.android.tools.lint:lint-checks:$lintVersion"
}
jar {
manifest {
// Only use the "-v2" key here if your checks have been updated to the
// new 3.0 APIs (including UAST)
attributes("Lint-Registry-v2": "com.example.lint.checks.SampleIssueRegistry")
}
}
在需要被检测的工程中添加依赖
dependencies {
lintChecks project(':checks')
}
目前大多设置Javaversion为1.8
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
- AndroidStudio4.0+同3.0+的差不多,不过指定自定义Lint的方式改为通过services的形式,配置如下:
checks
src
main
java
resources
META-INF
services
com.android.tools.lint.client.api.issueRegistry
com.android.tools.lint.client.api.issueRegistry文件中的内容就是registry的完整类名:
com.example.lint.checks.SampleIssueRegistry
java Module中的build.gradle中可以通过以下配置lint:
lintOptions {
htmlReport true
htmlOutput file("lint-report.html")
textReport true
absolutePaths false
ignoreTestSources true
}
其他配置和3.0+的一样
下面只是记录一下api相关的东西,方便查找
首先来了解一下Registry,从上面的配置可以看出这个自定义lint配置的入口类,这个类的主要作用就是提供一个检察的列表。
/*
* The list of issues that will be checked when running <code>lint</code>.
*/
public class SampleIssueRegistry extends IssueRegistry {
@Override
public List<Issue> getIssues() {
return Arrays.asList(
SampleCodeDetector.ISSUE,
FullImplDetector.ISSUE,
PrintDetector.ISSUE,
XLoggerDetector.ISSUE
);
}
@Override
public int getApi() {
return ApiKt.CURRENT_API;
}
}
接下来一起认识一下Issue。下面是源码中的一段注释
An issue is a potential bug in an Android application. An issue is discovered by a [Detector], and has an associated [Severity].
说白了Issue就是问题的简称,其内部囊括了上报问题的一些信息,下面简单的解释下:
public static final Issue ISSUE = Issue.create(
// the unique id of this issue(唯一ID)
"ShortUniqueId",
// 问题的简单描述
"Lint Mentions",
// 分析问题的原因,以及提出修改建议.
"This check highlights string literals in code which mentions " +
"the word `lint`. Blah blah blah.\n" +
"\n" +
"Another paragraph here.\n",
// 问题所属的主要类别
Category.CORRECTNESS,
// 严重级别 a priority from 1 to 10
6,
// 问题的严重程度 the severity of the issues found by this detector
Severity.WARNING,
// 问题的检查者(The implementation for the given issue,包括问题检察的detector和扫描的范围)
new Implementation(
SampleCodeDetector.class,
Scope.JAVA_FILE_SCOPE));
上面注释中提到Issue是通过Detector来发现的,所以重头戏在Detector,接下来我们来一起简单认识下这位重量级“人物”。
目前比较稳定的版本是26.5,当然其中的也是最新的,网上很多都是之前版本的,这里只介绍26.6版本的api。
Detector是一个abstract类,其中定义了几种扫描接口:
abstract class Detector {
/**
* See [com.android.tools.lint.detector.api.SourceCodeScanner]; this class is
* (temporarily) here for backwards compatibility
可以看到这里已经不推荐使用该接口,留在这里只是为了做兼容,分析文件专用(eg: Java source files (or other similar source files, such as Kotlin files.))
*/
interface UastScanner : com.android.tools.lint.detector.api.SourceCodeScanner
/**
* See [com.android.tools.lint.detector.api.ClassScanner]; this class is
* (temporarily) here for backwards compatibility
分析扫描字节码专用(scan bytecode / compiled class files)
*/
interface ClassScanner : com.android.tools.lint.detector.api.ClassScanner
/**
* See [com.android.tools.lint.detector.api.BinaryResourceScanner]; this class is
* (temporarily) here for backwards compatibility
分析扫描资源二进制文件专用(scan binary resource files
* (typically bitmaps but also files in res/raw)
*/
interface BinaryResourceScanner : com.android.tools.lint.detector.api.BinaryResourceScanner
/**
* See [com.android.tools.lint.detector.api.ResourceFolderScanner]; this class is
* (temporarily) here for backwards compatibility
分析扫描资源文件夹专用(scan resource folders)
*/
interface ResourceFolderScanner : com.android.tools.lint.detector.api.ResourceFolderScanner
/**
* See [com.android.tools.lint.detector.api.XmlScanner]; this class is (temporarily) here
* for backwards compatibility
分析扫描xml文件专用(scan XML files)
*/
interface XmlScanner : com.android.tools.lint.detector.api.XmlScanner
/**
* See [com.android.tools.lint.detector.api.GradleScanner]; this class is (temporarily)
* here for backwards compatibility
分析扫描gradle文件专用(scan Gradle files)
*/
interface GradleScanner : com.android.tools.lint.detector.api.GradleScanner
/**
* See [com.android.tools.lint.detector.api.OtherFileScanner]; this class is
* (temporarily) here for backwards compatibility
分析扫描其他文件使用(scan other files )
*/
interface OtherFileScanner : com.android.tools.lint.detector.api.OtherFileScanner
}
扫描顺序
1. Manifest file
2. Resource files, in alphabetical order by resource type
(therefore, "layout" is checked before "values", "values-de" is checked before
"values-en" but after "values", and so on.
3. Java sources
4. Java classes
5. Gradle files
6. Generic files
7. Proguard files
8. Property files
如果关心的是方法重写以下方法
fun getApplicableMethodNames(): List<String>?
fun visitMethodCall(
context: JavaContext,
node: UCallExpression,
method: PsiMethod
)
关心构造函数
fun getApplicableConstructorTypes(): List<String>?
fun visitConstructor(
context: JavaContext,
node: UCallExpression,
constructor: PsiMethod
)
关心引用
fun getApplicableReferenceNames(): List<String>?
fun visitReference(
context: JavaContext,
reference: UReferenceExpression,
referenced: PsiElement
)
关心资源引用
fun appliesToResourceRefs(): Boolean
fun visitResourceReference(
context: JavaContext,
node: UElement,
type: ResourceType,
name: String,
isFramework: Boolean
)
关心父类
fun applicableSuperClasses(): List<String>?
fun visitClass(context: JavaContext, declaration: UClass)
// 该方法主要针对匿名内部类,及lambda
fun visitClass(context: JavaContext, lambda: ULambdaExpression)
关心注解
/**
* Returns a list of fully qualified names for super classes that this
* detector cares about. If not null, this detector will **only** be called
* if the current class is a subclass of one of the specified superclasses.
* Lint will invoke [.visitClass] (and sometimes
* [.visitClass] when it encounters
* subclasses and lambdas for these types.
*
* @return a list of fully qualified names
*/
fun applicableAnnotations(): List<String>?
/**
* Returns whether this detector cares about an annotation usage of the given type.
* Most detectors are interested in all types except for
* [AnnotationUsageType.BINARY] and [AnnotationUsageType.EQUALITY], which only
* apply for annotations that are expressing a "type", e.g. it would be suspicious
* to combine (compare, add, etc) resources of different type.
*/
fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean
/**
* Whether this lint detector wants to consider annotations on an
* element that were inherited (e.g. defined on a super class or super method)
*/
fun inheritAnnotation(annotation: String): Boolean
fun visitAnnotationUsage(
context: JavaContext,
usage: UElement,
type: AnnotationUsageType,
annotation: UAnnotation,
qualifiedName: String,
method: PsiMethod?,
referenced: PsiElement?,
annotations: List<UAnnotation>,
allMemberAnnotations: List<UAnnotation>,
allClassAnnotations: List<UAnnotation>,
allPackageAnnotations: List<UAnnotation>
)
participate in a shared traversal of the tree, registering element types they’re
interested with using {@link #getApplicableUastTypes()} and then providing
a visitor where they implement the corresponding visit methods
For the shared traversal, just provide this handler instead and implement the
appropriate visit methods.
fun getApplicableUastTypes(): List<Class<out UElement>>?
fun createUastHandler(context: JavaContext): UElementHandler?
下面是一个简单的检察Serializable中属性没有实现Serializable例子:
①s继承Detector,这里由于要分析Java文件,实现SourceCodeScanner
public class FullImplDetector extends Detector implements SourceCodeScanner {
private final String SERIALIZABLE = "java.io.Serializable";
private final String PARCELABLE = "android.os.Parcelable";
// ②定义问题Issue
/**
* The main issue discovered by this detector
*/
public static final Issue ISSUE =
Issue.create(
"Parcelable&Serializable",
"missing implement Parcelable or Serializable",
"make fields that in a class that implement Parcelable or Serializable implements Parcelable or Serializable too ",
Category.CORRECTNESS,
3,
Severity.ERROR,
new Implementation(FullImplDetector.class, Scope.JAVA_FILE_SCOPE))
.addMoreInfo("http://developer.android.com/reference/android/os/Parcel.html")
.setAndroidSpecific(true);
// 由于这里关心的是实现Serializable或Parcelable的类,所以重写applicableSuperClasses方法
@Nullable
@Override
public List<String> applicableSuperClasses() {
return Arrays.asList(
SERIALIZABLE,
PARCELABLE
);
}
// applicableSuperClasses方法对应的方法是visitClass
@Override
public void visitClass(@NotNull JavaContext context, @NotNull UClass declaration) {
JavaEvaluator javaEvaluator = context.getEvaluator();
UField[] fields = declaration.getFields();
// 遍历所有field,过滤掉基本数据类型和Bitmap,drawable
for (UField field : fields) {
String clazzName = field.getType().getCanonicalText(); // 获取field的类型对应的完整字符串
if (!isBaseType(clazzName)) {
// check wrapper eg:array list
// if (javaEvaluator.extendsClass(javaEvaluator.findClass(clazzName), "java.util.Collection", false)) {
if (clazzName.contains("<")) { // 这里泛型拿不出来,暂时通过是否包含<来判断是不是容器类型
// 取泛型
// Type type = ((ParameterizedType)(field.getClass().getGenericSuperclass())).getActualTypeArguments()[0];
// 这里获取泛型暂时不知道怎么获取,先使用截取字符串的方法获取完整类名
String clazzFullName = getTargetStr(clazzName);
if (!isBaseType(clazzFullName)){
if (javaEvaluator.extendsClass(declaration,SERIALIZABLE, true)) {
// context.getUastContext()--转化
if (!javaEvaluator.extendsClass(javaEvaluator.findClass(clazzFullName), SERIALIZABLE, false)) {
context.report(ISSUE,context.getLocation(javaEvaluator.findClass(clazzFullName)),clazzFullName+" should implement "+SERIALIZABLE);
}
}
if (javaEvaluator.extendsClass(declaration,PARCELABLE, true)) {
if (!javaEvaluator.extendsClass(javaEvaluator.findClass(clazzFullName), PARCELABLE, false)) {
context.report(ISSUE,context.getLocation(javaEvaluator.findClass(clazzFullName)),clazzFullName+" should implement "+PARCELABLE);
}
}
}
} else {
// (循环--这里不需要,只要检察一次即可【下次添加的成为新的检察项】)检察是否实现 Parcelable or Serializable
if (javaEvaluator.extendsClass(declaration,SERIALIZABLE, true)) {
if (!javaEvaluator.extendsClass(javaEvaluator.findClass(clazzName), SERIALIZABLE, false)) {
context.report(ISSUE,context.getLocation(javaEvaluator.findClass(clazzName)),clazzName+" should implement "+SERIALIZABLE);
}
}
if (javaEvaluator.extendsClass(declaration,PARCELABLE, true)) {
if (!javaEvaluator.extendsClass(javaEvaluator.findClass(clazzName), PARCELABLE, false)) {
context.report(ISSUE,context.getLocation(javaEvaluator.findClass(clazzName)),clazzName+" should implement "+PARCELABLE);
}
}
}
}
}
}
// 下面是一些工具方法
private String[] types = {
byte.class.getCanonicalName(),
int.class.getCanonicalName(),
short.class.getCanonicalName(),
double.class.getCanonicalName(),
float.class.getCanonicalName(),
char.class.getCanonicalName(),
String.class.getCanonicalName(),
boolean.class.getCanonicalName(),
"android.graphics.Bitmap",// bitmap
"android.graphics.drawable.Drawable"// drawable
};
private boolean isBaseType(String typeClassName) {
for (String s : types) {
if (s.equals(typeClassName)) {
return true;
}
}
return false;
}
// 提取<>之间的泛型类型的完整字符串
private String getTargetStr(String s){
int start = 0;
int end = s.length();
if (s.contains("<")){
start = s.indexOf("<")+1;
}
if (s.contains(">")){
end = s.indexOf(">");
}
return s.substring(start,end);
}
}
执行命令:
gradlew app名字:lintDebug