butterknife-gradle-plugin插件

 在android library项目里由于R类中变量不再是final类型而无法使用butterknife,为了解决此问题,Jakewharton大神引入了butterknife-gradle-plugin插件,用于生成变量类型为final的R2类。

此处为butterknife-gradle-plugin 8.4.0版本为例,介绍一下插件在library中的使用以及源码分析。

使用

在项目的build.gradle中添加classpath:

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中引入插件:

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

 在library项目build.gradle中添加依赖:

implementation 'com.jakewharton:butterknife:8.4.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'

 源码分析

  插件主要有两个文件ButterKnifePlugin.groovy和FinalRClassBuilder.java

  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文件

FinalRClassBuilder.java

用于根据R.java文件生成R2.java文件

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文件写入到指定输出目录。

如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;
  }

 

猜你喜欢

转载自www.cnblogs.com/rainboy2010/p/9235645.html