javapoet实现的Butterknife的view注入功能

1.配置信息

1.1  创建一个java-library 用于存放 注解的类新型

[:annotion]

apply plugin: 'java-library'

然后创建一个用于绑定View的注解,指定为编译时注解类型,用于Filed的注入类型

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface PBindView {
    int value();
}

1.2 创建一个java-library 用于存放编译的实现接口,编译会实现此接口的方法,可以统一管理,方便后面的反射创建

[:arouterapi]
apply plugin: 'java-library'

创建一个管理的接口类

public interface IPBindPath {
     Map<String, Integer> getBingGroups(Class mClass);
}

通过传入的对象类型,获取注解声明的所有属性名称和相应的R.id

后面会通过javapoet封装一个Map<Class,Map<String,Integer>> map ,用于存放所有标记了注解的类 以及 该类所有标记的属性值;这里是定义一个方法,方便根据类获取属性并反射注入属性

1.3  创建一个java-library 用于操作自动生成的代码

[:compiler]

apply plugin: 'java-library'

dependencies {
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
    implementation "com.squareup:javapoet:1.9.0"
    implementation project(":annotion")
    implementation project(":arountapi")
}

1.4 在应用内引入

implementation project(":annotion")
annotationProcessor project(":compiler")
implementation project(":common")

2.自动生成代码书写

javapoet书写规范是逆序书写,就是格式按照我们平时创建类的反向思维

我们平时建一个类,是先选一个包名,然后类名,方法名,参数名这种方式

javapoet则是反过来,一层层包装后最终生成

同时注意常用的几种格式参数  $T 指class类型,$N引用类型  ,$S 指字符串类型  $L 指枚举类型或其他int等基本类型

2.1 配置

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.test.annotion.PBindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BindViewProcessor extends AbstractProcessor {
    ...
}

这里格式比较固定

@AutoService  绑定Processor用于工作的类,

@SupportedAnnotationTypes  监听PBindView注解类,或者说所有有PBindView注解的属性都会在这里集中回调

@SupportedSourceVersion  指定支持的版本,这里选的是版本7

    private Elements elementUtil;
    private Types typeUtil;
    private Filer filer;
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtil = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        typeUtil = processingEnvironment.getTypeUtils();
    }

这里有四个属性,列一下常用的功能介绍

elementUtil      对元素操作的工具,可以获取对应元素的属性

//获取元素的包名
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString()
//获取activity对应的类型
TypeMirror activityType = elementUtil.getTypeElement("android.app.Activity").asType()
//配合typeUtil进行类型判断,如判断选定的element是否是Activity的子类
typeUtil.isSubtype(element.asType(), activityType)

typeUtil   类型判断

filer         最终执行自动生成的工具,最终根据配置的代码生成相应的类,这个后面会用到

 JavaFile.builder(ProcessorConfig.getDefaultPackage(), typeSpec)
                    .build().writeTo(filer);

messager 日志输出类

messager.printMessage(Diagnostic.Kind.ERROR, "1111");

2.2 编写

核心方法都在process这里处理

2.2.1  列出所有标记了PBindView的元

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set == null || set.size() == 0) return false;

        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(PBindView.class);

        if (elements == null || elements.size() == 0) {
            throw new RuntimeException("未找到匹配的注解");
        }

        ......
}

 2.2.2  创建一个静态的成员变量map

 private static Map<Class, Map<String, Integer>> map = new HashMap();
TypeName pathName = ParameterizedTypeName.get(ClassName.get(Map.class), TypeName.get(String.class), TypeName.get(Integer.class));
TypeName groupName = ParameterizedTypeName.get(ClassName.get(Map.class), TypeName.get(Class.class), pathName);

        FieldSpec fieldSpec = FieldSpec.builder(groupName, "map")
                .addModifiers(Modifier.STATIC)
                .addModifiers(Modifier.PRIVATE)
                .initializer("new $T()", HashMap.class)
                .build();

泛型创建也是逆向思维,先创建一个Map<String,Integer>的类型,然后再在这个基础上创建一个Map<Class,Map>的类型

2.2.3 创建一个构造方法,里面调用另一init()方法

  MethodSpec conMethodSpc = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addStatement("init()")
                .build();

2.2.4 创建最上面接口的实现方法

@Override
public Map<String, Integer> getBingGroups(Class className) {
    return map.get(className);
  }
  MethodSpec methodSpec = MethodSpec.methodBuilder("getBingGroups")
                .addParameter(ParameterSpec.builder(TypeName.get(Class.class), "className").build())
                .addAnnotation(Override.class)
                .returns(pathName)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return $N.get($N)", "map", "className")
                .build();

2.2.5 定义一个往map里添加数据的方法

private void addToPath(Class className, String fieldName, Integer id) {
        Map<String, Integer> groupPath = map.get(className);
        if (groupPath == null) {
            groupPath = new HashMap();
            map.put(className, groupPath);
        }
        groupPath.put(fieldName, id);
    }
