Annotation註解(二) - 進階 - 自定義ButterKnife

關於註解,你須要知道的一些知識:
Annotation註解(一)- 基礎
Annotation註解(二)- 進階 - 自定義ButterKnifejava

在這篇博客中,咱們將利用前面講解的知識,自定義一個輕量級的ButterKnife。segmentfault

尊重原創,轉載請註明出處 https://segmentfault.com/a/11...
本文出自 強哥大天才的博客ide

幾個關鍵類

在開始以前,咱們先來熟悉如下幾個關鍵類:工具

  • Element
  • Elements
  • RoundEnvironment
  • ProcessingEnvironment

Element對象

Element:是一個接口,表示註解修飾的對象,例如類、成員變量、成員方法、包名等。ui

Element的分類this

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的工具類code

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();
    }
}
相關文章
相關標籤/搜索