WritingCustom Lint Rules
Lint comes preconfigured with around 100 checks as of ADT 20. However, it can also be extended with additional rules. For example, if you are the author of a library project, and your library project has certain usage requirements, you can write additional lint rules to check that your library is used correctly, and then you can distribute those extra lint rules for users of the library. Similarly, you may have company-local rules you'd like to enforce.
在ADT20中,Lint有预设的100个左右的检查规则。但依然可以添加规则作为扩展。例如,你是一个library project的作者,你的库有既定的使用规范,你可以自定lint规则来检查你的库是否被正确使用,并且将这些额外规则作为库的一部分一起提供给使用者。类似的,你可能会有公司相关的规则需要执行检查。
What's New in Android Development Tools
ADT的新特性
Create the Custom Rules Project for Android Studio 为Android Studio创建自定义规则 First, you'll need to create a separate Java project to compile the lint rules. Note that this is a Java project, not an Android project, since this code will be running in Android Studio, IntelliJ, Eclipse, or from Gradle or the command line lint tool -- not on a device.
首先,你需要创建一个独立的Java项目来编译lint规则。注意,是Java项目,而不是Android项目,因为代码将会被运行在Android Studio,IntelliJ,Eclipse,Gradle,lint tool 命令行中,而不是在设备中。
The simplest way to do this is to just start with this sample project, unzipping it, and then changing the source code as necessary. You can open it in Android Studio or IntelliJ by pointing to the build.gradle project to import it:
最简单的方法是从这个 this sample project示例工程开始,解压它,按需编写源码。你可以使用Android Studo或者IntelliJ指定build.gradle来导入项目:
It's a Gradle project which has all the dependencies set up to use the Lint APIs, and to build a .jar file with the right manifest entries.
这是一个Gradle项目,它已包含了使用Lint APIs所需的所有dependencies,以及生成a.jar所需的正确的manifest配置。
Create the Custom Rules Project in Eclipse If you're using Eclipse, create a plain Java project using a simple library:
用Eclipse创建自定义规则项目
如果你使用Eclipse,创建一个简单的Java project:
Next, add the lint_api.jar file to the classpath of the project. This jar file contains the Lint APIs that rules implement. You can find lint_api.jar in the SDK install directory as tools/lib/lint_api.jar.
然后,将lint_api.jar加入cliasspath中。此jar文件包含规则需要实现的LintAPIs.你可以在SDK安装目录中找到 tools/lib/lint_api.jar.
Create Detector 创建检测器 When you're implementing a "lint rule", you'll really be implementing a "detector", which can identify one or more different types of "issues". This separation lets a single detector identify different types of issues that are logically related, but may have different severities, descriptions, and that the user may want to independently suppress. For example, the manifest detector looks for separate issues like incorrect registration order, missing minSdkVersion declarations, etc. For more detailed information on how to write a lint check, see the Writing a Lint Check document.
当你实现一个lint rule时,你将需要实现一个detector,用以定义一个或者多个不同类型的issue事件。这些不同类型的issues有逻辑上的关联,但又有不同的严重等级和描述,使用者可以单独的处理他们。例如,一个manifest detector寻找例如错误的注册信息,缺失的minSdkVersion 声明之类的独立的issues。更多关于如何编写一个lint检查,请看Writing a Lint Check 。
In our example, we'll assume that we have a custom view, and we want to make a lint rule which makes sure that all uses of the custom view defines a particular custom attribute.
在我们的例子中,我们假设有一个自定义的view,我们想制定一个lint规则来确认所有使用了这个view的地方是否都定义了一个特殊的自定义属性。 Here's the full detector source code:
以下是完整的detector 源码:
package googleio.demo;
import java.util.Collection; import java.util.Collections;
import org.w3c.dom.Element;
import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.XmlContext;
public class MyDetector extends ResourceXmlDetector { public static final Issue ISSUE = Issue.create( "MyId", "My brief summary of the issue", "My longer explanation of the issue", Category.CORRECTNESS, 6, Severity.WARNING, new Implementation(MyDetector.class, Scope.RESOURCE_FILE_SCOPE));
@Override public Collection<String> getApplicableElements() { return Collections.singletonList( "com.google.io.demo.MyCustomView"); }
@Override public void visitElement(XmlContext context, Element element) { if (!element.hasAttributeNS( "http://schemas.android.com/apk/res/com.google.io.demo", "exampleString")) { context.report(ISSUE, element, context.getLocation(element), "Missing required attribute 'exampleString'"); } } }
First you can see that this detector extends ResourceXmlDetector. This is a detector intended for use with resource XML files such as layout and string resource declarations. There are other types of detectors, for example detectors to deal with Java source code or byte code.
首先你会注意到这个detector继承自ResourceXmlDetector。这是一个用于xml文件的detector,例如layout和string资源。
The getApplicableElements() method returns a set of XML tags that this detector cares about. Here we just return the tag for our custom view. The lint infrastructure will now call the visitElement() method for each occurrence of the tag -- and only for occurrences of that tag. In the visitElement()method, we simply see whether the current element defines our custom attribute ("exampleString"), and if not, we report an error.
getApplicableElements()方法返回一组detector关注的xml标签。这里我们返回的时我们自定义的view标签。当每一个标签被找到时,lint的底层实现就会调用visitElement()。在visitElement()方法中,我们简单的检查我们自定义的("exampleString")属性是否存在,如果没有,我们报告error。 The visitElement method passes a context object. The context provides a lot of relevant context; for example, you can look up the corresponding project (and from there, ask for the minSdkVersion or targetSdkVersion), or you can look up the path to the XML file being analyzed, or you can (as is shown here) create a Location for the error. Here we are passing in the element, which will make the error point to the start of the element, but you can for example pass in an XML attribute object instead, which would point to a particular attribute.
visitElement 方法拥有一个context对象。这个context对象提供与很多其他的context对象的联系;例如,你可以查询相关项目的信息(如minSdkVersion和targetSdkVersion),你可以获取被分析的xml文件的路径,或者如这里展示的一样创建一个本地的error。这里,我们通过element参数将error指向这个element开始的地方,当然你也可以指定error对应具体的属性。
Again, see the Writing a Lint Check document for more details.
查阅Writing a Lint Check 获取更多信息 Create Issue 创建事件 The first parameter to the report() method is ISSUE. This is a reference to the issue being reported by this detector. This is the static field we defined at the top of the class above:
report()方法的第一个参数是ISSUE。这是detector report时的一个对象引用。以下是我们在类开头定义的一个static field.
public static final Issue ISSUE = Issue.create( "MyId", "My brief summary of the issue", "My longer explanation of the issue", Category.CORRECTNESS, 6, Severity.WARNING, new Implementation(MyDetector.class, Scope.RESOURCE_FILE_SCOPE));
An issue has several different attributes, defined in this order above: 一个issue有多个不同的属性,按以上代码的顺序定义: • An id. This is a constant associated with the issue, which should be short and descriptive; this id is for example used in Java suppress annotations and XML suppress attributes to identify this issue. Id 这是一个与这个issue关联的常量,应当简要;这个id用于java 拦截annotations 和xml拦截属性对issue的识别。 • • A summary. This should be a brief (single line) summary of the issue, which for example is used in the Lint Options UI in Eclipse and elsewhere to briefly describe the issue. • 概要。此为issue的简短说明,用于例如Eclipse的LintOptions UI等地方以简短描述此issue • An explanation. This is a longer explanation of the issue, which should explain to the lint user what the problem is. Typically a lint error message is brief (a single line), and there are times when it's difficult to explain a subtle issue fully in a single line error message, so the explanation is used to provide more context. The explanation is shown in the full HTML report created by the lint command line tool, it's shown in the Eclipse Lint window for the currently selected issue, etc. • 解释。这是此issue的具体解释,用于向使用者说明具体问题。通常,lint error message是简短的一句话,当它很难描述时,explanation就用来提供更多信息。这段解释信息会被lint命令行工具写入HTML报告中,你也会在Eclipse Lint窗口看到当前选择的issue的解释。 • A category. There are many predefined categories, and categories can be nested (such as Usability > Icons), and this helps users filter and sort issues, or via the command line to only run issues of a certain type. The most common categories are "correctness" and "performance", but also includes internationalization, accessibility, usability, etc. • 类别。有很多预设的类别,类别也可以内嵌(通过Usability > Icons),它帮助使用者过滤和排序issues. • A priority. Priorities are an integer between 1 and 10 inclusive, with 10 being the most important, and this is used to sort issues relative to each other. • 优先级。优先级是包含1到10之间的整数,10是最高等级,它用于issue之间的排序。 • A severity. An issue can have a default severity of fatal, error, warning, or ignore. Note that I said "default": users can override the severities used for an issue via a lint.xml file (more info). Fatal and error are both shown as "errors" in Eclipse, but Fatal issues are also run automatically (without user intervention) whenever a user attempts to export an APK in Eclipse, and if any of them find an error, then the export will abort. Rules with severity "ignore" are not run. • 严重程度。Issus会有默认的严重程度等级,如,致命的,错误的,警告的,忽略的。注意我说的默认:用户可以通过lint.xml文件重写一个issue的严重程度等级。Fatal和error在Eclipse里都显示为errors,但是当用户想导出apk的时候,fatal的issues依然可以编译通过,但是如果他们发现了error,export则会中断。 • A detector class. This is simply pointing to the detector responsible for identifying the issue. This should point to our own class. Note that a detector is instantiated automatically on our behalf by lint. This is done for each run. Therefore, you don't have to worry about clearing out instance state in your detector after a run; in Eclipse (where lint may be run multiple times) a new detector will be created for each run. Therefore, your lint rule must have a public default constructor. (If you don't specify one, the compiler will automatically create one for you, which is the case above.) • Detector类。指定一个detector类来响应和鉴定issue。需要指定我们自己的类。Lint时我们的detector会自动初始化。每次运行时都会如此。因此,你不必担心运行后你得实体会被清除掉;在Eclipse中(lint会被多次运行),新的detector对象在每次运行时都会创建。因此,你的lint规则需要又一个默认的公共构造器。(如果你没有指定,编译器会自动为你加上) • A scope. This determines what types of files this issue applies to. Here we just state that we apply to XML files, but the unused resource issue has a scope which includes both Java and XML files. • 范围。它指定那种类型的文件需要检查。这里我们只申请检查了xml文件,但是如果要检查没有使用的资源文件issue,你需要将java和xml文件都指定申请。 You can also call additional methods on the issue to for example define an issue as disabled by default, or to set a "more info" URL.
你可以调用issus类其他的方法,如定义一个默认不可用的issue,或者设置一个指向更多信息的URL. We've created an instance of the issue. The builtin lint issues are all registered in the BuiltinIssueRegistry class. However, for custom rules, we need to provide our own registry. Each custom jar file provides its own issue registry, where each issue registry can contain one or more issues identified by the given jar file.
我们已经创建了一个issue实例。Lint的build操作将所有的issue注册到了BuiltinIssueRegistry类中。然而,我们需要为自定义的规则提供自己的注册表。每一个自定义的jar文件提供自由的issue注册表,可包含一个或多个issue。
Here's our custom issue registry: 以下是我们自己的issue注册表
package googleio.demo;
import java.util.Arrays; import java.util.List;
import com.android.tools.lint.client.api.IssueRegistry; import com.android.tools.lint.detector.api.Issue;
public class MyIssueRegistry extends IssueRegistry { public MyIssueRegistry() { }
@Override public List<Issue> getIssues() { return Arrays.asList( MyDetector.ISSUE ); } }
The getIssues() method returns a list of issues provided by this registry. We could obviously use Collections.singletonList() in this specific case instead of Arrays.asList, but there would typically be more than one issue. Note that the registry must have a public default constructor, such that it can be instantiated by lint.
getIssues()方法返回注册表中提供的issue列表。我们显然可以用Collections.singletonList()代替此案例中的Arrays.asList,但通常可能包含多个issue。注意,registry必须包含默认公共构造器,以被lint实例化。
Register Registry 注册注册表 The last thing we need to do is register the issue registry, such that it can be found by lint. We do that by editing the manifest file for the jar file.
最后我们要做的就是注册issue注册表,以被lint找到。我们通过修改jar文件中得manifest文件来达到这个目的。 If you're using the Gradle / Android Studio project, this is handled by Gradle; just open the build.gradle file and update the path to the registry in the jar entry as necessary.
如果你使用Gradle或者Android Studio项目,这件事可以交由Gradle做;你只要打开build.gradle文件,更新所需的registry的入口路径。 In Eclipse, create a manifest file like this:
在Eclipse中,创建如下的manifest文件:
Manifest-Version: 1.0 Lint-Registry: googleio.demo.MyIssueRegistry
and then export a Jar file from your project where you use the above manifest file. (I had some difficulties doing this with Eclipse; there are clearly options in the Export Jar dialog to do it, but when I looked in my exported .jar file and opened the manifest file, it didn't include my above line, so I created the jar file manually: jar cvfm customrule.jar META-INF/MANIFEST.MF googleio/).
然后将用到这个manifest文件的jar导出。(我在Eclipse做这个事的时候遇到了一些困难;有清晰的Export Jar对话框选项完成这件事,但当我检查我导出的文件时,我发现manifest文件里不包含我上面的内容,所以我通过命令手动创建的jar文件jar cvfm customrule.jar META-INF/MANIFEST.MF googleio/)
NOTE: that the exporter in Eclipse expects a newline (\n) after the second line. So, make sure there is a blank line at the end, and then export directly from Eclipse should work properly.
注意:Eclipse的导出器会在第二行之后添加(\n).所以,确保在最后有一个空行,这样直接从Eclipse导出的操作才可能有效。
Register Custom Jar File 注册自定义Jar文件 Now we have our customrule.jar file. When lint runs, it will look for custom rule jar files in the ~/.android/lint/ folder, so we need to place it there:
现在,我们有我们自己的customrule.jar文件了。当lint运行时,它会在~/.android/lint/文件夹下讯早自定义规则的jar,因此我们需要将它移到那里
$ mkdir ~/.android/lint/ $ cp customrule.jar ~/.android/lint/
(Somebody asked about this; Lint basically calls the general Android tools method to find the "tools settings directory", used for emulator snapshots, ddms configuration, etc. You can find the relevant code for that here: https://android.googlesource.com/platform/tools/base/+/master/common/src/main/java/com/android/prefs/AndroidLocation.java )
(有些人问关于这方面的实现;Lint主要调用一般性的Android tools method获取tools设置目录,用于模拟器快照,ddms配置等等。你可以通过这个url获取相关代码 https://android.googlesource.com/platform/tools/base/+/master/common/src/main/java/com/android/prefs/AndroidLocation.java)
Search Locations 搜索本地 The location of the .android directory is typically the home directory; lint will search in $ANDROID_SDK_HOME, in ${user.home} (the Java property), and in $HOME. Just to clarify: Lint searches, in order, for the first of these locations that exists:
本地.android 目录通常就是home目录;lint会在 $ANDROID_SDK_HOME ,${user.home} ,$HOME.目录中寻找。需要说明的是,Lint 会按顺序搜索,并返回存在的第一个路径地址:
• ANDROID_SDK_HOME (system prop or environment variable) • user.home (system prop) • HOME (environment variable)
So, if ANDROID_SDK_HOME exists then user.home and HOME will be ignored! This may seem counter intuitive, but the reason for this is that ANDROID_SDK_HOME is not intended to be pointed to your SDK installation; the environment variable for that is ANDROID_SDK. More recently the code also looks for ANDROID_HOME. So, ANDROID_SDK_HOME is really meant to be an alternative home directory location, used on build servers when you want to point to specific emulator AVDs etc for unit testing. This naming is very unfortunate, and has led to various bugs, but it's hard to change without breaking users already relying on the past documented behavior. For now, make sure you don't use the environment variable ANDROID_SDK_HOME to point to your SDK installation!
因此,如果ANDROID_SDK_HOME存在,则user.home和HOME将被忽略。这似乎有违直觉,原因在于 ANDROID_SDK_HOME 默认不再指向你的SDK安装目录;环境变量指定的是ANDROID_SDK.最近的改动使代码也在ANDROID_HOME中寻找。因此,ANDROID_SDK_HOME 真正成为了一个home目录路径的替代,当你想要指定一个模拟器做单元测试时用于build servers。这个命名很失败,会产生很多的bug,但由于用户依赖过去文档的行为,这个东西很难改变。从现在开始,确保你不在使用ANDROID_SDK_HOME作为你SDK安装目录的环境变量
Following that, it looks for the sub-path /.android/lint/, i.e. 按照规则,寻找以下路径的/.android/lint/子目录
whichever one of these corresponds to the one and only location just found: 定位匹配的路径
• ANDROID_SDK_HOME/.android/lint/ • user.home/.android/lint/ • HOME/.android/lint/ and loads any JAR files it finds there. 加载发现的任何jar Consequently, if you have Lint jar files in ~/.android/lint/ and ANDROID_SDK_HOME exists, your JAR files will not be read! If you have Lint JAR files in ~/.android/lint/ and ANDROID_SDK_HOME does not exist, your JAR files will be read. If ANDROID_SDK_HOME exists, put your Lint JAR files in ANDROID_SDK_HOME/.android/lint/
因此,如果你的Lint jar在~/.android/lint/下,并且存在ANDROID_SDK_HOME目录,你的jar会找不到 如果你的Lint jar在~/.android/lint/下,并且不存在ANDROID_SDK_HOME目录,你的jar会找到 如果存在ANDROID_SDK_HOME目录,你应该将你的jar放在ANDROID_SDK_HOME/.android/lint/目录下
Running the Custom Check 运行自定义检查 Finally, let's run lint to make sure it knows about our rule: 最后,让我们运行lint,并让lint知道我们的规则
$ lint --show MyId MyId ---- Summary: My summary of the issue
Priority: 6 / 10 Severity: Warning Category: Correctness
My longer explanation of the issue
If this does not work, troubleshoot it by making sure your jar file is in the right place, that it contains the right registration line in the manifest file, that the full package name and class name is correct, that it is instantiatable (public default constructor), and that it registers your issue.
如果无效,排查问题,你jar文件是否放在了合理的位置?你的manifest文件是否有正确的注册信息?全路径包名和类名是否拼写正确?是否初始化了(是否有默认公共构造器)?是否注册了你的issue
Finally, let's test the rule. Here's a sample layout file: 最后,让我们测试我们的规则,以下是一个实例布局文件:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.google.io.demo" android:layout_width="match_parent" android:layout_height="match_parent" >
<com.google.io.demo.MyCustomView android:layout_width="300dp" android:layout_height="300dp" android:background="#ccc" android:paddingBottom="40dp" android:paddingLeft="20dp" app:exampleDimension="24sp" app:exampleColor="#ff0000" app:exampleDrawable="@drawable/io_logo"
/>
</FrameLayout>
Now let's run lint on the project which contains the above layout (and with the --check MyId argument we'll only check for our issue): 让我们在包含上面布局的项目中运行lint(--check命令后加入MyId参数将只检查我们自己的issue)
$ lint --check MyId /demo/workspace/Demo
Scanning Demo: ............... res/layout/sample_my_custom_view.xml:20: Warning: Missing required attribute 'exampleString' [MyId] <com.google.io.demo.MyCustomView ^ 0 errors, 1 warnings
Using Jenkins / Other Build Servers 使用Jenkins或者其他build servers If you're running lint as part of a continuous build, instead of placing the custom lint rules in the home directory of the account used by the build server, you can also use the environment variable $ANDROID_LINT_JARS to point to a path of jar files (separated with File.pathSeparator, e.g. ; on Windows and : everywhere else) containing the lint rules to be used. For more info about using Jenkins+Lint, see https://wiki.jenkins-ci.org/display/JENKINS/Android+Lint+Plugin.
如果你想将lint作为连续build操作的一部分,你可以使用环境变量$ANDROID_LINT_JARS来制定规则的jar文件路径,而不是放入build server使用的home目录。更多关于使用Jenkins+Lint的信息请看 https://wiki.jenkins-ci.org/display/JENKINS/Android+Lint+Plugin.
More Complex Rules 更复杂的规则 The above check was extremely simple. A lot more is possible. A really useful resource to figure out how to do more is to look at the existing 100 or so issues. Look in the SDK git repository, at sdk/lint/, and in particular sdk/lint/libs/lint_checks/src/com/android/tools/lint/checks/.
上述的检查极其简单。更复杂的操作是可行的。想要知道如何做更多的检测的有效途径是看那些已经存在的100多个issue的源码。位于SDK git仓库sdk/lint/libs/lint_checks/src/com/android/tools/lint/checks/.
You should also read this document: Writing a Lint Check which contains more background information on how lint works, how the different detectors should implemented, etc.
你应该读读 Writing a Lint Check。其中包含了lint如何运行的更多的背景信息,如何实现不同的detector,等等
Contributing Rules 开源规则 The custom lint rule support in lint is primarily intended for writing truly local rules. If you've thought of some lint check of general interest, you can use the above approach to develop and check the rule. However, please consider contributing the rule to AOSP such that all Android developers can benefit from the check! See the Contributing page for details.
自定义lint规则主要是为了编写适合本地情况的规则。如果你对一些lint 检查感兴趣,你可以使用上面介绍的内容开发和测试自己的规则。然后考虑将规则贡献给AOSP(Android Open Source Project),以便所有的android开发都能从中获益!查看Contributing界面获取更多信息。
Sources 源码 For Gradle/Android Studio/IntelliJ, use this sample project, and for Eclipse, use this .zip of the above custom lint rules project and copy lint_api.jar into the root of the project before opening it in Eclipse. 使用Gradle/Android Studio/IntelliJ环境的,请使用 this sample project。使用Eclipse的,使用this .zip并在打开前将lint_api.jar导入项目根目录。
More Info 更多信息 If you have a Google+ account, you can comment or ask questions on this post here: https://plus.google.com/u/0/116539451797396019960/posts/iyH3ER3LJF7 You can also write to adt-dev (the Google Group) for more info.
如果你有Google+账号,你可以在https://plus.google.com/u/0/116539451797396019960/posts/iyH3ER3LJF7评论和提问。你也可以联系adt-dev(google小组)寻求更多信息
|