写点什么

字节三面:ButterKnife 为什么执行效率为什么比其他注入框架高?它的原理是什么 (1)

用户头像
Android架构
关注
发布于: 刚刚

FIELD, //属性 METHOD, //方法 PARAMETER, //参数 CONSTRUCTOR, //构造函数 LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USE;


private ElementType() {}}


  • @Retention 该注解的保留策略,有三种选项:


public enum RetentionPolicy {SOURCE, //被编译器所忽略


CLASS, //被编译器保留至类文件,但不会保留至运行时


RUNTIME //保留至类文件,且保留至运行时,能在运行时反射该注解修饰的对象}

2. 注解处理器

真正处理注解并生成代码的操作都在这里。 在写代码之前我们需要先导入两个重要的库,以及我们的注解模块:


compile 'com.google.auto.service:auto-service:1.0-rc4'compile 'com.squareup:javapoet:1.9.0'implementation project(':lib_annotations')


新建类RandomProcessor.java:


@AutoService(Processor.class)public class RandomProcessor extends AbstractProcessor{


@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);}


@Overridepublic SourceVersion getSupportedSourceVersion() {return super.getSupportedSourceVersion();}


@Overridepublic Set<String> getSupportedAnnotationTypes() {return super.getSupportedAnnotationTypes();}


@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {return false;}}


  • @AutoService @AutoService(Processor.class)会告诉编译器该注解处理器的存在,并在编译时自动在META-INF/services下生成javax.annotation.processing.Processor文件,文件的内容为


com.rhythm7.lib_compiler.RandomProcessor


也就是说,你所声明的注解处理器都会在被写入这个配置文件中。 这样子,当外部程序装载这个模块的时候,就能通过该模块的 jar 包下的 META-INF/services 下找到具体的注解处理器的实现类名,并加载实例化,完成模块的注入。 注解处理器需要实现AbstractProcessor接口,并实现对应的方法


  • init() 可选 在该方法中可以获取到processingEnvironment对象,借由该对象可以获取到生成代码的文件对象, debug 输出对象,以及一些相关工具类

  • getSupportedSourceVersion() 返回所支持的 java 版本,一般返回当前所支持的最新 java 版本即可

  • getSupportedAnnotationTypes() 你所需要处理的所有注解,该方法的返回值会被process()方法所接收

  • process() 必须实现 扫描所有被注解的元素,并作处理,最后生成文件。该方法的返回值为 boolean 类型,若返回 true,则代表本次处理的注解已经都被处理,不希望下一个注解处理器继续处理,否则下一个注解处理器会继续处理。

初始化

较详细代码如下:


private static final List<Class<? extends Annotation>> RANDOM_TYPES= Arrays.asList(RandomInt.class, RandomString.class);


private Messager messager;private Types typesUtil;private Elements elementsUtil;private Filer filer;


private TypeonProcess()per.init(processingEnv);messager = processingEnv.getMessager();typesUtil = processingEnv.getTypeUtils();elementsUtil = processingEnv.getElementUtils();filer = processingEnv.getFiler();}


@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}


@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> annotations = new LinkedHashSet<>();


for (Class<? extends Annotation> annotation : RANDOM_TYPES) {annotations.add(annotation.getCanonicalName());}return annotations;}

处理注解

process()方法中执行以下操作:


1.扫描所有注解元素,并对注解元素的类型做判断


for (Element element : roundEnv.getElementsAnnotatedWith(RandomInt.class)) {//AnnotatedRandomInt 是对被 RandomInt 注解的 Elment 的简单封装 AnnotatedRandomInt randomElement = new AnnotatedRandomInt(element);messager.printMessage(Diagnostic.Kind.NOTE, randomElement.toString());//判断被注解的类型是否符合要求 if (!element.asType().getKind().equals(TypeKind.INT)) {messager.printMessage(Diagnostic.Kin


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


d.ERROR, randomElement.getSimpleClassName().toString() + "#"


  • randomElement.getElementName().toString() + " is not in valid type int");}


//按被注解元素所在类的完整类名为 key 将被注解元素存储进 Map 中,后面会根据 key 生成类文件 String qualifier = randomElement.getQualifiedClassName().toString();if (annotatedElementMap.get(qualifier) == null) {annotatedElementMap.put(qualifier, new ArrayList<AnnotatedRandomElement>());}annotatedElementMap.get(qualifier).add(randomElement);}

生成类文件

将之前以注解所在类为 key 的 map 遍历,并以 key 值为分组生成类文件。


for (Map.Entry<String, List<AnnotatedRandomElement>> entry : annotatedElementMap.entrySet()) {MethodSpec constructor = createConstructor(entry.getValue());TypeSpec binder = createClass(getClassName(entry.getKey()), constructor);JavaFile javaFile = JavaFile.builder(getPackage(entry.getKey()), binder).build();javaFile.writeTo(filer);}


生成类、构造函数、代码段以及文件都是利用到了javapoet依赖库。当然你也可以选择拼接字符串和自己用文件 IO 写入,但是用javapoet要更方便得多。


private MethodSpec createConstructor(List<AnnotatedRandomElement> randomElements) {AnnotatedRandomElement firstElement = randomElements.get(0);MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addParameter(TypeName.get(firstElement.getElement().getEnclosingElement().asType()), "target");for (int i = 0; i < randomElements.size(); i++) {addStatement(builder, randomElements.get(i));}return builder.build();}


private void addStatement(MethodSpec.Builder builder, AnnotatedRandomElement randomElement) {builder.addStatement(String.format("target.%1s",randomElement.getElementName().toString(),randomElement.getRandomValue()));}


private TypeSpec createClass(String className, MethodSpec constructor) {return TypeSpec.classBuilder(className + "_Random").addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(constructor).build();}


private String getPackage(String qualifier) {return qualifier.substring(0, qualifier.lastIndexOf("."));}


private String getClassName(String qualifier) {return qualifier.substring(qualifier.lastIndexOf(".") + 1);}


通过以上几行代码,创建了类文件。在类的构造函数中添加参数(target), 并为每一个被注解元素添加语句"target.%1$s = %2$s",最后通过javaFile.writeTo(filer)完成文件写入。

3. 调用生成类的方法

在 lib_api 中新建一个类:RandomUtil.java,添加注入方法:


public static void inject(Object object) {Class bindingClass = Class.forName(object.getClass().getCanonicalName() + "_Random");Constructor constructor = bindingClass.getConstructor(object.getClass());constructor.newInstance(object);}


这里利用反射找到了以“Object 类名_Random”命名的生成类,并调用它的构造方法。而在我们之前的注解处理器中,我们已在生成类的构造方法中实现了属性的赋值操作。

4. 使用生成类

在 app module 中依赖刚才创建的库:


implementation project(':lib_annotations')implementation project(':lib_api')annotationProcessor project(':lib_compiler')


在 Activity 中的使用


public class MainActivity extends AppCompatActivity {@RandomInt(minValue = 10, maxValue = 1000)int mRandomInt;


@RandomStringString mRandomString;

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
字节三面:ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么(1)