栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

手写ButterKnife来搞明白Android注解处理器

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

手写ButterKnife来搞明白Android注解处理器

Butterknife现在在项目中基本没用到了,逐渐被ViewBinding所代替,而我们所熟知它的内部原理是通过自定义注解+自定义注解解析器来动态生成代码并为我们的view绑定id的。今天就通过重新手写ButterKinife来搞明白我们今天的主角–Anotation Processing(注解处理器)。

源码地址:APTDemo

运行时注解

在写注解处理器之前,先用运行时注解来操作下。这里我们先新建一个library取名lib-reflection

然后自定义注解,我们只实现了View与id的绑定功能,所以我们这里定义:

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

Target的Type说明这是一个用于修饰类和接口的注解,它也就是成员变量,即需要绑定资源id的view成员。

同时这个注解Retention是RUNTIME,表示注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

注解定义好后就可以直接在项目中使用了;

public class MainActivity extends AppCompatActivity {
 
    @BindView(R.id.textView)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
      	Binding.bind(this);
       textView.setText("哈哈哈哈");
    }
}

注意这里我们加了个Binding.bind(this),就是它来帮助我们做注解的解析,之后在内部调用MainActivity.textView=(TextView)MainActivity.findViewById()来实现为view绑定id的。还是在刚才的目录lib-reflect下创建了Binding类,具体代码如下:

