在android library項目裏因爲R類中變量再也不是final類型而沒法使用butterknife,爲了解決此問題,Jakewharton大神引入了butterknife-gradle-plugin插件,用於生成變量類型爲final的R2類。java
此處爲butterknife-gradle-plugin 8.4.0版本爲例,介紹一下插件在library中的使用以及源碼分析。node
在項目的build.gradle中添加classpath:android
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
}
}
在library項目build.gradle中引入插件:
api
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
在library項目build.gradle中添加依賴:app
implementation 'com.jakewharton:butterknife:8.4.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
插件主要有兩個文件ButterKnifePlugin.groovy和FinalRClassBuilder.javaide
ButterKnifePlugin.groovy源碼分析
public class ButterKnifePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
if (!(project.plugins.hasPlugin(LibraryPlugin) || project.plugins.hasPlugin(AppPlugin))) {
throw new IllegalStateException('Butterknife plugin can only be applied to android projects')
}
def variants
if (project.plugins.hasPlugin(LibraryPlugin)) {
variants = project.android.libraryVariants
} else {
variants = project.android.applicationVariants
}
project.afterEvaluate {
variants.all { BaseVariant variant ->
variant.outputs.each { BaseVariantOutput output ->
output.processResources.doLast {
File rDir = new File(sourceOutputDir, packageForR.replaceAll('\\.',
StringEscapeUtils.escapeJava(File.separator)))
File R = new File(rDir, 'R.java')
FinalRClassBuilder.brewJava(R, sourceOutputDir, packageForR, 'R2')
}
}
}
}
}
}
在apply方法裏首先判斷項目是否存在LibraryPlugin(com.android.library)或AppPlugin(com.android.application),如不存在則拋出異常,而後獲取對應的variants,在項目的processResources階段,獲取R.java文件的信息,sourceOutputDir對應輸出目錄如app\build\generated\source\r\release,packageForR爲包名,如com.example.pengf.myapplication,則R.java的輸出位置爲app\build\generated\source\r\release\com\example\pengf\myapplication\R.java,而後調用FinalRClassBuilder類的brewJava方法生成R2.java文件gradle
用於根據R.java文件生成R2.java文件ui
public final class FinalRClassBuilder { private static final String SUPPORT_ANNOTATION_PACKAGE = "android.support.annotation"; private static final String[] SUPPORTED_TYPES = { "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string" }; private FinalRClassBuilder() { } public static void brewJava(File rFile, File outputDir, String packageName, String className) throws Exception { CompilationUnit compilationUnit = JavaParser.parse(rFile); TypeDeclaration resourceClass = compilationUnit.getTypes().get(0); TypeSpec.Builder result = TypeSpec.classBuilder(className).addModifiers(PUBLIC).addModifiers(FINAL); for (Node node : resourceClass.getChildrenNodes()) { if (node instanceof TypeDeclaration) { addResourceType(Arrays.asList(SUPPORTED_TYPES), result, (TypeDeclaration) node); } } JavaFile finalR = JavaFile.builder(packageName, result.build()) .addFileComment("Generated code from Butter Knife gradle plugin. Do not modify!") .build(); finalR.writeTo(outputDir); } private static void addResourceType(List<String> supportedTypes, TypeSpec.Builder result, TypeDeclaration node) { if (!supportedTypes.contains(node.getName())) { return; } String type = node.getName(); TypeSpec.Builder resourceType = TypeSpec.classBuilder(type).addModifiers(PUBLIC, STATIC, FINAL); for (BodyDeclaration field : node.getMembers()) { if (field instanceof FieldDeclaration) { addResourceField(resourceType, ((FieldDeclaration) field).getVariables().get(0), getSupportAnnotationClass(type)); } } result.addType(resourceType.build()); } private static void addResourceField(TypeSpec.Builder resourceType, VariableDeclarator variable, ClassName annotation) { String fieldName = variable.getId().getName(); String fieldValue = variable.getInit().toString(); FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(int.class, fieldName) .addModifiers(PUBLIC, STATIC, FINAL) .initializer(fieldValue); if (annotation != null) { fieldSpecBuilder.addAnnotation(annotation); } resourceType.addField(fieldSpecBuilder.build()); } private static ClassName getSupportAnnotationClass(String type) { return ClassName.get(SUPPORT_ANNOTATION_PACKAGE, capitalize(type) + "Res"); } private static String capitalize(String word) { return Character.toUpperCase(word.charAt(0)) + word.substring(1); } }
brewJava方法主要是調用javapoet生成R2.java文件,支持的類型有array, attr, bool, color, dimen, drawable, id, integer, string,首先經過JavaParser類對R.java文件進行轉換,而後依次讀入R.java中每一個節點,如該節點的類型爲支持的類型,則將該節點下面的每一個變量都寫入到R2.java中,變量前加入final關鍵字,值爲R.java中變量對應的值,同時爲每一個變量添加註解。最後將R2.java文件寫入到指定輸出目錄。google
如R.java裏有如下內容:
public static final class bool { public static int abc_action_bar_embed_tabs = 0x7f050001; public static int abc_allow_stacked_button_bar = 0x7f050002; public static int abc_config_actionMenuItemAllCaps = 0x7f050003; public static int abc_config_showMenuShortcutsWhenKeyboardPresent = 0x7f050004; }
則生成的R2.java文件以下,每個變量前都加入了final關鍵字,其值爲R.java中對應的值,還加入了android.support.annotation類中對應的註解
public static final class bool { @BoolRes public static final int abc_action_bar_embed_tabs = 0x7f050001; @BoolRes public static final int abc_allow_stacked_button_bar = 0x7f050002; @BoolRes public static final int abc_config_actionMenuItemAllCaps = 0x7f050003; @BoolRes public static final int abc_config_showMenuShortcutsWhenKeyboardPresent = 0x7f050004; }