美文网首页
Android使用Lint自动检验代码

Android使用Lint自动检验代码

作者: 周_0717 | 来源:发表于2021-03-04 15:10 被阅读0次

项目中常常需要对代码的合规性、安全性等进行检查,Android Studio中自带的Lint工具可以满足大部分情况,其他情况则需要自定义的检验规则。

一、创建自定义Lint规则
1.1 新建一个Module,选择Java Library

apply plugin: 'java-library'

1.2 引入lint相关依赖

dependencies {
    compileOnly "com.android.tools.lint:lint-api:27.1.2"
    compileOnly "com.android.tools.lint:lint-checks:27.1.2"
}

1.3 创建自定义Detector文件

@SuppressWarnings("UnstableApiUsage")
public class LogDetector extends Detector implements Detector.UastScanner{

    public static final Issue ISSUE = Issue.create(
            "LogUsage",//id:唯一值,在配置lint屏蔽规则时会使用到
            "Avoid to use android.util.Log",//简短的描述
            "请使用统一的日志工具类!",//对问题的解释,需要注意汉字编码问题可能导致乱码
            Category.CORRECTNESS,//问题类别
            6,//优先级:1-10,数字越大级别越高(越严重)
            Severity.ERROR,//严重程度:Fatal, Error, Warning, Informational, Ignore
            new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE)//执行规制:包含探测器和探测范围2个参数
    );

    @Override
    public List<String> getApplicableMethodNames() {
        return Arrays.asList("v", "d", "i", "w", "e", "wtf");
    }

    @Override
    public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
        if (context.getEvaluator().isMemberInClass(method, "android.util.Log")) {
            context.report(ISSUE, node, context.getLocation(node), "避免调用android.util.Log");
        }
    }
}

1.4 创建自定义IssueRegistry文件,注册创建的自定义Detector

@SuppressWarnings("UnstableApiUsage")
public class MyIssueRegistry extends IssueRegistry {
    @Override
    @NotNull
    public List<Issue> getIssues() {
        return Arrays.asList(LogDetector.ISSUE);
    }

    @Override
    public int getApi() {
        return ApiKt.CURRENT_API;
    }
}

1.5 在gradle中声明入口文件

jar {
    manifest {
        attributes('Lint-Registry': 'com.zpf.lintrule.MyIssueRegistry')//自定义IssueRegistry
    }
}

二、打包成android依赖
步骤一生成的jar包无法直接使用,需要将其包装。
2.1 新建一个Module,选择Android Library,整个Module内不需要写代码

apply plugin: 'com.android.library'

2.2 添加步骤一生成的Module作为依赖

dependencies {
    lintPublish project(":lintrule")
}

2.3 将生成的aar上传Maven库
我使用的是bintray,生成的远端库为:com.zpf.android:tool-lint

三、通过git hook在push前触发检测
在项目的.git文件中的hooks文件夹内,默认有一系列以smaple后缀结尾的文件,去掉后缀文件脚本就会生效。

hooks smaple

我选择在pre-push进行lint检测,所以修改对应文件,添加命令执行gradle相关的task

project_path=$(dirname $(dirname $(dirname "$0")))
echo "project_path="$project_path
cd $project_path
os_type=$(uname -s)
if [[ $os_type =~ "MINGW" || $os_type =~ "Window" ]]; then
  os_type="Window"
elif [[ $os_type =~ "Darwin" ]]; then
  os_type="Mac"
elif [[ $os_type =~ "Linux" ]]; then
  os_type="Linux"
fi
echo "os_type="$os_type
if [[ "Window" == $os_type ]]; then
  cd ${project_path,0,2}
fi
#lintForArchon是自定义的lint检测task,可根据实际需要修改要执行的task
./gradlew lintForArchon
if [ $? -eq 0 ]; then
  echo "gradle task build successful"
  exit 0
else
  echo "gradle task build failed"
  cat <<\EOF
Error: Failed to through the lint test(task lintForArchon).
See lint-report/lint-report.html for more details.
EOF
  exit 1
fi

四、通过gradle插件实现自动配置

  1. 自动添加最新版本的自定义lint依赖库
  2. 统一配置lint规则
  3. 添加git hook,并执行lint检测

4.1 新建一个Module(选择哪个类型不重要),创建自定义gradle插件库

apply plugin: 'groovy'
apply plugin: 'maven'

dependencies {
    implementation gradleApi()
    implementation localGroovy()
    implementation 'com.android.tools.build:gradle:4.1.2'
}
sourceSets {
    main {
        //指定资源文件夹,不写这一行可能会导致getResourceAsStream为null
        resources.srcDirs = ['src/main/resources', 'src/main/java']
    }
}

4.2 在main文件夹在创建groovy文件夹,在此文件夹下创建包和文件.


目录

4.3 创建自定义Plugin


class MyLintPlugin implements Plugin<Project> {
    private static long lastCheck = 0L

    @Override
    void apply(Project project) {
        def androidVariants = getAndroidVariants(project)
        if (androidVariants != null) {
            applyTask(project, androidVariants)
        }
        if (System.currentTimeMillis() - lastCheck > 600000) {
            //检查git hook文件是否复制到了指定位置
            checkGitHookFile(project)
        }
    }

