关于注解,你需要知道的一些知识:
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了。
- 注解@TargetClass
- 注解@InjectView
- build项目
- 调用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();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。