public class Binding {
    public static void bind(Activity activity){
        //反射获取注解注释
        for (Field field: activity.getClass().getDeclaredFields()){
            BindView bindView = field.getAnnotation(BindView.class);
            if (bindView != null){
                try {
                    //扩大范围
                    field.setAccessible(true);
                    field.set(activity, activity.findViewById(bindView.value()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

再运行就可以绑定了view的id了,不再需要我们手动的绑定id了。但是依靠反射始终是消耗性能的,这时候就用到我们的注解处理器了。

编译时注解

这里,我们新创建个Java Library的项目,就叫lib-processor吧。然后再这个目录下创建BindingProcessor,继承自AbstractProcessor。这个就是用于解析自定义注解的解析器了。不过要想让它生效还必须在resource下新建如下目录(现在google给提供了一个注册处理器的库@AutoService(Processor.class)的注解来简化我们的操作。):

javax.annotation.processing.Processor的文本文件里面内容就一行:

com.pince.lib_processor.BindingProcessor

接下来将之前定义的BindView注解改为编译时注解,放进新创建的lin-annotations目录下:

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

还需要修改build.gradle文件,加入:

app依赖:

implementation project(':lib')
annotationProcessor project(':lib-processor')

lib-processor依赖:

implementation project(':lib-annotations')

lib依赖:

//主项目需要的依赖
api project(':lib-annotations')

这么做就是为了让编译器使用我们的解析器用于解析注解。

后面的的工作都是在BindingProcessor操作了。通过读取类中的自定义注解,生成相应的绑定视图的代码,还需要引入一个库。

compile 'com.squareup:javapoet:1.9.0'

先展示下最后自动生成的类:

public class MainActivityBinding {
  public MainActivityBinding(MainActivity activity) {
    activity.textView = activity.findViewById(2131231093);
  }
}

上面的内容就是由javapoet生成的,下面就按照上面这个最终效果来一步一步分析要怎么生成我们的代理类。

看下面我们需要创建一个构造函数作为参数传入:

String packageStr = element.getEnclosingElement().toString();
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(ClassName.get(packageStr, classStr), "activity");

然后查找所有标注了BidView注解的成员变量:

 for (Element enclosedElement: element.getEnclosedElements()){
                if (enclosedElement.getKind() == ElementKind.FIELD){
                    //寻找BindView注释
                    BindView bindView = enclosedElement.getAnnotation(BindView.class);
                    if (bindView != null){
                        ....
                    }
                }
            }

再然后是生成findViewById的具体语句代码:

for (Element enclosedElement: element.getEnclosedElements()){
                if (enclosedElement.getKind() == ElementKind.FIELD){
                    //寻找BindView注释
                    BindView bindView = enclosedElement.getAnnotation(BindView.class);
                    if (bindView != null){
                        hasBinding = true;
                        constructorBuilder.addStatement("activity.$N = activity.findViewById($L)",
                                enclosedElement.getSimpleName(), bindView.value());
                    }
                }
            }

做好了上面步骤,主要的代码也写完了,最后就是生成这个MainActivityBinding这个类:

String packageStr = element.getEnclosingElement().toString();
ClassName className = ClassName.get(packageStr, classStr + "Binding");
TypeSpec builtClass = TypeSpec.classBuilder(className)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(constructorBuilder.build())
                    .build();
try {
                    JavaFile.builder(packageStr, builtClass)
                            .build().writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }

注意这里的包名,生成的类的包名尽量与需要绑定的Activity所在的包名一致,这样BindView修饰的成员变量只需是包内可见就行。到了这里我们再执行./gradlew :app:compileDebugJava编译就可以自动生成我们所需要的类了。
到了这一步还没完,我们还需要在lib moodule目录下创建新的Binding帮助类:

public class Binding {
    public static void bind(Activity activity){
        try {
            Class bindingClass = Class.forName(activity.getClass().getCanonicalName() + "Binding");
            Constructor constructor = bindingClass.getDeclaredConstructor(activity.getClass());
            constructor.newInstance(activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

在这里利用了一点反射new出了MainActivityBinding实体,传入相应的activity在内部进行绑定操作。到这里简单的ButterKnife版本就实现了。下面BindingProcessor给出完整代码:

public class BindingProcessor extends AbstractProcessor {

    Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
      //使用Filer你可以创建文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
				//获取到全部的类
      	//Element代表的是源代码
   			// Element可以是类、方法、变量等
        for (Element element: roundEnv.getRootElements()){
            String packageStr = element.getEnclosingElement().toString();
            String classStr = element.getSimpleName().toString();
            
          //构建新的类的名字:原类名 + Binding
            ClassName className = ClassName.get(packageStr, classStr + "Binding");
          构建新的类的构造方法
            MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(ClassName.get(packageStr, classStr), "activity");
            boolean hasBinding = false;
  				//还有个getEnclosingElement  单数 被包住的外层
            //子类里的元素  字段 方法 内部类
            for (Element enclosedElement: element.getEnclosedElements()){
              //仅获取成员变量
                if (enclosedElement.getKind() == ElementKind.FIELD){
                    //寻找BindView注解
                    BindView bindView = enclosedElement.getAnnotation(BindView.class);
                    if (bindView != null){
                        hasBinding = true;
                      //在构造方法中加入代码
                        constructorBuilder.addStatement("activity.$N = activity.findViewById($L)",
                                enclosedElement.getSimpleName(), bindView.value());
                    }
                }
            }

            TypeSpec builtClass = TypeSpec.classBuilder(className)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(constructorBuilder.build())
                    .build();

            if (hasBinding){
                try {
                  //生成 Java 文件
                    JavaFile.builder(packageStr, builtClass)
                            .build().writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return false;
    }

    @Override
    public Set getSupportedAnnotationTypes() {
        //对这个注解进行注解处理
        return Collections.singleton(BindView.class.getCanonicalName());
    }
}

源码地址:APTDemo

注解后话

元注解是用来定义其他注解的注解(在自定义注解的时候,需要使用到元注解来定义我们的注解)。java.lang.annotation提供了四种元注解:@Retention、 @Target、@Inherited、@documented。

元注解说明
@Target表明我们注解可以出现的地方。是一个ElementType枚举
@Retention这个注解的的存活时间
@document表明注解可以被javadoc此类的工具文档化
@Inherited是否允许子类继承该注解,默认为false
@Target
@Target-ElementType类型说明
ElementType.TYPE接口、类、枚举、注解
ElementType.FIELD字段、枚举的常量
ElementType.METHOD方法
ElementType.PARAMETER方法参数
ElementType.CONSTRUCTOR构造函数
ElementType.LOCAL_VARIABLE局部变量
ElementType.ANNOTATION_TYPE注解
ElementType.PACKAGE
@Retention

表示需要在什么几倍保存该注释信息,用于描述注解的生命周期。

@Retention-RetentionPolicy类型说明
RetentionPolicy.SOURCE注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
RetentionPolicy.CLASS注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
RetentionPolicy.RUNTIME解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@document

@document表明我们标记的注解可以被javadoc此类的工具文档化

@Inherited

@Inherited表明我们标记的注解是被继承的。比如,如果一个父类使用了@Inherited修饰的注解,则允许子类继承该父类的注解

注解解析

Class类里面常用方法如下:

    public String getName();

    
    public String getSimpleName();

    
    public Constructor[] getConstructors();

    
    public Constructor[] getDeclaredConstructors();

    
    public Field[] getFields();

    
    public native Field[] getDeclaredFields();

    
    public Method[] getMethods();

    
    public Method[] getDeclaredMethods();

    
    public Method getEnclosingMethod();

    
    public Package getPackage();

    
    public String getPackageName$();

    
    public Type getGenericSuperclass();

    
    public Class[] getInterfaces();

    
    public int getModifiers();

Field和Method都实现了AnnotatedElement接口,常用方法如下:

    default boolean isAnnotationPresent(Class annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    
     T getAnnotation(Class annotationClass);

    
    Annotation[] getAnnotations();

    
    default  T[] getAnnotationsByType(Class annotationClass) {
        return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
    }

    
    default  T getDeclaredAnnotation(Class annotationClass) {
        Objects.requireNonNull(annotationClass);
        // Loop over all directly-present annotations looking for a matching one
        for (Annotation annotation : getDeclaredAnnotations()) {
            if (annotationClass.equals(annotation.annotationType())) {
                // More robust to do a dynamic cast at runtime instead
                // of compile-time only.
                return annotationClass.cast(annotation);
            }
        }
        return null;
    }

    
    default  T[] getDeclaredAnnotationsByType(Class annotationClass) {
        return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
    }

    
    Annotation[] getDeclaredAnnotations();
注解处理器后话

注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册相应的注解处理器(自定义的注解处理器需继承自AbstractProcessor)。

定义一个注解处理器,需要继承自AbstractProcessor。如下所示:

public class MyProcessor extends AbstractProcessor {

  
    @Override
    public synchronized void init(ProcessingEnvironment env){ }

  
    @Override
    public boolean process(Set annoations, RoundEnvironment env) { }

  
    @Override
    public Set getSupportedAnnotationTypes() { }

  
    @Override
    public SourceVersion getSupportedSourceVersion() { }

}

注处理器的核心是process()方法,而process方法的核心是Element元素。Element里的元素可以是类、方法或是变量。在操作注解的过程中,编译器会扫描所有的Java文件,并将每一个部分看做是特定类型的的Element。可以代表包、类、接口、方法、字段等多种元素种类。

Element子类解释
TypeElement类或接口元素
VariableElement字段、enum常量、方法或构造方法参数、局部变量或异常参数元素
ExecutableElement类或接口的方法、构造方法,或者注解类型元素
PackageElement包元素
TypeParameterElement类、接口、方法或构造方法元素的泛型参数

Element类常用方法如下:

    
    TypeMirror asType();

    
    ElementKind getKind();

    
    Set getModifiers();

    
    Name getSimpleName();

    
    Element getEnclosingElement();

    
    List getEnclosedElements();

    
    List getAnnotationMirrors();

    
     A getAnnotation(Class var1);

还有四个帮助类也是需要我们了解的:

  • Elements:一个用来处理Element的工具类
  • Types:一个用来处理TypeMirror的工具类
  • Filer:用于创建文件(比如创建class文件)
  • Messager:用于输出,类似printf函数

参考

自定义注解和解析器实现ButterKnife

Android 自定义注解(Annotation)

Java注解处理器

JavaPoet源码初探

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号