Butterknife原理
2021-07-04 本文已影响0人
旺仔_100
一、目标
写一个demo来实现Butterknife的findViewById功能。
二、核心原理以及实现
核心原理
通过@BindView注解来替代findViewById。主要是通过APT(注解解析器)在编译文件的时候查找到@BindView注解,获取到对应的控件引用和控件id,动态的生成java文件,里面会有个bindView方法,方法内容就是activity.findViewById(id)。java文件可以通过两种方式,一种是直接使用StringBuilder拼接,还有一种是通过javapoet的api来拼接。后者不会出现拼写错误。最后一步就是反射调用生成的bindview方法。
实现
项目主要分为四个模块
- app 主要是用来测试我们功能实现的。
- apt-annotation 是 一个java library,主要放置自定义注解@SensorsDataBindView
- apt-processor 是我们注解处理器模块,它主要是根据apt-annotation模块中定义的注解,在编译时生成xxxActivity_sensorsDataViewBinding.java文件。该module需要依赖apt-annotation。
- apt-sdk 是一个android module,它反射调用apt-process模块生成的xxxActivity_sensorsDataViewBinding.java中的方法,实现对View的绑定,该module需要依赖apt-annotation。
三、代码实现及对应讲解
a、创建一个java library apt-annotation
自定义注解@SensorsDataBindView
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface SensorsDataBindView {
int value();
}
上述代码使用元注解@Retention和@Target
@Retention 定义该注解被保留的时间长短策略。
- SOURCE 表示会被编译器忽略
- CLASS 表示该注解将会被保留在Class文件中,但在运行时并不会被VM保留。这是一种默认行为,没有使用@Retention注解的注解都会采用这种策略。
- RUNTIME 表示保留至运行时。所以我们可以通过反射去获取注解信息。
@Tartget
定义注解所修饰的对象范围。Annotation可用于packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量。
Target主要有下面几种范围: - CONSTRUCTOR:用于描述构造器;
- FIELD:用于描述域;
- LOCAL_VARIABLE:用于描述局部变量;
- METHOD:用于描述方法;
- PACKAGE:用于描述包;
- PARAMETER:用于描述参数;
- TYPE:用于描述类、接口(包括注解类型)或者enum声明。
b、创建一个java library apt-processor
需要依赖auto-service注解
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation project(path: ':apt-annotation')
创建注解处理器,注意类上面需要添加注解@AutoService
/**
* Element中定义的一些常用的方法
* asType 返回此元素的种类:包、类、接口、字段、方法
* getModifiers 返回此元素的修饰符号
* getSimpleName 返回此元素的简单名称 如类名
* getEnclosedElements 返回封装此元素的最里面元素
* getAnnotation 返回此元素针对指定类型的注解
* */
/***
*Element 5个直接子类
* TypeElement 一个类或者接口程序元素
* VariableElement 代表一个字段、enum常量、方法、构造参数、局部变量或异常参数
* ExecutableElement 某个类或者接口方法、构造方法或初始化程序(静态或实例),包括注解类型元素
* PackageElement 一个包程序元素
* ExecutableElement 某个类或者接口的方法、构造方法或者初始化程序(静态或者实例),包括注解类型元素
* TypeParameterElement 一般类、接口、方法或构造方法元素的泛型参数
*
* */
@AutoService(Process.class)
public class SensorsDataBindViewProcessor extends AbstractProcessor {
private Elements elementUtils;
private Map<String,SensorsDataClassCreateFactory> mClassCreatorFactoryMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//提供了很多工具类如 Elements \ Types \ Filer
elementUtils = processingEnvironment.getElementUtils();
}
/**
* 注解处理器是注册给哪个注解的
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(SensorsDataBindView.class.getCanonicalName());
//CanonicalName : com.example.apt_annotation.SensorsDataBindView
System.out.println("CanonicalName : " + SensorsDataBindView.class.getCanonicalName()) ;
return supportTypes;
}
///最小支持的java版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("process : " ) ;
mClassCreatorFactoryMap.clear();
//得到所有的注解
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(SensorsDataBindView.class);
for (Element element : elementsAnnotatedWith) {
//因为我们是方法注解,所以可以直接强转成VariableElement
//VariableElement 代表一个字段、enum常量、方法、构造参数、局部变量或异常参数
VariableElement variableElement = (VariableElement) element;
//返回封装元素最里面的元素 就是类的全名
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
//TypeElement : com.example.aptdemo.MainActivity
System.out.println("TypeElement : "+classElement.toString());
String fullClassName = classElement.getQualifiedName().toString();
//fullClassName : com.example.aptdemo.MainActivity
System.out.println("fullClassName : "+fullClassName);
SensorsDataClassCreateFactory proxy = mClassCreatorFactoryMap.get(fullClassName);
if(proxy == null){
proxy = new SensorsDataClassCreateFactory(elementUtils,classElement);
mClassCreatorFactoryMap.put(fullClassName,proxy);
}
SensorsDataBindView bindAnnotation = variableElement.getAnnotation(SensorsDataBindView.class);
int id = bindAnnotation.value();
proxy.putElement(id,variableElement);
}
//创建java文件
for (String s : mClassCreatorFactoryMap.keySet()) {
SensorsDataClassCreateFactory proxyInfo = mClassCreatorFactoryMap.get(s);
// try {
// //todo xiecuolecreateSourceFile 写成了createClassFile
// JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
//
// Writer writer = jfo.openWriter();
// writer.write(proxyInfo.generateJavaCode());
// writer.flush();
// writer.close();
// System.out.println("JavaFileObject : ");
// } catch (IOException e) {
// e.printStackTrace();
// System.out.println("IOException : ");
// }
//javapoet
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCodeWithJavaPoet()).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
}
- init 初始化函数,可以得到ProcessingEnvioment对象。ProcessingEnvironment提供很多有用的工具类,如Element、Types、Filer。
- getSupportedAnnotedAnnotationTypes 指定这个注解处理器是注册给哪个注解的,这里指定的是我们上面创建的注解@SensorsDataBindView。
- getSupportedSourceVersion 指定使用的java版本,通常都是返回SourceVersion.lastSupported。这个方法也可以使用@SupportedSourceVersion注解代替
- process 这里可以扫描、评估、处理注解的代码,生产java文件。通过roundEnvironment.getElementsAnnotatedWith(SensorsDataBindView.class)可以得到所有含有@SensorsDataBindView注解的elements集合。可以将element强制转化为VariableElement类型,通过variableElement.getEnclosingElement()可以获取类的信息TypeElement,通过classElement.getQualifiedName().toString()可以获取完整的包名和类名。然后讲elements的信息以完整的包名和类名作为key保存到mClassCreateFactorFactoryMap中,最后通过mClassCreateFactoryMap创建对应的Java文件,其中mClassCreateFactoryMap是提供给SensorsDataClassCreateFactory的集合。
SensorsDataClassCreateFactory 主要是拼接生成java文件的,源码如下
public class SensorsDataClassCreateFactory {
private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public SensorsDataClassCreateFactory(Elements elements,TypeElement mTypeElement) {
this.mTypeElement = mTypeElement;
PackageElement packageElement = elements.getPackageOf(mTypeElement);
String packgeName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mPackageName = packgeName;
this.mBindingClassName = className + "_SensorsDataViewBinding";
}
public void putElement(int id,VariableElement element){
mVariableElementMap.put(id,element);
}
/**
* 创建java代码
*/
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("/**\n" +
" * Auto Created by SensorsData APT\n" +
" */\n");
builder.append("package ").append(mPackageName).append(";\n");
builder.append('\n');
builder.append("public class ").append(mBindingClassName);
builder.append(" {\n");
generateBindViewMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
/**
* 加入Method
*
* @param builder StringBuilder
*/
private void generateBindViewMethods(StringBuilder builder) {
builder.append("\tpublic void bindView(");
builder.append(mTypeElement.getQualifiedName());
builder.append(" owner ) {\n");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String viewName = element.getSimpleName().toString();
String viewType = element.asType().toString();
builder.append("\t\towner.");
builder.append(viewName);
builder.append(" = ");
builder.append("(");
builder.append(viewType);
builder.append(")(((android.app.Activity)owner).findViewById( ");
builder.append(id);
builder.append("));\n");
}
builder.append(" }\n");
}
public TypeSpec generateJavaCodeWithJavaPoet(){
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethodsWithJavaPoet())
.build();
return bindingClass;
}
public MethodSpec generateMethodsWithJavaPoet(){
ClassName owner = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(owner,"owner");
for (Integer integer : mVariableElementMap.keySet()) {
VariableElement variableElement = mVariableElementMap.get(integer);
String viewName = variableElement.getSimpleName().toString();
String viewType = variableElement.asType().toString();
methodBuilder.addCode("owner."+viewName+ " = "+"("+viewType+")(((android.app.Activity)owner).findViewById("+integer
+"));");
}
return methodBuilder.build();
}
public String getPackageName() {
return mPackageName;
}
public String getProxyClassFullName(){
return mPackageName + "." + mBindingClassName;
}
public TypeElement getTypeElement(){
return mTypeElement;
}
}
c.创建一个Android module apt-sdk
通过反射调用生成的SensorsDataViewBinding.java的bindView方法。
需要依赖apt-annottion
public class SensorsDataAPI {
public static void bindView(Activity activity){
Class<? extends Activity> aClass = activity.getClass();
try {
Class<?> bindViewClass = Class.forName(aClass.getName() + "_SensorsDataViewBinding");
Method method = bindViewClass.getMethod("bindView", activity.getClass());
method.invoke(bindViewClass.newInstance(),activity);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
到这里基本完成。源码上传到github:https://github.com/yangzai100/APTDemo/tree/master