美文网首页三方架构分析
Android 自定义Lint实践总结

Android 自定义Lint实践总结

作者: Zurich37度 | 来源:发表于2018-02-26 13:38 被阅读847次

    自定义Lint

    Android Lint 是由 Android SDK 提供的一种静态代码检测工具,用于检测 Android 项目的代码质量,帮你查出可能发生的bug以及可以优化的代码
    此文档只针对
    AS 3.0+
    Gradle 3.3+
    Android Plugin Version 3.0.1+

    本文参考以下项目
    googlesamples的android-custom-lint-rules库
    GavinCT(美团)的MeituanLintDemo

    LintOptions配置

    如果将 abortOnError 设置为true,自定义Lint很容易出现编译失败,此时检查message窗口将不必要的检查进行忽略禁用十分必要,例如 disable 'MissingTranslation' 此配置将忽略没有对strings.xml进行翻译报的错误。其他配置项查看下面各注释👇

    各个配置含义

    android {
    lintOptions {
        // 设置为 true时lint将不报告分析的进度
        quiet true
        // 如果为 true,则当lint发现错误时停止 gradle构建
        abortOnError false
        // 如果为 true,则只报告错误
        ignoreWarnings true
        // 如果为 true,则当有错误时会显示文件的全路径或绝对路径 (默认情况下为true)
        //absolutePaths true
        // 如果为 true,则检查所有的问题,包括默认不检查问题
        checkAllWarnings true
        // 如果为 true,则将所有警告视为错误
        warningsAsErrors true
        // 不检查给定的问题id
        disable 'TypographyFractions','TypographyQuotes'
        // 检查给定的问题 id
        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
        // * 仅 * 检查给定的问题 id
        check 'NewApi', 'InlinedApi'
        // 如果为true,则在错误报告的输出中不包括源代码行
        noLines true
        // 如果为 true,则对一个错误的问题显示它所在的所有地方,而不会截短列表,等等。
        showAll true
        // 重置 lint 配置(使用默认的严重性等设置)。
        lintConfig file("default-lint.xml")
        // 如果为 true,生成一个问题的纯文本报告(默认为false)
        textReport true
        // 配置写入输出结果的位置;它可以是一个文件或 “stdout”(标准输出)
        textOutput 'stdout'
        // 如果为真,会生成一个XML报告,以给Jenkins之类的使用
        xmlReport false
        // 用于写入报告的文件(如果不指定,默认为lint-results.xml)
        xmlOutput file("lint-report.xml")
        // 如果为真,会生成一个HTML报告(包括问题的解释,存在此问题的源码,等等)
        htmlReport true
        // 写入报告的路径,它是可选的(默认为构建目录下的 lint-results.html )
        htmlOutput file("lint-report.html")
        // 设置为 true, 将使所有release 构建都以issus的严重性级别为fatal(severity=false)的设置来运行lint
        // 并且,如果发现了致命(fatal)的问题,将会中止构建(由上面提到的 abortOnError 控制)
        checkReleaseBuilds true
        // 设置给定问题的严重级别(severity)为fatal (这意味着他们将会
        // 在release构建的期间检查 (即使 lint 要检查的问题没有包含在代码中)
        fatal 'NewApi', 'InlineApi'
        // 设置给定问题的严重级别为error
        error 'Wakelock', 'TextViewEdits'
        // 设置给定问题的严重级别为warning
        warning 'ResourceAsColor'
        // 设置给定问题的严重级别(severity)为ignore (和不检查这个问题一样)
        ignore 'TypographyQuotes'
        }
    }
    

    此部分内容粘贴自简书LintOptions
    此外,还可以通过 lint.xml 文件进行lint规则的配置,如果项目工程中没有此文件自行创建在项目module根目录下即可,具体配置参考:

    <?xml version="1.0" encoding="UTF-8"?>
    <lint>
        <!-- Ignore the ObsoleteLayoutParam issue in the given files -->
        <issue id="ObsoleteLayoutParam">
            <ignore path="res/layout/activation.xml" />
            <ignore path="res/layout-xlarge/activation.xml" />
        </issue>
    
        <!-- Ignore the UselessLeaf issue in the given file -->
        <issue id="UselessLeaf">
            <ignore path="res/layout/main.xml" />
        </issue>
    
        <!-- Change the severity of hardcoded strings to "error" -->
        <issue id="HardcodedText" severity="error" />
    </lint>  
    

    自定义Lint方案

    针对不同的自定义lint方案,其中LinkedIn方案可行性最高,将lint.jar放入AAR包中,项目依赖AAR从而进行自定义lint开发,其中lint.jar只对当前项目工程有效。
    LinkedIn提供的解决方案原文

    项目详细配置

    创建java工程,配置Gradle

    提供lint.jar,自定义lint规则在此工程中进行

    apply plugin: 'java-library'
    
    def lintVersion = "26.0.0-beta5"
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compileOnly "com.android.tools.lint:lint-api:$lintVersion"
        compileOnly "com.android.tools.lint:lint-checks:$lintVersion"
        testCompile "junit:junit:4.12"
        testCompile "com.android.tools.lint:lint:$lintVersion"
    }
    
    jar {
       manifest {
            attributes("Lint-Registry-v2":"com.appchina.android.lint.core.AppChinaIssueRegistry")
        }
     }  
    

    创建AAR,配置Gradle

    apply plugin: 'com.android.library'
    ...
    dependencies {
        lintChecks project('lintCoreLibrary') //lintCoreLibrary为上面的java工程名
    }
    ...  
    

    此处 lint 还不会生效,需要将 aar 包引用到主项目,两种方式:
    1.手动编译出 .aar 集成至主项目,即放至 libs 目录并进行 aar 依赖
    2.还上传至 JCenter,进行远程依赖,更新更为方便

    创建Detector

    下面是系统ToastUtilsDetector的源码

    public class ToastUtilsDetector extends Detector implements Detector.UastScanner {
    
    private static final Class<? extends Detector> DETECTOR_CLASS = ToastUtilsDetector.class;
    private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;
    private static final Implementation IMPLEMENTATION = new Implementation(
            DETECTOR_CLASS,
            DETECTOR_SCOPE
    );
    
    private static final String ISSUE_ID = "ToastUseError";
    private static final String ISSUE_DESCRIPTION = "You should use our{ToastUtils}";
    private static final String ISSUE_EXPLANATION = "You should NOT use android.widget.Toast directly. Instead you should use ToastUtils we offered.";
    private static final Category ISSUE_CATEGORY = Category.CORRECTNESS;
    private static final int ISSUE_PRIORITY = 9;
    private static final Severity ISSUE_SEVERITY = Severity.ERROR;
    private static final String CHECK_PACKAGE = "android.widget.Toast";
    
    public static final Issue ISSUE = Issue.create(
            ISSUE_ID,
            ISSUE_DESCRIPTION,
            ISSUE_EXPLANATION,
            ISSUE_CATEGORY,
            ISSUE_PRIORITY,
            ISSUE_SEVERITY,
            IMPLEMENTATION
    );
    
    @Override
    public List<String> getApplicableMethodNames() {
        return Arrays.asList("makeText", "show");
    }
    
    @Override
    public void visitMethod(@NonNull JavaContext context, @NonNull UCallExpression node, @NonNull PsiMethod method) {
        if (!context.getEvaluator().isMemberInClass(method, CHECK_PACKAGE)) {
            return;
        }
    
        List<UExpression> args = node.getValueArguments();
        UExpression duration = null;
        if (args.size() == 3) {
            duration = args.get(2);
        }
        LintFix fix = null;
        if (duration != null) {
            String replace;
            if ("Toast.LENGTH_LONG".equals(duration.toString())) {
                replace = "ToastUtils.showLong(" + args.get(0).toString() + ", " + args.get(1).toString() + ");";
            } else {
                replace = "ToastUtils.showShort(" + args.get(0).toString() + ", " + args.get(1).toString() + ");";
            }
            fix = fix().name("Replace with ToastUtils")
                    .replace()
                    .with(replace)
                    .build();
        }
        if (fix != null) {
            context.report(ISSUE, node, context.getLocation(node), ISSUE_DESCRIPTION, fix);
        }
    }   
    }  
    

    LintFix 提供快捷修复错误,如上面代码中快捷替换错误代码片段;支持使用正则表达式,具体api可查看LintFix源码,使用比较简单;
    如果不需要快捷修复,可以使用JavaContext.report()的其他方法,此时只会进行代码标注(红(Error) / 黄(Waring))提示,Alt+Enter并不会出现快捷修复的提示

    自定义Detector需要继承自Detector并实现 Detector.UastScanner 接口,25.2.0及之前版本的Detector.JavaPsiScanner已被弃用,UastScanner相比于JavaPsiScanner以及更老的JavaScanner,主要提供了对Kotlin支持,API更加简单,特点是成对存在(满足条件 -> visitor)此外可以lint-checks-version.jar中的各类型Detector源码可以学习其用法。

    UastScanner包含13个回调方法,下面介绍常用的几个:
    1.getApplicableUastTypes

    此方法返回需要检查的AST节点的类型,类型匹配的UElement将会被createUastHandler(createJavaVisitor)创建的UElementHandler(Visitor)检查。

    2.createUastHandler

    创建一个UastHandler来检查需要检查的UElement,对应于getApplicableUastTypes

    3.getApplicableMethodNames

    返回你所需要检查的方法名称列表,或者返回null,相匹配的方法将通过visitMethod方法被检查

    4.visitMethod

    检查与getApplicableMethodNames相匹配的方法

    5.getApplicableConstructorTypes

    返回需要检查的构造函数类型列表,类型匹配的方法将通过visitConstructor被检查

    6.visitConstructor

    检查与getApplicableConstructorTypes相匹配的构造方法

    7.getApplicableReferenceNames

    返回需要检查的引用路径名,匹配的引用将通过visitReference被检查

    8.visitReference

    检查与getApplicableReferenceNames匹配的引用

    9.appliesToResourceRefs

    返回需要检查的资源引用,匹配的引用将通过visitResourceReference被检查

    10.visitResourceReference

    检查与appliesToResourceRefs匹配的资源引用

    11.applicableSuperClasses

    返回需要检查的父类名列表,此处需要类的全路径名

    11.visitClass

    检查applicableSuperClasses返回的类

    ISSUE

    ISSUE在每个Detector中定义,lint检查到相关项将ISSUE报告出来,示例:

    public static final Issue ISSUE = Issue.create(
            "ListView",
            "AppChinaLint:Replace 'ListView' with 'RecyclerView'",
            "RecyclerView is better than ListView",
            Category.CORRECTNESS, 6, Severity.WARNING,
            new Implementation(ListViewDetector.class, Scope.JAVA_FILE_SCOPE));
    

    在此处可自定义错误描述、安全级别(WARNING、ERROR等)

    Category

    Category表示lint结果在IDE中的分类,系统已有类别:
    Lint
    Correctness (incl. Messages)
    Security
    Performance
    Usability (incl. Icons, Typography)
    Accessibility
    Internationalization
    Bi-directional text
    此外还可以自定义Category,示例:

    public class MTCategory {
        public static final Category NAMING_CONVENTION = Category.create("命名规范", 101);
    }
    
    IssueRegistry

    提供需要被检测的Issue列表,示例:

    @Override
    public synchronized List<Issue> getIssues() {
        return Arrays.asList(
                LogDetector.ISSUE,
                ListViewDetector.ISSUE,
                HashMapForJDK7Detector.ISSUE,
                ToastUtilsDetector.ISSUE,
                SampleCodeDetector.ISSUE,
                FindViewDetector.ISSUE,
                BaseActivityDetector.ISSUE,
                BaseFragmentDetector.ISSUE
        );
    }

    相关文章

      网友评论

        本文标题:Android 自定义Lint实践总结

        本文链接:https://www.haomeiwen.com/subject/rzcyxftx.html