MethodSpec methodAdd = MethodSpec.methodBuilder("addToPath")
                .addParameter(ParameterSpec.builder(TypeName.get(Class.class), "className").build())
                .addParameter(ParameterSpec.builder(TypeName.get(String.class), "fieldName").build())
                .addParameter(ParameterSpec.builder(TypeName.get(Integer.class), "id").build())
                .addModifiers(Modifier.PRIVATE)
                .addStatement("$T $N = $N.get($N)", pathName, "groupPath", "map", "className")
                .addStatement("if($N == null){", "groupPath")
                .addStatement("$N = new $T()", "groupPath", HashMap.class)
                .addStatement("$N.put($N,$N)", "map", "className", "groupPath")
                .addStatement("}")
                .addStatement("$N.put($N,$N)", "groupPath", "fieldName", "id").build();

变量类型引用$N ,如果传入的字符串比如 groupPath,局部变量和成员变量都没声明,则会创建一个这个名字的变量,否则直接引用前面创建的变量

类引用 $T 引用的是类名称,不是完整的类文件;比如上面虽然写了$T()  -> HashMap.class ,但只会把HashMap这个字符串传到这个表达式里替换,如果要引用 .class类型则要自己补齐,比如$T.class这样

值得一提的是,内部类或者类部枚举等因为不能直接访问,表达式可以写成 $T.$T 和 $T.$L 前面是外部类,后面是内部属性,等价于  A.B 即A类的内部类B的表现形式

补上上面定义的init方法,在构造方法里调用

   public AROUTR_TOTEL_BIND_VIEW_CLASS() {
        init();
    }
MethodSpec.Builder initBuilder = MethodSpec.methodBuilder("init")
                .returns(TypeName.VOID)
                .addModifiers(Modifier.PRIVATE);

2.2.6 遍历有标记的注解参数,依次写入

 TypeMirror activityType = elementUtil.getTypeElement(ProcessorConfig.ACTIVITY_TYPE).asType();

        for (Element element : elements) {
            PBindView pBindView = element.getAnnotation(PBindView.class);
            int id = pBindView.value();
            //获取父节点
            Element parentElement = element.getEnclosingElement();
            if (typeUtil.isSubtype(parentElement.asType(), activityType)) {
                initBuilder.addStatement("addToPath($T.class,$S,$L)", parentElement.asType(), element.getSimpleName(), id);
            } else {
                throw new RuntimeException("暂不支持非ActivityViewBind");
            }
        }

getEncloseingElement是获取当期注解属性的父节点,这里就对应标记了该注解的Activity

这里偷懒了,最好按Element传入配置,方便后续获取,这里是直接获取属性名称,混淆会出问题

2.2.7 把所有的方法和变量全部装构建到类中

  TypeSpec typeSpec = TypeSpec.classBuilder("AROUTR_TOTEL_BIND_VIEW_CLASS")
                .addField(fieldSpec)
                .addMethod(conMethodSpc)
                .addMethod(methodAdd)
                .addMethod(initBuilder.build())
                .addMethod(methodSpec)    
                .addSuperinterface(TypeName.get(IPBindPath.class))  //添加实现的接口
                .addModifiers(Modifier.PUBLIC)
                .build();

2.2.8 执行生成文件的代码

  try {
            JavaFile.builder("com.test.test.arouter", typeSpec)
                    .build().writeTo(filer);
        } catch (Exception e) {
            e.printStackTrace();
        }

 

然后我在测试的两个Activity分别定义了一个view去引用注解,最终生成的类结构是

package com.test.test.arouter;

import com.arountapi.IPBindPath;
import com.example.testmodule.debug.A;
import com.example.testmodule.debug.TestModuleActivity;

import java.lang.Class;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.util.HashMap;
import java.util.Map;

public class AROUTR_TOTEL_BIND_VIEW_CLASS implements IPBindPath {
    private static Map<Class, Map<String, Integer>> map = new HashMap();

    public AROUTR_TOTEL_BIND_VIEW_CLASS() {
        init();
    }

    private void addToPath(Class className, String fieldName, Integer id) {
        Map<String, Integer> groupPath = map.get(className);
        if (groupPath == null) {
            groupPath = new HashMap();
            map.put(className, groupPath);
        }
        groupPath.put(fieldName, id);
    }

    private void init() {
        addToPath(A.class, "tvTextView", 2131165362);
        addToPath(TestModuleActivity.class, "imageView", 2131165286);
    }

    @Override
    public Map<String, Integer> getBingGroups(Class className) {
        return map.get(className);
    }
}

分别把属性名称和id都存入了map ,并把类对象作为key再包装成一个方便查询的大map

2.2.9 定义注入入口

public class BindUtils {

    public static void bind(Activity activity) {
        try {
            Class<?> mClass = Class.forName("com.test.test.arouter.AROUTR_TOTEL_BIND_VIEW_CLASS");
            IPBindPath ipBindPath = (IPBindPath) mClass.newInstance();
            invokeDatas(activity,ipBindPath.getBingGroups(activity.getClass()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void invokeDatas(Object main, Map<String, Integer> map) {
        try {
            Method findViewById = main.getClass().getMethod("findViewById", int.class);
            for (String key : map.keySet()) {
                Field field = main.getClass().getDeclaredField(key);
                field.setAccessible(true);
                Object res = findViewById.invoke(main, map.get(key));
                field.set(main, res);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

很简单,只需要反射拿到这个类的实例,然后去查询当期类对应的map集合,反射给属性赋值即可

最后的核心还是依赖反射,很多常见的注入的框架比如 EventBus ,Arouter 等都是这个原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值