一个快速生成R2.java中fields的插件

1,150 阅读1分钟

项目地址:github.com/JeasonWong/…

介绍

在子 module 中使用 ButterKnife 时,如果想使用 ButterKnife 提供的编译期注解,那么就得使用 ButterKnife 的 gradle 插件所生成的R2.java,比如 @BindView( R2.id.xxx ),关于更多 R.java 与 R2.java 的资料可以看我这篇文章 R.java、R2.java是时候懂了

当我们在子 module 中新增资源 id 时使用 R2.id.xxx 会报红,报红的原因是 R2.java 是依赖 R.java 生成的,必须重新 build project 生成全新的 R2.java,但这样耗时太久了,大点的工程基本需要四五分钟,报红又使强迫症实在看不下去,那么 R2Assistant 就是来解决这个问题的,使用这个插件可以快速生成 R2.java 中还不存在的 fileds,从而提高工作效率。

演示

r2assistant.gif

使用

在主工程的 build.gradle 中添加插件

apply plugin: 'me.wangyuwei.r2assistant'
buildscript {
    repositories {
        maven {
            url 'https://dl.bintray.com/wangyuwei/maven'
        }
    }
    dependencies {
        classpath 'me.wangyuwei:r2assistant-plugin:1.0.0'
    }
}

运行命令

如果你想对所有的子 module 生效,执行 ./gradlew sweepR2

如果你只想对指定的子 module 生效,执行 ./gradlew sweepR2 -PmoduleName=${subModuleName}

原理

原理其实很简单,基本利用正则表达式。

1、写出 @BindView( R2.id.xxx ) 的正则 R2\.id\.([\w]*

2、遍历 /src/main/java 下的所有 java 文件,并找出所有匹配 1 中正则的资源名:

File srcDir = new File(subProject.projectDir.path.toString() + "/src/main/java")
srcDir.eachFileRecurse(FileType.FILES) { File file ->
    if (file.toString().endsWith(".java")) {
        String fileContent = new String(file.bytes)
        Pattern p = Pattern.compile(FIELD_SRC_ID_REGEX)
        Matcher m = p.matcher(fileContent)
        while (m.find()) {
            srcFieldsSet.add(m.group(1))
        }
    }
}

3、写出子 module 对应的R2.java 中 id 的正则 ,如 @IdRes public static final int action_bar = 0x7f0a004f;,对应的正则是:@IdRes[\s]*public static final int ([\w]*) = *[\w]*;

4、找出子 module 对应的R2.java 中 符合 2 中正则的资源名:

File r2File = new File(subProject.buildDir.path.toString() + "/generated/source/r/debug/" + packageName.replaceAll("\\.", "/") + "/R2.java")
String r2Content = new String(r2File.bytes)
Pattern p = Pattern.compile(FIELD_R2_REGEX)
Matcher m = p.matcher(r2Content)
while (m.find()) {
    r2FieldsSet.add(m.group(1))
}

5、找出 /src/main/java 下的新增资源:

srcFieldsSet.each {
    if (!r2FieldsSet.contains(it)) {
        R2Log.log("add filed: ${it}")
        generateFieldsSet.add(it)
    }
}

6、在 R2.java 中生成新的 filed ,新增 filed 的值可以随便撸,反正运行时用不着:


def STR_CLASS_ID = '''public static final class id {'''
int index = r2Content.indexOf(STR_CLASS_ID)
StringBuilder sb = new StringBuilder()
sb.append(r2Content.substring(0, index + STR_CLASS_ID.length()))
generateFieldsSet.each {
    sb.append("\n\t@IdRes\n\tpublic static final int ${it} = 0x7f888888;\n")
}
sb.append(r2Content.substring(index + STR_CLASS_ID.length(), r2Content.length()))
r2File.delete()
r2File.withWriter(StandardCharsets.UTF_8.name()) { writer ->
    writer.write(sb.toString())
}

7、简单吧。

尾语

实现这个功能其实有很多方案,我的这种并不是最好的,我目前想的一个不错的方案是监听 xml 里的变化,如果有新增资源 id ,而这个 id 在 R2.java 中又不存在,那么自动添加这个 field,而不用现在这样执行一个task,感兴趣的同学可以做做。