如何在android lint命令行中输出命令行

Android代码优化——使用Android lint工具_Linux编程_Linux公社-Linux系统门户网站
你好,游客
Android代码优化——使用Android lint工具
来源:Linux社区&
作者:xyz_lmn
作为移动应用开发者,我们总希望发布的apk文件越小越好,不希望资源文件没有用到的图片资源也被打包进apk,不希望应用中使用了高于minSdk的api,也不希望Manifest文件存在异常,lint就能解决我们的这些问题。Android lint是在ADT 16提供的新工具,它是一个代码扫描工具,能够帮助我们识别代码结构存在的问题,主要包括:
1)布局性能(以前是 layoutopt工具,可以解决无用布局、嵌套太多、布局太多)
2)未使用到资源
3)不一致的数组大小
4)国际化问题(硬编码)
5)图标的问题(重复的图标,错误的大小)
6)可用性问题(如不指定的文本字段的输入型)
7)manifest文件的错误
Android lint可以解决如上的问题,当然还有更多,具体的可以参考Android Lint Checks。Android官方也总结了lint能解决的问题,如下图。
lint是命令工具,它已经完美的集成到了Eclipse中,我们可以方便的使用。通过lint,我们可以检测出每个问题的说明和问题的严重性,根据检测报告可以对程序作出改进。下面介绍下在Eclipse怎么使用lint。
lint的使用可以通过两个途径,Eclipse左上角的打钩的按钮或者选择项目-&右键-&Android Tools,如下图所示:
图一& & & & & & & & & & & & & & & & & & & & &
lint工具简单实用,自动化分析,分析完成会给我们分析报告:
分析包括中会包括错误和警告,会给出具体的描述、类别、位置。上图是一个错误的描述,下图给出警告描述。
Android lint是对android开发者很有帮助的一款工具,对于项目打包发布前优化代码、查找没用到的资源、查找错误等非常有帮助。作为开发者是必须掌握的工具之一,如果想了解更多可以参考。
更多Android相关信息见 专题页面
相关资讯 & & &
& (02/09/:03)
& (06/03/:51)
& (07/15/:14)
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款本人原文英文原文:http://tools..com/tips/lint&参照:http://blog.csdn.net/thl789/article/details/8037473一、简介Android Lint是SDK Tools 16 (ADT 16)之后才引入的工具,通过它对Android工程源进行扫描和检查,可发现潜在的问题,以便员及早修正这个问题。Android Lint提供了命令行方式执行,还可与IDE(如Eclipse)集成,并提供了html形式的输出报告。由于Android Lint在最初设计时就考虑到了independent于IDE,所以它可以很方便的与中的其他自动系统(/ Build / 测试等)集成.Android Lint主要用于检查以下这些错误:1、Missing translations (and unused translations)没有翻译的文本2、Layout permance problems (all the issues the old layoutopt tool used to find, and more)3、Unused resources未使用的冗余资源4、Inconsistent array sizes (when arrays are defined in multiple configurations)在多个中的数组大小不一致文件5、Accessibility and internationalization problems (hardcoded strings, missing contentDescription, etc)6、Icon problems (like missing densities, duplicate icons, wrong sizes, etc)7、Usability problems (like not specifying an input type>Android Lint远远不至检查以上的错误,更多的内容请参考《Android Lint 检查规则列表》在Eclipse中可以在菜单Window-&Preference-&“Lint Eerro checking”中设置规则的检查级别,如图1所示。检查级别可以是:DefaultFatalErrroWaringInformationIngore(即不检查)图1&如果你只是想对lint的检查规则做些简单的定制,请参考《Android Lint 检查规则的定制(基本篇)》或英文官方文档如果你想对lint的检查规则做些高级的定制,请参考官方文档&Writing New Lint Checks&and&Writing Custom Lint Rules.二、命令行中使用Lint2.1、基本使用There is a
line tool in the SDK tools/ directory called&lint.If you have the SDK&tools/&directory>lint”. Just point to a specific Android project directory. You can also point to a random directory, which (if it is not an Android project) will be searched recursively and all projects under that directory will be checked. (And you can also specify multiple projects separated by spaces)在Android SDK的tools下有个叫lint.bat的文件,它就是lint的命令行工具。lint命令后可以带一个或多个参数,参数之间用空格隔开,参数表示的是需要使用lint进行扫描的Android项目的目录。示例1命令行&lint /src/astrid/Scanning GreenDroid-APIs: ..Scanning stream: ...Scanning api: ...........................Scanning GDCatalog: .......................Scanning GreenDroid: ...........................................................Scanning tests: ...Scanning filters: ....Scanning tests: .....Scanning astrid: ....................................................................................................................................................Scanning simple: .......api/res/values-ca: Error: Locale ca is missing translations for: sync_SPr_bg_key, sync_SPr_forget_key, sync_SPr_interval_values, sync_SPr_logged_in_prefix... (2 more) [MissingTranslation]astrid/res/values-ca: Error: Locale ca is missing translations for: DLG_cancel, DLG_dismiss, DLG_ok, EPr_deactivated... (117 more) [MissingTranslation]api/res/values-cs: Error: Locale cs is missing translations for: sync_SPr_bgwifi_key, sync_SPr_forget_key, sync_SPr_interval_values, sync_SPr_logged_in_prefix... (2 more) [MissingTranslation](many lines omitted)43 errors, 466 warnings示例2Window命令行C:\Documents and Settings\Administrator&lint D:\workspace\TestScanning Test: ....................................................................................................................................................................................................................................................Scanning Test (Phase 2): ......res\layout\internet_image_demo.xml:9: Warning: The id &button1& is not referring to any views in this layout [UnknownIdInLayout]
android:layout_alignLeft=&@+id/button1&
^res\layout\internet_image_demo.xml:10: Warning: The id &textView1& is not referring to any views in this layout [UnknownIdInLayout]
android:layout_below=&@+id/textView1&
^AndroidManifest.xml:52: Warning: Exported receiver does not require permission [ExportedReceiver]
&receiver android:name=&.AlarmReceiver& &
^res\menu\activity_main.xml: Warning: The resource R.menu.activity_main appears to be unused [UnusedResources]res\drawable-hdpi\ic_action_search.png: Warning: The resource R.drawable.ic_action_search appears to be unused [UnusedResources]res\values\strings.xml:7: Warning: The resource R.string.hello appears to be unused [UnusedResources]&string name=&hello&&你好!&/string&^res\drawable-mdpi: Warning: Missing the following drawables in drawable-mdpi: icon.png, icon2.png [IconDensities]res\drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: icon.png, icon2.png [IconDensities]res\layout\internet_image_demo.xml:5: Warning: [Accessibility] Missing contentDescription attribute style="color:rgb(102,102,0)">[ContentDescription]
&ImageView
^res\layout\activity_main.xml:17: Warning: [I18N] Hardcoded string &go Hello&, should use @string resource [HardcodedText]
android:text=&go Hello&
^res\layout\activity_main.xml:23: Warning: [I18N] Hardcoded string &打印所有任务栈信息&, should use @string resource [HardcodedText]
android:text=&打印所有任务栈信息&
^res\layout\activity_main.xml:29: Warning: [I18N] Hardcoded string &打印所有服务信息&, should use @string resource [HardcodedText]
android:text=&打印所有服务信息&
^res\layout\activity_main.xml:35: Warning: [I18N] Hardcoded string &打印进程信息&, should use @string resource [HardcodedText]
android:text=&打印进程信息&
^res\layout\hello.xml:23: Warning: [I18N] Hardcoded string &please click me&, should use @string resource [HardcodedText]
android:text=&please click me&
^0 errors, 14 warnings2.2、Disabling Checks(--disable)在执行lint命令时可以通过--disable选项要指定关闭的检查规则项。--disable选项后接要关闭的检查规则项的id(比如示例3中的MissingTranslation)或检查规则项的类别(比如示例3中的Usability:Icons和示例4的Internationalization)。关于lint检查项的id和类别(Category)等信息请参考《Android Lint 检查规则列表》示例3$ lint --disable MissingTranslation,UnusedIds,Usability:Icons /src/astrid/示例4C:\Documents and Settings\Administrator&lint --disable Internationalization D:\workspace\TestScanning Test: ....................................................................................................................................................................................................................................................Scanning Test (Phase 2): ......res\layout\internet_image_demo.xml:9: Warning: The id &button1& is not referring to any views in this layout [UnknownIdInLayout]& & & & android:layout_alignLeft=&@+id/button1&& & & & ^res\layout\internet_image_demo.xml:10: Warning: The id &textView1& is not referring to any views in this layout [UnknownIdInLayout]& & & & android:layout_below=&@+id/textView1&& & & & ^AndroidManifest.xml:52: Warning: Exported receiver does not require permission [ExportedReceiver]& & & & &receiver android:name=&.AlarmReceiver& && & & & ^res\menu\activity_main.xml: Warning: The resource R.menu.activity_main appears to be unused [UnusedResources]res\drawable-hdpi\ic_action_search.png: Warning: The resource R.drawable.ic_action_search appears to be unused [UnusedResources]res\values\strings.xml:7: Warning: The resource R.string.hello appears to be unused [UnusedResources]&string name=&hello&&你好!&/string&^res\drawable-mdpi: Warning: Missing the following drawables in drawable-mdpi: icon.png, icon2.png [IconDensities]res\drawable-xhdpi: Warning: Missing the following drawables in drawable-xhdpi: icon.png, icon2.png [IconDensities]res\layout\internet_image_demo.xml:5: Warning: [Accessibility] Missing contentDescription attribute style="color:rgb(102,102,0)">[ContentDescription]& & &ImageView& & ^0 errors, 9 warnings2.3、enabling Checks(--enable和--check)lint的有些检查项默认是关闭的(disable),在执行lint命令时可以通过--enable选项开启它。-enable选项后接要开启的检查规则项的id(比如示例5中的MissingTranslation&)或检查规则项的类别(示例5中的Usability:Icons)示例5$ lint --disable MissingTranslation,UnusedIds,Usability:Icons /src/astrid/在执行lint命令时可以通过--check选项来指定只进行某些检查。-check选项后接要开启的检查规则项的id(比如示例6中的MissingPrefix)示例6$ lint --check MissingPrefix /src/astrid/2.4、检查项类别和检查项id可以通过lint的--list选项来得到检查项类别和检查项id.比如:$&lint&--listValid issue categories:CorrectnessSecurityPerformanceUsabilityUsability:IconsAccessibilityInternationalizationValid issue id's:&ContentDescription&: Ensures that image widgets provide a contentDescription&DuplicateIds&: Checks for duplicate ids within a single layout&StateListReachable&: Looks for unreachable states in a &selector&&InefficientWeight&: Looks for inefficient weight declarations in LinearLayouts&ScrollViewSize&: Checks that ScrollViews use wrap_content in scrolling dimension&MergeRootFrame&: Checks whether a
&FrameLayout& can be replaced with a &merge& tag...可以通过lint的--show选项后跟检查项id来得到一个检查项的详细说明.比如:$ lint --show MissingPrefixMissingPrefix-------------Summary: Detect XML attributes not using the Android namespacePriority: 8 / 10Severity: WarningCategory: CorrectnessMost Android views have attributes in the Android namespace. Whenreferencing these attributes you *must* include the namespace prefix,or your attribute will be interpreted by aapt as just a customattribute.当然你也可以通过《Android Lint 检查规则列表》来查阅检查项的id等详细信息2.4、html形式的report在lint中,我们可以通过--html选项接文件路径的形式把代码扫描结果以html文件的形式进行输出。示例6C:\Documents and Settings\Administrator&lint --html D:\workspace\Test\report.html D:\workspace\TestScanning Test: ....................................................................................................................................................................................................................................................Scanning Test (Phase 2): ......Wrote HTML report to D:\workspace\Test\report.htmlhtml输出报告如图2所示图2&By default, links to source files will just use local file:// path resources. You can remap the URLs to a different prefix with the --url option. For example:$ lint --html /tmp/report.html --url /src/MyProj=http://buildserver/src/MyProj2.5、命令行帮助在lint中,你可以使用--help选项来得到lint命令的一些帮助信息。示例7lint --help三、Eclispe中使用Lint从ADT16开始,lint就集成到了ADT中。该它在lint命令行的基础上新增了以下功能3.1、Automatic LintLint将在以下情况下自动运行:Configure Columns lets you edit which columns are visible. There are several new columns you can display, such as Category, Priority, etc, and you can click>Configure Columns用于设置在lint Window中对于检查出的issue的哪些项显示哪些项不显示,如图3-7所示图3-7&&The Edit Options actions brings up the Lint Preference dialog, which has also been improved. &You can now search through the options by filter:点击按钮会弹出处理Lint Preference dialog,你里面可以定制默认/全局的Android Lint的基本检查规则图3-8&&3.3、Quick FixesMany lint warnings have automatic fixes. For example, the various layoutopt fixes suggest replacements (e.g. replace wrap_content with 0dp).&3.4、Suppressing Errors(检查规则的基本定制)From the editor quick fix menu(如图3-9所示), you can also choose to图3-9&(If you do not see the lint fix action in the quickfix list, see the Known Bugs section)These choices are stored in a file named&lint.xml&in the project, which is also read by the command line tool. Thus, you can ignore warnings from the UI, and check in thelint.xml&file with your source projects, and others running lint will not see warnings you have ignored (presumably because they have been manually verified).你的选择在被存在Android工程目录下的&lint.xml&文件中关于此的详细内容请参考《Android Lint 检查规则的定制(基本篇)》结束!你的位置: >
> Android Lint工作原理剖析
Android Lint是Android SDK提供的一项静态代码分析工具,对于提高代码质量具有重要作用。到目前为止,Android SDK自带的Lint检查项目达到了253项,我们在开发过程中经常见到的提示信息比如“Id被重复定义”“HandlerLeak风险”其实都是由Lint检查实现的。
Android Studio 2.0 Stable版本已经于日正式发布。除了Instant Run让人眼前一亮,更让人惊喜的是,官方已经悄然把自定义Lint的检查与IDE整合起来了。在此之前,自定义Lint规则只能通过在终端中执行gradle任务来运行,然后生成报告文件。
Android Studio 2.0中整合自定义Lint检查的效果如图:
图中红线提示的错误是我自定义的Lint规则检查的结果,大意是Activity使用的布局文件应该以“activity_”为前缀进行命名。
关于Lint的一些基本知识,以及自定义Lint如何实现,可以参考我之前的文章:
从Android Studio的“偏好”设置窗口中,用户可以设置IDE整合的Lint检查功能的细节,如图:
虽然这个设置选项在旧版的Android Studio中也能看到,但实际上在旧版中是不起作用的。
除了在Editor中能够以红色下划线标注自定义检查项目外,使用Android Studio的 “Analyze” –& “Inspect Code”现在也会检查自定义Lint规则中定义的项目了。Android Studio 2.0的这一功能整合真是太棒了,大大提高了自定义Lint的实用性。
Lint工作流程探究
介绍完Android Studio 2.0的新特性,现在进入正题,我们来探究一下Lint检查的工作原理,包括系统默认的Lint检查项目以及用户自定义的Lint检查项目。
本文以终端运行gradle的lint任务为例进行分析。其中自定义lint的使用方式是把自定义lint以aar的形式提供给app进行引用,具体实现方式可以参考 。
当我们在终端执行“gradle lint”任务后,会加载com.android.build.gradle.tasks.Lint类,它的源码位于Lint.groovy文件中。
Lint类的lint()方法会首先被执行,这也是整个lint检查流程开始运转的起点。这部分代码如下:
@SuppressWarnings(&GroovyUnusedDeclaration&)
@TaskAction
public void lint() {
def modelProject = createAndroidProject(project)
if (getVariantName() != null && !getVariantName().isEmpty()) {
for (Variant variant : modelProject.getVariants()) {
if (variant.getName().equals(getVariantName())) {
lintSingleVariant(modelProject, variant);
lintAllVariants(modelProject);
这里会根据getVariantName()的执行结果,选择去调用lintSingleVariant()还是lintAllVariants()。而观察lintSingleVariant()和lintAllVariants()的源码,发现这两个方法最终都要调用runLint()方法,这个runLint()方法很重要,代码片段如下:
/** Runs lint on the given variant and returns the set of warnings */
private List&Warning& runLint(
@NonNull AndroidProject modelProject,
@NonNull Variant variant,
boolean report) {
IssueRegistry registry = createIssueRegistry()
LintCliFlags flags = new LintCliFlags()
LintGradleClient client = new LintGradleClient(registry, flags, project, modelProject, mSdkHome, variant, getBuildTools())
//..........这里省略部分代码.......
warnings = client.run(registry)
//.........
这个方法的第一句话是创建了一个IssueRegistry,而了解自定义Lint的用户一定对这个类不会陌生,在之前的文章中我们提到过,Android内建的Lint检查项目都是定义在BuiltinIssueRegistry类中,而BuiltinIssueRegistry就是派生自IssueRegistry,我们要实现的自定义Lint检查规则实际上也就是实现自定义的IssueRegistry子类。
IssueRegistry类的完整名称是com.android.tools.lint.client.api.IssueRegistry,它是一个Java类。自此,lint从一个gradle task开始与lint api包中的java类产生交互了。
createIssueRegistry()方法很简单,只有一句话:
private static BuiltinIssueRegistry createIssueRegistry() {
return new LintGradleIssueRegistry()
继续跟踪LintGradleIssueRegistry类:
public static class LintGradleIssueRegistry extends BuiltinIssueRegistry {
private boolean mI
public LintGradleIssueRegistry() {
public List&Issue& getIssues() {
List&Issue& issues = super.getIssues();
if (!mInitialized) {
mInitialized =
for (Issue issue : issues) {
if (issue.getImplementation().getDetectorClass() == GradleDetector.class) {
issue.setImplementation(GroovyGradleDetector.IMPLEMENTATION);
这里的BuiltinIssueRegistry我们刚才也提到了,用户平时在执行gradle lint时默认会执行200多项检查,这些默认检查项目都是Android SDK通过BuiltinIssueRegistry定义的。
继续执行上面的run()方法,new出来的LintGradleClient实际上是com.android.tools.lint.LintCliClient的子类,这个类的作用是提供执行lint任务的环境信息(比如控制台、IDE的信息),执行IssueRegistry中定义的各种ISSUE检查,以及以多种形式输出lint报告等。
继续执行run()方法,也就是warnings = client.run(registry)。看到这里终于知道BuiltinIssueRegistry中定义的200多项ISSUE是如何被gradle的lint任务引入检查了。
到这里为止,对groovy文件的分析就结束了,由于LintGradleClient是继承自java类LintCliClient,后续真正的lint检查工作都通过client.run(registry)这句话转交给java实现的LintCliClient类来完成。
读到这里有人会问,client.run(registry)中的参数registry是派生自BuiltinIssueRegistry,那么lint检查的项目也就是BuiltinIssueRegistry中定义的那200多项默认检查项目。那么我们自定义的lint规则中的ISSUE又是如何被引入lint检查的呢?不要急,下面会有分析。
LintCliClient类的run()方法的主要代码如下:
//LintCliClient.run()部分代码
public int run(@NonNull IssueRegistry registry, @NonNull List&File& files) throws IOException {
//............
mDriver = new LintDriver(registry, this);
//............
mDriver.analyze(createLintRequest(files));
Collections.sort(mWarnings);
boolean hasConsoleOutput =
for (Reporter reporter : mFlags.getReporters()) {
reporter.write(mErrorCount, mWarningCount, mWarnings);
if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
hasConsoleOutput =
if (!mFlags.isQuiet() && !hasConsoleOutput) {
System.out.println(String.format(
&Lint found %1$d errors and %2$d warnings&, mErrorCount, mWarningCount));
return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
不要被run()这个方法名迷惑了,以为LintCliClient是一个线程类。其实LintCliClient只是一个普通类,不是Runnable类,这里的方法也叫run()仅仅是一个巧合。
这里的run()方法中首先new了一个LintDriver对象,其实它才是真正用来对project和file进行lint分析的类,也就是通过mDriver.analyze()来进行lint分析。
LintDriver的analyze()方法精简后的代码如下:
//LintDriver.analyze()部分源码
private void analyze() {
//............
Collection&Project&
projects = mRequest.getProjects();
if (projects == null) {
projects = computeProjects(mRequest.getFiles());
//............
registerCustomDetectors(projects);
if (mScope == null) {
mScope = Scope.infer(projects);
fireEvent(EventType.STARTING, null);
for (Project project : projects) {
mPhase = 1;
Project main = mRequest.getMainProject(project);
// The set of available detectors varies between projects
computeDetectors(project);
if (mApplicableDetectors.isEmpty()) {
// No detectors enabled in this project: skip it
checkProject(project, main);
if (mCanceled) {
runExtraPhases(project, main);
fireEvent(mCanceled ? EventType.CANCELED : PLETED, null);
这里的projects变量中保存的就是等待进行Lint检查的工程项目,它是我们最开始在终端中执行gradle lint任务时指定的。比如在本例中,projects中保存的就是“Project [dir=/Users/netease/AndroidStudioProjects/HTLint/app]”这个项目。
继续往下执行,走到registerCustomDetectors(projects)这句话,看到这个方法名你是不是发现了什么?别急,我们先看看registerCustomDetectors()方法的源码:
private void registerCustomDetectors(Collection&Project& projects) {
// Look at the various projects, and if any of them provide a custom
// lint jar, &add& them (this will replace the issue registry with
// a CompositeIssueRegistry containing the original issue registry
// plus JarFileIssueRegistry instances for each lint jar
Set&File& jarFiles = Sets.newHashSet();
for (Project project : projects) {
jarFiles.addAll(mClient.findRuleJars(project));
for (Project library : project.getAllLibraries()) {
jarFiles.addAll(mClient.findRuleJars(library));
jarFiles.addAll(mClient.findGlobalRuleJars());
if (!jarFiles.isEmpty()) {
List&IssueRegistry& registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
registries.add(mRegistry);
for (File jarFile : jarFiles) {
IssueRegistry registry = JarFileIssueRegistry.get(mClient, jarFile);
if (myCustomIssues == null) {
myCustomIssues = Sets.newHashSet();
myCustomIssues.addAll(registry.getIssues());
registries.add(registry);
} catch (Throwable e) {
mClient.log(e, &Could not load custom rule jar file %1$s&, jarFile);
if (registries.size() & 1) { // the first item is mRegistry itself
mRegistry = new CompositeIssueRegistry(registries);
对于projects中的每一项project,都通过mClient.findRuleJars(project)方法来寻找该project中的RuleJars,那么findRuleJars()是如何实现的呢?它返回的RuleJars又是什么呢?
由于在LintDriver的构造函数中,mClient被初始化为一个LintClientWrapper对象,而LintClientWrapper类的findRuleJars()方法内部只有一句话:
return mDelegate.findRuleJars(project)
所以上面的mClient.findRuleJars(project)实际上是被委托给了LintGradleClient.java来实现。LintGradleClient类又在它的createLintRequest()方法中调用了LintGradleProject的静态方法create(),其中有这样一个片段:
public static Pair&LintGradleProject, List&File&& create(
@NonNull LintGradleClient client,
@NonNull AndroidProject project,
@NonNull Variant variant,
@NonNull org.gradle.api.Project gradleProject) {
//............
List&File& customRules = Lists.newArrayList();
File appLintJar = new File(gradleProject.getBuildDir(), &lint& + separatorChar + &lint.jar&);
if (appLintJar.exists()) {
customRules.add(appLintJar);
//............
这段代码会寻找当前项目的构建目录下是否引用了一个名为lint.jar文件,如果有就把它加入customRules列表中。
我们在《浅谈Android自定义Lint规则的实现》中提到过通过aar包装lint.jar文件,然后让需要自定义lint检查的android项目添加对aar的依赖,这也是本文的例子使用的引入自定义lint规则的方法。原来我们添加的依赖中的lint.jar文件是在这里被找出来的。
继续回去看registerCustomDetectors()方法后续的代码执行,也就是这一段:
for (Project library : project.getAllLibraries()) {
jarFiles.addAll(mClient.findRuleJars(library));
这段代码会对当前工程依赖的所有库文件进行检查,如果这些库文件有对名为lint.jar文件的引用,则把它们引用的lint.jar文件也加入到jarFiles集合中。
如此一来,不管是项目直接依赖的lint.jar文件,还是间接通过其他库引入的lint.jar文件,就都被放入jarFiles集合中了。
继续往下执行registerCustomDetectors()中的代码,走到了:
jarFiles.addAll(mClient.findGlobalRuleJars());
这里的findGlobalRuleJars()方法实际是由LintClient实现的:
//com.android.tools.lint.client.api.LintClient类
public List&File& findGlobalRuleJars() {
// Look for additional detectors registered by the user, via
// (1) an environment variable (useful for build servers etc), and
// (2) via jar files in the .android/lint directory
List&File& files =
String androidHome = AndroidLocation.getFolder();
File lint = new File(androidHome + File.separator + &lint&); //$NON-NLS-1$
if (lint.exists()) {
File[] list = lint.listFiles();
if (list != null) {
for (File jarFile : list) {
if (endsWith(jarFile.getName(), DOT_JAR)) {
if (files == null) {
files = new ArrayList&File&();
files.add(jarFile);
} catch (AndroidLocation.AndroidLocationException e) {
// Ignore -- no android dir, so no rules to load.
String lintClassPath = System.getenv(&ANDROID_LINT_JARS&); //$NON-NLS-1$
if (lintClassPath != null && !lintClassPath.isEmpty()) {
String[] paths = lintClassPath.split(File.pathSeparator);
for (String path : paths) {
File jarFile = new File(path);
if (jarFile.exists()) {
if (files == null) {
files = new ArrayList&File&();
} else if (files.contains(jarFile)) {
files.add(jarFile);
return files != null ? files : Collections.&File&emptyList();
这段代码逻辑也很简单,就是在环境变量指定的路径和“.android/lint”路径下分别寻找是否存在自定义lint规则的jar文件。如果有,就把它们返回并加入jarFiles集合中。
现在,不管是通过引入依赖库的方式,还是在系统指定路径或环境变量指定路径下放置lint.jar的方式(这2种方式在《浅谈Android自定义Lint规则的实现》中都有介绍)引入的lint.jar文件都已经被找出来放到jarFiles集合中了。
继续往下执行registerCustomDetectors()中的代码,这一行:
registries.add(mRegistry);
会把最开始生成的LintGradleIssueRegistry(实际就是系统默认的Lint检查项目BuiltinIssueRegistry的子类)缓存到列表registries中。
然后紧接着的for循环会针对jarFiles中的每一项指定lint规则的jarFile,获取jarFile中包含的IssueRegistry,把这些IssueRegistry也都缓存到列表registries中,并把IssueRegistry中包含的所有ISSUE都缓存到集合myCustomIssues中。也就是这段代码(再贴一遍):
if (!jarFiles.isEmpty()) {
//............
for (File jarFile : jarFiles) {
//............
myCustomIssues.addAll(registry.getIssues());
registries.add(registry);
if (registries.size() & 1) { // the first item is mRegistry itself
mRegistry = new CompositeIssueRegistry(registries);
然后通过创建一个CompositeIssueRegistry对象,把所有lint检查的IssueRegistry对象(不论是系统默认的检查项目还是用户实现的自定义检查项目)都包装到CompositeIssueRegistry中。这样,在后面真正进行ISSUE检查工作时,就可以直接使用CompositeIssueRegistry对象中返回的ISSUE列表了,因为它包含了系统自带的和用户自定义的所有ISSUE。
到了这里,registerCustomDetectors(projects)方法就执行完了(你不会忘了我们其实是因为跟踪LintDriver的analyze()方法所以才会有上面这么多balabala吧o(╯□╰)o ),让我们继续回到LintDriver的analyze()方法中往下看,也就是这一段:
//LintDriver.analyze()部分源码
if (mScope == null) {
mScope = Scope.infer(projects);
fireEvent(EventType.STARTING, null);
for (Project project : projects) {
mPhase = 1;
Project main = mRequest.getMainProject(project);
// The set of available detectors varies between projects
computeDetectors(project);
if (mApplicableDetectors.isEmpty()) {
// No detectors enabled in this project: skip it
checkProject(project, main);
if (mCanceled) {
runExtraPhases(project, main);
fireEvent(mCanceled ? EventType.CANCELED : PLETED, null);
首先会在mScope字段中缓存当前要做Lint检查的工程都需要对哪些Scope进行检查,比如需不需要检查Java源代码(Scope.JAVA_FILE)、Java字节码(Scope.CLASS_FILE)、资源文件(Scope.RESOURCE_FILE)等等。
fireEvent(EventType.STARTING, null)会回调所有已经注册过的LintListener,通知它们Lint检查开始了。LintListener是一个interface,可以对Lint检查的各个阶段进行响应。
接着一个for循环分别对projects中的每个project进行检查,由于每个project对lint的配置都不同,比如用户通过配置当前project目录下的lint.xml文件关闭了某些检查项目,或者更改了某些ISSUE的严重等级等。所以这里使用了computeDetectors(project)来获取当前检查的project的lint配置信息:
private void computeDetectors(@NonNull Project project) {
//...................
Configuration configuration = project.getConfiguration(this);
mScopeDetectors = new EnumMap&Scope, List&Detector&&(Scope.class);
mApplicableDetectors = mRegistry.createDetectors(mClient, configuration,
mScope, mScopeDetectors);
validateScopeList();
这里获取到的configuration中包含了当前正接受lint检查的project的基本信息,以及lint属性配置文件的信息,debug截图如下:
然后这个configuration中的信息就被作为参数传给了mRegistry.createDetectors()方法,来获知需要使用哪些Detector来检查当前project,而这里的mRegistry对象其实是一个CompositeIssueRegistry对象,也就是把android sdk自带的lint检查项目和用户自定义实现的lint检查项目都包含在内了。这里的createDetectors()是没有被CompositeIssueRegistry重写,直接继承父类IssueRegistry的方法,主要代码如下:
final List&? extends Detector& createDetectors(
@NonNull LintClient client,
@NonNull Configuration configuration,
@NonNull EnumSet&Scope& scope,
@Nullable Map&Scope, List&Detector&& scopeToDetectors) {
List&Issue& issues = getIssuesForScope(scope);
//.................
Set&Class&? extends Detector&& detectorClasses = new HashSet&Class&? extends Detector&&();
for (Issue issue : issues) {
Implementation implementation = issue.getImplementation();
Class&? extends Detector& detectorClass = implementation.getDetectorClass();
EnumSet&Scope& issueScope = implementation.getScope();
if (!detectorClasses.contains(detectorClass)) {
if (!configuration.isEnabled(issue)) {
//.................
detectorClasses.add(detectorClass);
List&Detector& detectors = new ArrayList&Detector&(detectorClasses.size());
for (Class&? extends Detector& clz : detectorClasses) {
Detector detector = clz.newInstance();
detectors.add(detector);
//................
逻辑很简单,先获取CompositeIssueRegistry对象中所有的ISSUE,也就是默认的200多项检查加上用户自己实现的检查项目,然后分别对这些ISSUE进行判断:如果集合detectorClasses中还没有包含当前ISSUE对应的lint探测器实现类detectorClass,并且当前project的配置文件没有禁用这个issue,那么就把探测器实现类detectorClass加入集合detectorClasses中。当所有issue都通过这个循环检查完毕后,把这些测器实现类都实例化成对象detector,加入列表detectors,最后把detectors返回给调用者,这样上一级调用者就获得了当前project可以用的所有Detector的实例了。
回到上一级调用者,继续往下执行LintDriver.analyze()剩下的代码,终于完成了所有的前期准备工作,来到了checkProject(project, main)这一句,这个方法才是真正使用前面所有工作提供的信息,开始正式对project的文件、字节码等进行lint检查了。而checkProject()方法中又调用了一个最核心的方法runFileDetectors()来进行lint检查工作,大致结构如下:
//LintDriver.runFileDetectors()部分源码
private void runFileDetectors(@NonNull Project project, @Nullable Project main) {
// Look up manifest information (but not for library projects)
if (project.isAndroidProject()) {
for (File manifestFile : project.getManifestFiles()) {
//.................
fireEvent(EventType.SCANNING_FILE, context);
v.visitFile(context, manifestFile);
if (mScope.contains(Scope.ALL_RESOURCE_FILES)
|| mScope.contains(Scope.RESOURCE_FILE)
|| mScope.contains(Scope.RESOURCE_FOLDER)
|| mScope.contains(Scope.BINARY_RESOURCE_FILE)) {
if (......) {
checkIndividualResources(project, main, xmlDetectors, dirChecks,
binaryChecks, files);
checkResFolder(project, main, res, xmlDetectors, dirChecks,
binaryChecks);
//.................
if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) {
if (......) {
checkIndividualJavaFiles(project, main, checks, files);
//.................
checkJava(project, main, sourceFolders, checks);
//.................
if (mScope.contains(Scope.CLASS_FILE)
|| mScope.contains(Scope.ALL_CLASS_FILES)
|| mScope.contains(Scope.JAVA_LIBRARIES)) {
checkClasses(project, main);
if (mScope.contains(Scope.GRADLE_FILE)) {
checkBuildScripts(project, main);
if (mScope.contains(Scope.OTHER)) {
List&Detector& checks = mScopeDetectors.get(Scope.OTHER);
if (checks != null) {
OtherFileVisitor visitor = new OtherFileVisitor(checks);
visitor.scan(this, project, main);
if (project == main && mScope.contains(Scope.PROGUARD_FILE) &&
project.isAndroidProject()) {
checkProGuard(project, main);
if (project == main && mScope.contains(Scope.PROPERTY_FILE)) {
checkProperties(project, main);
这段原始代码比较长,这里只截取了一个大致的框架。可以看到首先判断如果当前检查的是一个android项目,那么就检查它所有的Manifest文件,检查顺序为:
The manifests should be provided such that the main manifest comes first, then any flavor versions, then any build types.
然后再检查所有的资源文件和文件夹。
到了这里,专门针对android项目的检查就完成了,接下来就是对所有类型项目都要进行的检查了,这段代码框架的结构很清晰的列出了检查顺序为:java源文件 --& java字节码 --& GRADLE文件 --& 其他文件 --& ProGuard文件 --& PROPERTY文件。这与上关于lint检查顺序的描述是一致的:
OK,对所有project的所有issue都已经检查完成了,现在让我们回到LintCliClient.run()的执行(别怪我一下跳的太远,实在是计算机就是这样执行的啊o(╯□╰)o,代码再贴一遍…):
//LintCliClient.run()部分代码
public int run(@NonNull IssueRegistry registry, @NonNull List&File& files) throws IOException {
//............
mDriver = new LintDriver(registry, this);
//............
mDriver.analyze(createLintRequest(files));
Collections.sort(mWarnings);
boolean hasConsoleOutput =
for (Reporter reporter : mFlags.getReporters()) {
reporter.write(mErrorCount, mWarningCount, mWarnings);
if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
hasConsoleOutput =
if (!mFlags.isQuiet() && !hasConsoleOutput) {
System.out.println(String.format(&Lint found %1$d errors and %2$d warnings&, mErrorCount, mWarningCount));
return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
我们前面这么长的篇幅其实都是在分析这里的mDriver.analyze(createLintRequest(files))方法,它会把lint检查出来的警告和错误信息保存在列表mWarnings中,然后用这句Collections.sort(mWarnings)对所有警告进行排序。剩下的工作当然就是把这些警告信息输出啦,输出成为我们平常见到的html报告、或者控制台报告、或者其他形式。输出报告的工作是由这段代码完成的:
for (Reporter reporter : mFlags.getReporters()) {
reporter.write(mErrorCount, mWarningCount, mWarnings);
if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
hasConsoleOutput =
到此为止,Lint的工作流程就分析完了。
如何debug Lint源码
在终端执行gradle的lint任务默认是无法debug的,也就是说你在系统定义的200多项Issue或者你自定义的Issue中打的断点都不起作用。如果需要调试,可以用下面的方法:
1、在gradle.properties文件中加上下面这句话:
org.gradle.jvmargs='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005'
2、创建一个Remote类型的debug配置文件:
3、在终端中以daemon模式启动gradle lint任务
然后快速点击debug按钮(注意此时要切换到刚才创建的Debug Lint的配置文件上),如图:
现在Lint源码(包括Android SDK中的和用户自定义的Lint规则)中的断点就可以调试了。
发布时间:, 11:45:50
最后更新:, 16:05:11
转载请注明: &
与本文相关的文章}

我要回帖

更多关于 running android lint 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信