博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Annotation注解(二) - 进阶 - 自定义ButterKnife
阅读量:5763 次
发布时间:2019-06-18

本文共 8654 字,大约阅读时间需要 28 分钟。

关于注解,你需要知道的一些知识:

在这篇博客中,我们将利用前面讲解的知识,自定义一个轻量级的ButterKnife。

尊重原创,转载请注明出处

本文出自

几个关键类

在开始之前,我们先来熟悉以下几个关键类:

  • Element
  • Elements
  • RoundEnvironment
  • ProcessingEnvironment

Element对象

Element:是一个接口,表示注解修饰的对象,例如类、成员变量、成员方法、包名等。

Element的分类

package com.example;            // PackageElementpublic class TestClass{         // ClassElement    private String name;        // VariableElement    public TestClass() {}       // ExecutableElement    public void getName() {}    // ExecutableElement}

Element的方法

public interface Element extends AnnotatedConstruct {    Name getSimpleName();    Set
getModifiers(); // 获取修饰符 Element getEnclosingElement(); // 获取父类元素 List
getEnclosedElements(); // 获取子类元素
A getAnnotation(Class
var1); // 获取注解 TypeMirror asType(); // 可以根据TypeMirror,获取到Class对象 ElementKind getKind(); List
getAnnotationMirrors(); boolean equals(Object var1); int hashCode();
R accept(ElementVisitor
var1, P var2);}

Elements工具类

Elements是一个操作Element的工具类

public interface Elements {    Name getName(CharSequence var1);    PackageElement getPackageOf(Element var1);                  // 获取PackageElement    PackageElement getPackageElement(CharSequence var1);    TypeElement getTypeElement(CharSequence var1);              // 获取TypeElement    List
getAllMembers(TypeElement var1); // 获取子类Element}

ClassName

了解了Element对象后,我们就可以理解ClassNmae的另外2个get方法了

ClassName的3个重载方法

ClassName.get(String packageName, String... simpleName);ClassName.get(TypeElement element);ClassName.get(TypeMirror mirror);

RoundEnvironment

RoundEnvironment:主要用来获取,注解所修饰的Element对象

  • getElementsAnnotatedWith(Annotation.Class):获取所有注解了Annotation的Element对象
Set
elements = roundEnvironment.getElementsAnnotatedWith(Annotation.class);Set
elements = roundEnvironment.getRootElements();

ProcessingEnvironment

ProcessingEnvironment:主要用来获取,解析注解所需的工具类。

  • Elements:操作Element的工具类
  • Types:操作TypeElement的工具类
  • Filer:创建文件的工具类
public interface ProcessingEnvironment {    Elements getElementUtils();    Types getTypeUtils();    Filer getFiler();    Locale getLocale();    Messager getMessager();    Map
getOptions(); SourceVersion getSourceVersion(); }

轻量级ButterKnife

了解了上面几个关键类之后,我们就可以开始定义属于我们自己的 "轻量级ButterKnife" 了。

定义注解

在这个例子中,我们只需要2个注解:

  • @TargetClass:注解在类上,标记Activity对象
  • @InjectView:注解在成员变量上,标记View控件,需要传入一个id值
@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface TargetClass {}
@Target(ElementType.FIELD)@Retention(RetentionPolicy.SOURCE)public @interface InjectView {    int value();}

定义注解处理器

在注解处理器中,我们重写了3个方法

