上一篇 ButterKnife源码分析- 涉及元注解和AbstractProcessor和javapoet 我们已经了解了Butterknife的源码,总体来讲这个框架理解起来并不难, 思路很清晰,基本就是一条线没有很绕的地方。
接下来我们结合源码的实现,我们实现一个自己的简易版本的带有findViewById功能的Knife。
新建一个annotations 的java Library,它主要负责定义注解。
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:support-annotations:28.0.0'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "7"
targetCompatibility = "7"
新建一个compiler 的java Library, 它的作用主要是解析注解。
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
//annotations是上文创建的注解module
api project(':annotations')
//javapoet提供了一套生成java代码的api,利用这些api处理注解,生成新的代码或源文件。
api 'com.squareup:javapoet:1.11.1'
//auto-service的作用是向系统注册processor(自定义注解处理器),执行编译时使用processor进行处理。
api 'com.google.auto.service:auto-service:1.0-rc3'
api 'com.google.auto:auto-common:0.9'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "7"
targetCompatibility = "7"
我们的需求很简单 所以这部分内容很少。
import android.support.annotation.IdRes;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
@IdRes int value();
}
继承AbstractProcessor 实现 getSupportedAnnotationTypes 和 process方法。
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotationNames = new LinkedHashSet<>();
annotationNames.add(BindView.class.getCanonicalName());
return annotationNames;
}
getSupportedAnnotationTypes 方法的内容很简单,返回一个该注解处理器需要处理的注解的字符串集合,我的理解他是一个注册过程。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
//获取所有的带有BindView注解的元素
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
// 按照Activity 对 BindView 注解的元素进行分类
Map<String,List<VariableElement>> activityElementMap = new HashMap<>();
for (Element element : elements) {
//获取对应的MainActivity名称
VariableElement variableElement = (VariableElement) element;
TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement();
String packageName = MoreElements.getPackage(enclosingElement).getQualifiedName().toString();
System.out.print("BindViewGenerator = "+packageName);
String activityName = enclosingElement.getQualifiedName().toString();
//createFile(enclosingElement.getQualifiedName().toString());
//按照activity的包路径进行分类
List<VariableElement> elementList = activityElementMap.get(activityName);
if (elementList == null){
elementList = new ArrayList<>();
activityElementMap.put(activityName,elementList);
}
elementList.add(variableElement);
}
// 生成java 文件
Set<Map.Entry<String, List<VariableElement>>> entries = activityElementMap.entrySet();
for (Map.Entry<String, List<VariableElement>> entry : entries) {
String activityName = entry.getKey();
String substring = activityName.substring(activityName.lastIndexOf(".")+1, activityName.length());
//创建类
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(substring+"_ViewBinding")
.addModifiers(Modifier.PUBLIC);
//创建构造方法
MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeVariableName.get(activityName),"target");
List<VariableElement> elementList = entry.getValue();
String packageName = MoreElements.getPackage(elementList.get(0)).getQualifiedName().toString();
for (VariableElement element : elementList) {
String name = element.getSimpleName().toString();
TypeMirror typeMirror = element.asType();
int id = element.getAnnotation(BindView.class).value();
methodBuilder.addStatement("target."+name + " = ("+typeMirror+")"+"target.findViewById("+id+")");
}
MethodSpec methodSpec = methodBuilder.build();
TypeSpec typeSpec = typeBuilder.addMethod(methodSpec).build();
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
process 方法主要就是解析指定注解上的所有信息。
参数 RoundEnvironment roundEnv :表示当前或是之前的运行环境,可以通过该对象查找找到的注解。
拿到所有的注解对象元素以后,遍历所有注解元素。
for (Element element : elements) {
//VariableElement 是注解在成员变量身上的元素
VariableElement variableElement = (VariableElement) element;
//返回这个元素的最里层元素, 通常我们将注解写在了成员变量上, 所以这里返回的就是类元素
TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement();
String packageName = MoreElements.getPackage(enclosingElement).getQualifiedName().toString();
System.out.print("BindViewGenerator = "+packageName);
//返回此类型元素的完全限定名称,即类名全路径
String activityName = enclosingElement.getQualifiedName().toString();
//createFile(enclosingElement.getQualifiedName().toString());
//将一个类上的所有的ViewBind注解元素都放在一起
//按照activity的包路径进行分类
List<VariableElement> elementList = activityElementMap.get(activityName);
if (elementList == null){
elementList = new ArrayList<>();
activityElementMap.put(activityName,elementList);
}
elementList.add(variableElement);
}
通过上面的方法我们可以已经将所有带有BindView的注解的元素,按照类名进行分类。
//按照类(Activity)进行遍历,生成对应的xxx_BindViewing类
Set<Map.Entry<String, List<VariableElement>>> entries = activityElementMap.entrySet();
for (Map.Entry<String, List<VariableElement>> entry : entries) {
// 获取到要解析注解对应的类名
String activityName = entry.getKey();
//获取到simpleName
String substring = activityName.substring(activityName.lastIndexOf(".")+1, activityName.length());
//创建类 我们需要创建的类和我们的Activity在同一个包下,类名是_ViewBinding 这是一个统一的规则
//JavaPoet提供了(TypeSpec)用于创建类或者接口,(FieldSpec)用来创建字段,(MethodSpec)用来创建方法和构造函数,(ParameterSpec)用来创建参数,(AnnotationSpec)用于创建注解。
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(substring+"_ViewBinding")
.addModifiers(Modifier.PUBLIC);// 创建的类是public
//创建构造方法
MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()// 构造方法
.addModifiers(Modifier.PUBLIC)// 创建的方法是public
.addParameter(TypeVariableName.get(activityName),"target");// 需要传入的方法
// 在方法内部加入findViewById,并把得到的值传给对应的Activity
List<VariableElement> elementList = entry.getValue();
String packageName = MoreElements.getPackage(elementList.get(0)).getQualifiedName().toString();
//拿到类里面所有的注解元素 (标记成员变量的注解元素VariableElement)
for (VariableElement element : elementList) {
//得到注解的变量名
String name = element.getSimpleName().toString();
//得到变量名的类型
TypeMirror typeMirror = element.asType();
// 得到变量名的值 也就是Id值
int id = element.getAnnotation(BindView.class).value();
// 添加指定的代码 并且添加分号和换行
methodBuilder.addStatement("target."+name + " = ("+typeMirror+")"+"target.findViewById("+id+")");
}
//最后得到methodSpec 并加入到 TypeSpec, 可以理解成给一个类加一个方法
MethodSpec methodSpec = methodBuilder.build();
TypeSpec typeSpec = typeBuilder.addMethod(methodSpec).build();
// 使用JavaPoet 动态创建出对应的类文件
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
这里的主角就是TypeSpec 和 MethodSpec,他们都同属于JavaPoet,JavaPoet提供了(TypeSpec)用于创建类或者接口,(FieldSpec)用来创建字段,(MethodSpec)用来创建方法和构造函数,(ParameterSpec)用来创建参数,(AnnotationSpec)用于创建注解。
至此我们的编译解析类 就写好了。 只需要在我们的MainActivity加上BindView注解 ,build 就可以生成对应的findViewById文件类。
package com.example.guava;
public class MainActivity_ViewBinding {
public MainActivity_ViewBinding(com.example.guava.MainActivity target) {
target.toolbar = (android.support.v7.widget.Toolbar)target.findViewById(2131689648);
}
}
这是我生成的类文件。 接下来的目的是 在oncreate方法中调用这个类的构造方法。
import android.app.Activity;
import android.view.View;
import com.orhanobut.logger.Logger;
import java.lang.reflect.InvocationTargetException;
public class LemonKnife {
public static void bind(Activity activity){
// 获取到对应的 Activity的class
Class<? extends Activity> aClass = activity.getClass();
//获取到全路径字符串
String canonicalName = aClass.getCanonicalName();
try {
// 因为我们的规则是在XXX_ViewBinding 所以我们找到对应的MainActivity_ViewBinding的class
Class<?> bindingClass = aClass.getClassLoader().loadClass(canonicalName + "_ViewBinding");
try {
try {
// 获取到他的构造方法,传入对应的参数类型,执行它。
bindingClass.getConstructor(aClass).newInstance(activity);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Logger.d(canonicalName);
}
}
这部分很简单就是通过classloader 获取到动态生成的类,调用他的构造方法。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LemonKnife.bind(MainActivity.this);
Drawable logo = toolbar.getLogo();
}