项目中常常需要对代码的合规性、安全性等进行检查,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后缀结尾的文件,去掉后缀文件脚本就会生效。
![](https://img.haomeiwen.com/i11880671/8975d84576fa6870.png)
我选择在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插件实现自动配置
- 自动添加最新版本的自定义lint依赖库
- 统一配置lint规则
- 添加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文件夹,在此文件夹下创建包和文件.
![](https://img.haomeiwen.com/i11880671/0cf11b9afbd020de.png)
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
网友评论