  • init:利用ProcessingEnvironment对象获取Elements
  • getSupportedAnnotationTypes:返回自定义注解的Set集合
  • process:实现具体的注入操作

下面,我们重点讲解下process的实现细节。

生成findViewById的方法

想要找到某个控件,就必须使用findViewById进行查找;因此我们可以定义一个方法,这个方法会根据传入的activity对象,自动生成我们所需的所有findViewById操作。

而我们要做的就是:如何通过Annotation注解,自动生成下面这段代码?

public static void bind(Activity activity) {    activity.field = (View)activity.findViewById(resId);    ....    ....}

第一步:找到所需的对象。

这里有3个重要的对象:activity、field、resId;这3个对象,可以根据@TargetClass、@InjectView注解获取到:

// 1. 获取所有注解了TargetClass的ElementSet
elements = roundEnvironment.getElementsAnnotatedWith(TargetClass.class);for (Element element : elements) { TypeElement activityElement = (TypeElement) element; // 2. 获取单个类中,所有子Element List
members = elementUtils.getAllMembers(activityElement); for (Element fieldElement : members) { InjectView annotation = fieldElement.getAnnotation(InjectView.class); if (annotation != null) { // 3. 获取resID int redID = annotation.value(); } }}

第二步:生成所需的方法。

回忆一下,上个博客中介绍的JavaPoet的用法,我们可以利用MethodSpec定义这个bind方法:

MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)                    .returns(TypeName.VOID)                    .addParameter(ClassNmae.get(activityElement), "activity");// 添加代码methodBuilder.addStatement(                    "activity.$L= ($T) activity.findViewById($L)",                    fieldElement,                    ClassName.get(fieldElement.asType()),                    redID);

由于对于每一个InjectView对象,都要生成一行代码,所以添加代码的这部分,需要放在for循环内部完成。

至此,就完成了bind方法的创建。

生成方法依赖的类

由于方法不能单独存在,我们还需要给方法创建对应的容器:类。

类的创建,同样借助与JavaPoet,这里我们用TypeSpec进行创建:

TypeSpec typeSpec = TypeSpec.classBuilder("View" + activityElement.getSimpleName())                    .addModifiers(Modifier.PUBLIC)                    .addMethod(methodSpec)                    .build();

为了区分每个Activity,我们给类取名:View + Activity类名。

同时,我们还将之前创建的method方法,添加到了这个typeSpec类中。

创建Java文件

最后,我们根据上面创建的TypeSpec类,利用JavaFile将真实的Java文件创建出来。

try {    // 获取包名    PackageElement packageElement = elementUtils.getPackageOf(activityElement);    String packageName = packageElement.getQualifiedName().toString();    // 创建文件    JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();    javaFile.writeTo(processingEnv.getFiler());} catch (Exception e) {    e.printStackTrace();}

使用注解

完成上面步骤之后,我们就可以在项目中使用这个轻量级的ButterKnife了。

  1. 注解@TargetClass
  2. 注解@InjectView
  3. build项目
  4. 调用ViewMainActivity.bind(this)进行注入动作

注意:在注解完成之后,需要build下项目,才会在build目录生成对应的ViewXxxActivity类。

@TargetClasspublic class MainActivity extends AppCompatActivity {    @InjectView(R.id.tv1)    TextView tv1;    @InjectView(R.id.tv2)    TextView tv2;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ViewMainActivity.bind(this);        tv1.setText("Hello");        tv2.setText("World!");    }}

完整代码

注意:

  • 如果GBK编码报错,去除里面的中文即可。
  • Annotation要单独新建一个Java Library。
@AutoService(Processor.class)public class InjectViewProcessor extends AbstractProcessor {    private Elements elementUtils;    @Override    public synchronized void init(ProcessingEnvironment processingEnvironment) {        super.init(processingEnvironment);        elementUtils = processingEnvironment.getElementUtils();    }    @Override    public Set
getSupportedAnnotationTypes() { HashSet
set = new HashSet<>(); set.add(TargetClass.class.getCanonicalName()); set.add(InjectView.class.getCanonicalName()); return set; } @Override public boolean process(Set
set, RoundEnvironment roundEnvironment) { // 1. 获取所有注解了TargetClass的Element Set
elements = roundEnvironment.getElementsAnnotatedWith(TargetClass.class); for (Element element : elements) { TypeElement activityElement = (TypeElement) element; // 创建方法 MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(TypeName.VOID) .addParameter(ClassName.get(activityElement), "activity"); // 2. 获取单个TypeElement中的,所有子Element List
members = elementUtils.getAllMembers(activityElement); for (Element fieldElement : members) { InjectView annotation = fieldElement.getAnnotation(InjectView.class); if (annotation != null) { // 3. 获取resID int redID = annotation.value(); // 添加代码 methodBuilder.addStatement( "activity.$L= ($T) activity.findViewById($L)", fieldElement, ClassName.get(fieldElement.asType()), redID ); } } MethodSpec methodSpec = methodBuilder.build(); // 创建类 TypeSpec typeSpec = TypeSpec.classBuilder("View" + activityElement.getSimpleName()) .addModifiers(Modifier.PUBLIC) .addMethod(methodSpec) .build(); try { // 获取包名 PackageElement packageElement = elementUtils.getPackageOf(activityElement); String packageName = packageElement.getQualifiedName().toString(); // 创建文件 JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build(); javaFile.writeTo(processingEnv.getFiler()); } catch (Exception e) { e.printStackTrace(); } } return false; } public String getPackageName(TypeElement typeElement) { PackageElement packageElement = elementUtils.getPackageOf(typeElement); return packageElement.getQualifiedName().toString(); }}
你可能感兴趣的文章
springmvc 拦截器使用注意点
查看>>
SqlServer TimeOut服务器设置
查看>>
Java 代码性能优化总结
查看>>
Jira配置openLdap服务器进行用户认证
查看>>
正则表达式之match与exec【转的 楼兰之风】
查看>>
php魔法函数与魔法常量的说明
查看>>
Redis Cluster Notes
查看>>
【转】临界区、互斥量、信号灯、事件
查看>>
lyui 列表 上传
查看>>
Juqery ready的几种写法
查看>>
Java编程思想(第4版)读书笔记——02
查看>>
软件工程第四次作业
查看>>
[软考]之进程调度 ...
查看>>
HTTP与HTTPS的区别
查看>>
带参数的函数装饰器
查看>>
家庭作业补充
查看>>
HTML之禁止输入文本
查看>>
mysql报错问题解决MySQL server PID file could not be found!
查看>>
孕三十六周第五天
查看>>
【算法学习笔记】43.动态规划 逆向思维 SJTU OJ 1012 增长率问题
查看>>