手写butterKnifer使用了 apt 技术。APT 是一种处理注解工具,他对项目源代码进行扫描,获取注解。然后通过注解处理器对注解进行操作,生成新的class文件。
apt技术使用三个技术点:
annotation模块(Java Library) 该模块存放的是我们自定义的注解,是一个Java Library
compiler模块 (Java Library) 依赖annotation模块,处理注解并自动生成代码等,同样也是Java Library。
butterKnife模块 (Android Library) 编写关于butterknife的相关代码。
第一部门:我们需要创建一个 annotation模块(Java Library),命名为:bk-annotations,该模块存放的是我们自定义的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BkBindView {
int value();
}
第二部分:我们需要创建一个 android library,命名为:bk-butterknife
public class Utils {
public static <T extends View> T findViewById(Activity activity, int viewId){
return (T)activity.findViewById(viewId);
}
}
public interface Unbinder {
@UiThread
void unbind();
Unbinder EMPTY = new Unbinder() {
@Override
public void unbind() {
}
};
}
public class ButterKnife {
public static Unbinder bind(Activity activity){
try {
Class<? extends Unbinder> clazz = (Class<? extends Unbinder>) Class.forName(activity.getClass().getName() + "_ViewBinding");
//构造函数
Constructor<? extends Unbinder> constuctor = clazz.getDeclaredConstructor(activity.getClass());
constuctor.setAccessible(true);
Unbinder unbinder = constuctor.newInstance(activity);
return unbinder;
} catch (Exception e) {
e.printStackTrace();
}
return Unbinder.EMPTY;
}
}
第三部分:我们需要创建一个注解处理器,获取项目注解,通过javapoet生成代码,命名为:bk-compiler
.gradle文件如下:
implementation 'com.squareup:javapoet:1.7.0' //这个库的主要作用就是帮助我们通过类调用的形式来生成代码
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation project(path: ':bk-annotations')
代码如下:
/**
* Created by malei on 6/21/21
* Describe:定义一个注解处理器
*/
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
private Filer mFiler;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BkBindView.class.getCanonicalName());
return types;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//1 . 将项目中所有的注解添加到map中,map根据类做key
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BkBindView.class);
Map<Element,List<Element>> totalMap = new LinkedHashMap<>(); //类名 -》类中注解
for (Element element : elements) {
Element enclosingElement = element.getEnclosingElement(); //该注解属于的类
List<Element> annotationListForClass = totalMap.get(enclosingElement); //在map中通过类名获取注解表
if (annotationListForClass == null){ //注解表为null的时候
annotationListForClass = new ArrayList<>(); //创建注解表
totalMap.put(enclosingElement,annotationListForClass); //将该注解表 添加到map中
}
//如果注解表不为空,就直接将该注解添加到注解表中
annotationListForClass.add(element);
}
// 2. 对拿到的所有注解进行处理
for (Map.Entry<Element,List<Element>> entry: totalMap.entrySet()){
//每一个entry 都是一个类中的注解表
Element enclosingElement = entry.getKey(); //类名 key
List<Element> bindListForClass = entry.getValue(); //类中的注解表
/**
* 拼装这一行代码:
* public final class MainActivity_ViewBinding implements Unbinder {
* private MainActivity target;
* }
*/
String activityName = enclosingElement.getSimpleName().toString(); //类名的字符串
ClassName activityClassName = ClassName.bestGuess(activityName); //创建新的实例
ClassName unbinderClassName = ClassName.get("com.qiyi.bk_butterknife","Unbinder");
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName + "_ViewBinding2")
.addModifiers(Modifier.FINAL,Modifier.PUBLIC) //类名前添加public final
.addSuperinterface(unbinderClassName) //添加类的实现接口
.addField(activityClassName,"target",Modifier.PRIVATE); //添加一个成员变量,这个名字target是仿照butterknife
/**
* 拼装这一行代码:
* @Override
* @CallSuper
* public final void unbind() {
* target.btn = null;
* }
*/
//实现Unbinder的方法
//CallSuper这个注解不像Override可以直接拿到,需要用这种方式
ClassName callSuperClass = ClassName.get("androidx.annotation","CallSuper");
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")//和你创建的Unbinder中的方法名保持一致
.addAnnotation(Override.class)
.addAnnotation(callSuperClass)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC);
/**
* MainActivity_ViewBinding(MainActivity target) {
* this.target = target;
* target.btn = Utils.findViewById(target,2131230807);
* }
*/
//添加构造函数
MethodSpec.Builder constructMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(activityClassName,"target");
constructMethodBuilder.addStatement("this.target = target");
for (Element bindViewElement : bindListForClass) {
//获取一个注解的地方
String fieldName = bindViewElement.getSimpleName().toString();
ClassName utilsClassName = ClassName.get("com.qiyi.bk_butterknife", "Utils");
BkBindView annotation = bindViewElement.getAnnotation(BkBindView.class);
if (annotation != null){
int resId = annotation.value();
constructMethodBuilder.addStatement("target.$L = $T.findViewById(target,$L)",fieldName,utilsClassName,resId);
//在unbind方法中添加代码 target.textView1 = null;
//不能用addCode,因为它不会在每一行代码后加分号和换行
unbindMethodBuilder.addStatement("target.$L = null",fieldName);
}
}
classBuilder.addMethod(constructMethodBuilder.build());
classBuilder.addMethod(unbindMethodBuilder.build());
try {
//得到包名
String packageName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
log(packageName);
JavaFile.builder(packageName,classBuilder.build())
.addFileComment("butterknife 自动生成") //添加类的注释
.build().writeTo(mFiler);
}catch (Exception ex){
ex.printStackTrace();
}
}
return false;
}
private void log(String log){
System.out.println("MALEI: " + log);
}
}
最后在项目中使用:
@BkBindView(R.id.btn)
Button btn;
在看产生的代码:
结构目录: