butterknife-gradle-plugin插件

 在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

FinalRClassBuilder.java

用于根据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; }
相关文章
相关标签/搜索