AOP学习总结-利用APT仿写ButterKnife
在应用 AOP 之前,应该思考几个问题:
- 明确你应用 AOP 在什么项目
小范围试用,选择一个侵入性小的 AOP 方法
- 明确切入点的相似性
考虑切入点的数量和相似性,确定你是否愿意一个个在切点上加注。 解还是用相似性统一切入。
- 明确织入的粒度和织入的时机
怎么选择织入的时机?编译期间织入,还是编译后?载入时?或是运行时?通过比较各大 AOP 方法在织入时机方面的不同和优缺点,来获得对于如何选择织入时机进行判定的准则。
- 明确对性能的要求,明确对方法数的要求
除了动态织入,其他 AOP 方法对性能的影响可以忽略不计,看各自方法的优缺点进行权衡。
-
明确是否需要修改原有类
-
明确调用的时机
仿造 ButterKnife
步骤:
- 定义注解
- 编写注解处理器
- 扫描注解
- 编写代理类内容
- 生成代理类
- 调用代理类
第一步:
新建一个名为 annoation 的 Java Library,里面存放注解。
新建一个名为 compiler 的 Java Library,里面实现 APT,compiler 引用 annoation。
新建一个名为 code 的 android Library,里面封装对 APT 调用的门面,code 引用 annoation。
第二步:
在 annoation 中编写注解 BindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
第三步:
在 compiler 中编写 APT 类 BindViewProcessor:
@AutoService(Processor.class) //自动注册
@SupportedAnnotationTypes("com.lzx.annoation.BindView") //指定解析注解
public class BindViewProcessor extends AbstractProcessor {
}
BindViewProcessor 类要继承 AbstractProcessor,并且添加 @AutoService 注解,参数填 Processor.class,这样它就能自动注册,为什么要自动注册,因为想要运行注解处理器,需要繁琐的步骤:
- 在 processors 库的 main 目录下新建 resources 资源文件夹;
- 在 resources文件夹下建立 META-INF/services 目录文件夹;
- 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
- 在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;
注解 @SupportedAnnotationTypes 参数是我们刚刚编写的 BindView 注解的路径,这样的意思是指定解析 BindView。
然后重写 init 方法:
private Messager mMessager;
private Filer mFiler;
private Elements mElements;
private Map<String, List<Element>> classMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mFiler = processingEnvironment.getFiler();
mElements = processingEnvironment.getElementUtils();
mMessager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor init");
}
在 init 方法里面,一般是给一些变量赋值。以上的写法可以作为模版写法,就是说基本都会这样写。
重点在于重写 process 方法:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor process");
//typeElement就相当于com.lzx.annoation.BindView
//通过roundEnvironment来获取所有被BindView注解注解了的字段
for (TypeElement typeElement : set) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "typeElement = " + typeElement.toString());
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(typeElement);
for (Element element : elementsAnnotatedWith) {
//如果在MainActivity中有这段代码 @BindView(R.id.textView)TextView textView;
//此处的element就是TextView节点元素
//element.getEnclosingElement()为获取其父节点元素,即MainActivty
Element enclosingElement = element.getEnclosingElement();
TypeMirror classTypeMirror = enclosingElement.asType();
//className为MainActivty的全类名
String className = classTypeMirror.toString();
//上面只是拿MainActivty举个例子,但是真实的使用注解的可能还有SecondActivity等等,
// 所有需要以类名为键保存里面所有使用了BindView注解的节点
List<Element> elements = classMap.get(className);
if (elements == null) {
elements = new ArrayList<>();
elements.add(element);
classMap.put(className, elements);
} else {
elements.add(element);
}
}
Set<Map.Entry<String, List<Element>>> entries = classMap.entrySet();
for (Map.Entry<String, List<Element>> entry : entries) {
String key = entry.getKey();
List<Element> value = entry.getValue();
//生成java代码
generateViewBinding(key, value);
}
}
return false;
}
下面来一一分析一下:
typeElement 就相当于 com.lzx.annoation.BindView,通过遍历 set 得到,通过roundEnvironment 来获取所有被 BindView 注解注解了的字段:
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(typeElement);
遍历所有被标记字段:
for (Element element : elementsAnnotatedWith) {
//如果在 MainActivity 中有这段代码 @BindView(R.id.textView) TextView textView;
//此处的 element 就是 TextView 节点元素
//element.getEnclosingElement() 为获取其父节点元素,即 MainActivty
Element enclosingElement = element.getEnclosingElement();
TypeMirror classTypeMirror = enclosingElement.asType();
//className 为 MainActivty 的全类名
String className = classTypeMirror.toString();
//上面只是拿 MainActivty 举个例子,但是真实的使用注解的可能还有 SecondActivity 等等,
//所以需要以类名为键保存里面所有使用了 BindView 注解的节点
List<Element> elements = classMap.get(className);
if (elements == null) {
elements = new ArrayList<>();
elements.add(element);
classMap.put(className, elements);
} else {
elements.add(element);
}
}
接下来遍历节点去生成代码,生成代码是通过 JavaPoet 框架去完成:
Set<Map.Entry<String, List<Element>>> entries = classMap.entrySet();
for (Map.Entry<String, List<Element>> entry : entries) {
String key = entry.getKey();
List<Element> value = entry.getValue();
//生成java代码
generateViewBinding(key, value);
}
generateViewBinding方法:
private void generateViewBinding(String key, List<Element> value) {
// 生成类元素节点
TypeElement classTypeElement = mElements.getTypeElement(key);
// 生成参数 final MainActivity target
ParameterSpec targetParameterSpec = ParameterSpec
.builder(ClassName.get(classTypeElement), "target", Modifier.FINAL)
.build();
//生成参数 View source
ParameterSpec viewParameterSpec = ParameterSpec
.builder(ClassName.get("android.view", "View"), "source")
.build();
MethodSpec methodSpec = null;
// 生成构造函数
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(targetParameterSpec)
.addParameter(viewParameterSpec)
.addAnnotation(ClassName.bestGuess("android.support.annotation.UiThread"))
.addModifiers(Modifier.PUBLIC);
// 构造函数中添加代码块
constructorMethodBuilder.addStatement("this.target = target");
for (Element element : value) {
BindView bindView = element.getAnnotation(BindView.class);
int id = bindView.value();
Name simpleName = element.getSimpleName();
constructorMethodBuilder.addStatement("target.$L = source.findViewById($L)", simpleName.toString(), id);
}
methodSpec = constructorMethodBuilder.build();
// 生成unbind方法
MethodSpec.Builder unbindMethodSpec = MethodSpec.methodBuilder("unbind")
.addModifiers(Modifier.PUBLIC);
unbindMethodSpec.addStatement("$T target = this.target", ClassName.get(classTypeElement));
unbindMethodSpec.addStatement("this.target = null");
for (Element element : value) {
Name simpleName = element.getSimpleName();
unbindMethodSpec.addStatement("target.$L = null", simpleName.toString());
}
// 生成MainActivity_ViewBinding类
TypeSpec typeSpec = TypeSpec.classBuilder(classTypeElement.getSimpleName() + "_ViewBinding")
.addField(ClassName.get(classTypeElement), "target", Modifier.PRIVATE)
.addMethod(methodSpec)
.addMethod(unbindMethodSpec.build())
.addSuperinterface(ClassName.bestGuess("com.lzx.code.Unbinder"))
.addModifiers(Modifier.PUBLIC)
.build();
// 获取包名
String packageName = mElements.getPackageOf(classTypeElement).getQualifiedName().toString();
mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName = " + packageName);
JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
//写入java文件
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到上面生成的类的类名是由 Activity 名字加上 _ViewBinding 组成的。
第四步:
在 code 中编写门面代码:
ButterKnife:
public class ButterKnife {
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@NonNull
@UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
public static Unbinder bind(@NonNull Fragment target, View sourceView){
return createBinding(target,sourceView);
}
public static Unbinder bind(@NonNull android.app.Fragment target, View sourceView){
return createBinding(target,sourceView);
}
private static Unbinder createBinding(Object target, View sourceView) {
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
try {
return constructor.newInstance(target, sourceView);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> targetClass) {
Constructor<? extends Unbinder> constructor = BINDINGS.get(targetClass);
if (constructor != null) {
return constructor;
}
String targetClassName = targetClass.getName();
try {
Class<?> viewBindingClass = Class.forName(targetClassName + "_ViewBinding");
constructor = (Constructor<? extends Unbinder>) viewBindingClass.getConstructor(targetClass, View.class);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
BINDINGS.put(targetClass, constructor);
return constructor;
}
}
上面代码的逻辑就是通过 findBindingConstructorForClass 方法找到刚刚生成的类。然后在 createBinding 方法中通过反射去实例化它,这当中使用了缓存,提高了性能。
还有上面出现的 Unbinder 接口:
public interface Unbinder {
@UiThread
void unbind();
Unbinder EMPTY = new Unbinder() {
@Override public void unbind() { }
};
}
第五步:
在 APP 工程去引用上面的代码:
dependencies {
implementation project(':code')
annotationProcessor project(':compiler')
}
最后看看生成的代码是怎么样的:
MainActivity_ViewBinding:
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
target.mTextView = source.findViewById(2131165209);
}
public void unbind() {
MainActivity target = this.target;
this.target = null;
target.mTextView = null;
}
}
对应着生成的代码再看刚刚的 generateViewBinding 方法,是否就一目了然。
看看如何使用:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.add) TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mTextView.setText("Hello APT");
}
}
回过头来看看 ButterKnife 是怎么使用 APT 的:
15572231486342.jpg你可能发现了,最后一个步骤是在合适的时机去调用代理类或门面对象。这就是 APT 的缺点之一,在任意包位置自动生成代码但是运行时却需要主动调用。