    private static DomainObjectSet<BaseVariant> getAndroidVariants(Project project) {
        //只有使用了com.android.library或com.android.application插件的工程lint规则才起作用
        def extension = project.extensions.findByName("android") as BaseExtension
        if (extension == null) {
            return null
        }
        if (extension instanceof LibraryExtension) {
            return (extension as LibraryExtension).libraryVariants
        } else if (extension instanceof AbstractAppExtension) {
            return (extension as AbstractAppExtension).applicationVariants
        } else {
            return null
        }
    }

    /**
     * 查找并复制pre-push文件
     */
    private static void checkGitHookFile(Project project) {
        println("search and copy pre-push")
        def rootFile = project.getRootDir()
        def gitFile = new File(rootFile, ".git")
        if (!gitFile.exists()) {
            gitFile = new File(rootFile.getParentFile(), ".git")
        }
        println(".git file exists=" + gitFile.exists())
        if (gitFile.exists()) {
            def prePushHookFile = new File(gitFile, "hooks/pre-push")
            if (!prePushHookFile.exists() || prePushHookFile.length() == 0) {
                println("start copy pre-push")
                try {
                    InputStream inputStream = MyLintPlugin.class.getResourceAsStream("/config/pre-push")
                    OutputStream outputStream = new FileOutputStream(prePushHookFile)
                    IOUtils.writeToFile(inputStream, outputStream)
                    println("copy pre-push success")
                    lastCheck = System.currentTimeMillis()
                } catch (Exception ignored) {
                    println("copy pre-push failed!")
                }
            } else {
                lastCheck = System.currentTimeMillis()
            }
        }
    }

    private void applyTask(Project project, DomainObjectSet<BaseVariant> variants) {
        project.dependencies {
            //添加最新版的自定义lint依赖
            implementation("com.zpf.android:tool-lint:latest.integration") {
                force = true
            }
        }
        project.configurations.all {
            //设置缓存有效时长
            resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
        }
        def archonTaskExists = false
        variants.all { variant ->
            def variantName = variant.name.capitalize()
            LintBaseTask lintTask = project.tasks.getByName("lint" + variantName) as LintBaseTask
            //通过lint.xml配置规则
            File lintFile = project.file("lint.xml")
            def lintOptions = lintTask.lintOptions
            lintOptions.lintConfig = lintFile
            lintOptions.abortOnError = true
            lintOptions.htmlReport = true
            lintOptions.htmlOutput = project.file("${project.projectDir}/lint-report/lint-report.html")
            lintOptions.xmlReport = false
            def hasCustomLint = lintFile.exists()
            lintTask.doFirst {
                hasCustomLint = lintFile.exists()
                println("hasCustomLint=" + hasCustomLint)
                //如果本地已有lint.xml配置文件则使用本地配置
                if (!hasCustomLint) {
                    try {
                        InputStream inputStream = MyLintPlugin.class.getResourceAsStream("/config/lint.xml")
                        OutputStream outputStream = new FileOutputStream(lintFile)
                        IOUtils.writeToFile(inputStream, outputStream)
                        println("copy /config/lint.xml success")
                    } catch (Exception ignored) {
                        println("copy /config/lint.xml failed")
                    }
                }
            }
            project.gradle.taskGraph.afterTask { task, TaskState state ->
                if (task == lintTask) {
                    //如果使用的插件中的规则,则检测结束后删除lint.xml
                    if (!hasCustomLint && lintFile != null && lintFile.exists()) {
                        lintFile.delete()
                    }
                }
            }
            if (!archonTaskExists) {
                //创建自定义task用于执行lint检测,此task会在gti hook中调用
                archonTaskExists = true
                println("create lintForArchon task")
                project.task("lintForArchon").dependsOn lintTask
            }
        }
    }
}

4.4 配置插件名称
创建文件 src\resources\META_INF\gradle-plugins\xxxx.properties,xxxx为设定的插件名称。
文件内容:

#等号后面即为前面创建的自定义Plugin
implementation-class = com.zpf.plugin.lint.MyLintPlugin

4.5 发布插件
为方便测试,有时需要先将插件发布到本地仓库,首先在Module下的build.gradle中添加发布命令和参数

uploadArchives {
    repositories.mavenDeployer {
        repository(url: uri('../repo'))//仓库相对地址
        pom.groupId = 'com.zpf.plugin'//插件名称
        pom.artifactId = 'android-check'//在需要引用插件时用到
        pom.version = '0.0.7'
    }
}

然后在主工程的build.gradle中添加仓库地址和,差价版本依赖

buildscript {
    repositories {
        google()
        maven { url uri("repo") }//本地仓库地址
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.2'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
        classpath 'com.zpf.plugin-check:0.0.7'//差价依赖及版本
    }
}

4.6 使用插件
在希望进行检测的Module下的build.gradle中添加:

//4.4中设置的插件名称
apply plugin: 'MyLintPlugin'

在执行push操作时将触发lint检测,配置了插件的Module在执行lint检测时将使用插件内配置。

github地址:https://github.com/FirstLetterZ/AndroidLint

参考:
【我的Android进阶之旅】Android自定义Lint实践
美团 Android自定义Lint实践

2021-03-04

相关文章

网友评论

      本文标题:Android使用Lint自动检验代码

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