关于注解,你需要知道的一些知识:
Annotation注解(一)- 基础
Annotation注解(二)- 进阶 - 自定义ButterKnife

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

尊重原创,转载请注明出处 https://segmentfault.com/a/11...
本文出自 强哥大天才的博客

几个关键类

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

  • Element
  • Elements
  • RoundEnvironment
  • ProcessingEnvironment

Element对象

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

Element的分类

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

Element的方法

public interface Element extends AnnotatedConstruct {
    Name getSimpleName();
    Set<Modifier> getModifiers();                             // 获取修饰符
    Element getEnclosingElement();                            // 获取父类元素
    List<? extends Element> getEnclosedElements();            // 获取子类元素
    <A extends Annotation> A getAnnotation(Class<A> var1);    // 获取注解
    TypeMirror asType();                                      // 可以根据TypeMirror,获取到Class对象
    ElementKind getKind();
    List<? extends AnnotationMirror> getAnnotationMirrors();
    boolean equals(Object var1);
    int hashCode();
    <R, P> R accept(ElementVisitor<R, P> 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<? extends Element> 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<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Annotation.class);
Set<? extends Element> elements = roundEnvironment.getRootElements();

ProcessingEnvironment

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

  • Elements:操作Element的工具类
  • Types:操作TypeElement的工具类
  • Filer:创建文件的工具类
public interface ProcessingEnvironment {
    Elements getElementUtils();
    Types getTypeUtils();
    Filer getFiler();
    Locale getLocale();
    Messager getMessager();
    Map<String, String> 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的Element
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(TargetClass.class);
for (Element element : elements) {
    TypeElement activityElement = (TypeElement) element;
    // 2. 获取单个类中,所有子Element
    List<? extends Element> 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类。

@TargetClass
public 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<String> getSupportedAnnotationTypes() {
        HashSet<String> set = new HashSet<>();
        set.add(TargetClass.class.getCanonicalName());
        set.add(InjectView.class.getCanonicalName());
        return set;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 1. 获取所有注解了TargetClass的Element
        Set<? extends Element> 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<? extends Element> 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();
    }
}

强哥大天才
6 声望1 粉丝

引用和评论

0 条评论