關於註解,你須要知道的一些知識:
Annotation註解(一)- 基礎
Annotation註解(二)- 進階 - 自定義ButterKnifejava
在這篇博客中,咱們將利用前面講解的知識,自定義一個輕量級的ButterKnife。segmentfault
尊重原創,轉載請註明出處 https://segmentfault.com/a/11...
本文出自 強哥大天才的博客ide
在開始以前,咱們先來熟悉如下幾個關鍵類:工具
Element
Elements
RoundEnvironment
ProcessingEnvironment
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
是一個操做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 }
瞭解了Element對象後,咱們就能夠理解ClassNmae的另外2個get方法了對象
ClassName的3個重載方法接口
ClassName.get(String packageName, String... simpleName); ClassName.get(TypeElement element); ClassName.get(TypeMirror mirror);
RoundEnvironment
:主要用來獲取,註解所修飾的Element對象
getElementsAnnotatedWith(Annotation.Class)
:獲取全部註解了Annotation的Element對象Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Annotation.class); Set<? extends Element> elements = roundEnvironment.getRootElements();
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" 了。
在這個例子中,咱們只須要2個註解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface TargetClass { }
@Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface InjectView { int value(); }
在註解處理器中,咱們重寫了3個方法
init
:利用ProcessingEnvironment對象獲取ElementsgetSupportedAnnotationTypes
:返回自定義註解的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了。
注意:在註解完成以後,須要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!"); } }
注意:
@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(); } }