关于注解,你需要知道的一些知识:
在这篇博客中,我们将利用前面讲解的知识,自定义一个轻量级的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(); SetgetModifiers(); // 获取修饰符 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(); MapgetOptions(); 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了。
- 注解@TargetClass
- 注解@InjectView
- build项目
- 调用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 SetgetSupportedAnnotationTypes() { 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(); }}