在應(yīng)用 AOP 之前呻拌,應(yīng)該思考幾個(gè)問(wèn)題:
- 明確你應(yīng)用 AOP 在什么項(xiàng)目
小范圍試用萌朱,選擇一個(gè)侵入性小的 AOP 方法
- 明確切入點(diǎn)的相似性
考慮切入點(diǎn)的數(shù)量和相似性,確定你是否愿意一個(gè)個(gè)在切點(diǎn)上加注红省。 解還是用相似性統(tǒng)一切入额各。
- 明確織入的粒度和織入的時(shí)機(jī)
怎么選擇織入的時(shí)機(jī)?編譯期間織入吧恃,還是編譯后虾啦?載入時(shí)?或是運(yùn)行時(shí)痕寓?通過(guò)比較各大 AOP 方法在織入時(shí)機(jī)方面的不同和優(yōu)缺點(diǎn)傲醉,來(lái)獲得對(duì)于如何選擇織入時(shí)機(jī)進(jìn)行判定的準(zhǔn)則。
- 明確對(duì)性能的要求呻率,明確對(duì)方法數(shù)的要求
除了動(dòng)態(tài)織入硬毕,其他 AOP 方法對(duì)性能的影響可以忽略不計(jì),看各自方法的優(yōu)缺點(diǎn)進(jìn)行權(quán)衡礼仗。
明確是否需要修改原有類(lèi)
明確調(diào)用的時(shí)機(jī)
仿造 ButterKnife
步驟:
- 定義注解
- 編寫(xiě)注解處理器
- 掃描注解
- 編寫(xiě)代理類(lèi)內(nèi)容
- 生成代理類(lèi)
- 調(diào)用代理類(lèi)
第一步:
新建一個(gè)名為 annoation 的 Java Library吐咳,里面存放注解。
新建一個(gè)名為 compiler 的 Java Library元践,里面實(shí)現(xiàn) APT挪丢,compiler 引用 annoation。
新建一個(gè)名為 code 的 android Library卢厂,里面封裝對(duì) APT 調(diào)用的門(mén)面乾蓬,code 引用 annoation。
第二步:
在 annoation 中編寫(xiě)注解 BindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
第三步:
在 compiler 中編寫(xiě) APT 類(lèi) BindViewProcessor:
@AutoService(Processor.class) //自動(dòng)注冊(cè)
@SupportedAnnotationTypes("com.lzx.annoation.BindView") //指定解析注解
public class BindViewProcessor extends AbstractProcessor {
}
BindViewProcessor 類(lèi)要繼承 AbstractProcessor慎恒,并且添加 @AutoService 注解任内,參數(shù)填 Processor.class撵渡,這樣它就能自動(dòng)注冊(cè),為什么要自動(dòng)注冊(cè)死嗦,因?yàn)橄胍\(yùn)行注解處理器趋距,需要繁瑣的步驟:
- 在 processors 庫(kù)的 main 目錄下新建 resources 資源文件夾;
- 在 resources文件夾下建立 META-INF/services 目錄文件夾越除;
- 在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件节腐;
- 在 javax.annotation.processing.Processor 文件寫(xiě)入注解處理器的全稱(chēng),包括包路徑摘盆;
注解 @SupportedAnnotationTypes 參數(shù)是我們剛剛編寫(xiě)的 BindView 注解的路徑翼雀,這樣的意思是指定解析 BindView。
然后重寫(xiě) 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 方法里面孩擂,一般是給一些變量賦值狼渊。以上的寫(xiě)法可以作為模版寫(xiě)法,就是說(shuō)基本都會(huì)這樣寫(xiě)类垦。
重點(diǎn)在于重寫(xiě) process 方法:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor process");
//typeElement就相當(dāng)于com.lzx.annoation.BindView
//通過(guò)roundEnvironment來(lái)獲取所有被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節(jié)點(diǎn)元素
//element.getEnclosingElement()為獲取其父節(jié)點(diǎn)元素狈邑,即MainActivty
Element enclosingElement = element.getEnclosingElement();
TypeMirror classTypeMirror = enclosingElement.asType();
//className為MainActivty的全類(lèi)名
String className = classTypeMirror.toString();
//上面只是拿MainActivty舉個(gè)例子,但是真實(shí)的使用注解的可能還有SecondActivity等等蚤认,
// 所有需要以類(lèi)名為鍵保存里面所有使用了BindView注解的節(jié)點(diǎn)
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;
}
下面來(lái)一一分析一下:
typeElement 就相當(dāng)于 com.lzx.annoation.BindView米苹,通過(guò)遍歷 set 得到,通過(guò)roundEnvironment 來(lái)獲取所有被 BindView 注解注解了的字段:
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(typeElement);
遍歷所有被標(biāo)記字段:
for (Element element : elementsAnnotatedWith) {
//如果在 MainActivity 中有這段代碼 @BindView(R.id.textView) TextView textView;
//此處的 element 就是 TextView 節(jié)點(diǎn)元素
//element.getEnclosingElement() 為獲取其父節(jié)點(diǎn)元素砰琢,即 MainActivty
Element enclosingElement = element.getEnclosingElement();
TypeMirror classTypeMirror = enclosingElement.asType();
//className 為 MainActivty 的全類(lèi)名
String className = classTypeMirror.toString();
//上面只是拿 MainActivty 舉個(gè)例子驱入,但是真實(shí)的使用注解的可能還有 SecondActivity 等等,
//所以需要以類(lèi)名為鍵保存里面所有使用了 BindView 注解的節(jié)點(diǎn)
List<Element> elements = classMap.get(className);
if (elements == null) {
elements = new ArrayList<>();
elements.add(element);
classMap.put(className, elements);
} else {
elements.add(element);
}
}
接下來(lái)遍歷節(jié)點(diǎn)去生成代碼氯析,生成代碼是通過(guò) 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) {
// 生成類(lèi)元素節(jié)點(diǎn)
TypeElement classTypeElement = mElements.getTypeElement(key);
// 生成參數(shù) final MainActivity target
ParameterSpec targetParameterSpec = ParameterSpec
.builder(ClassName.get(classTypeElement), "target", Modifier.FINAL)
.build();
//生成參數(shù) View source
ParameterSpec viewParameterSpec = ParameterSpec
.builder(ClassName.get("android.view", "View"), "source")
.build();
MethodSpec methodSpec = null;
// 生成構(gòu)造函數(shù)
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(targetParameterSpec)
.addParameter(viewParameterSpec)
.addAnnotation(ClassName.bestGuess("android.support.annotation.UiThread"))
.addModifiers(Modifier.PUBLIC);
// 構(gòu)造函數(shù)中添加代碼塊
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類(lèi)
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();
//寫(xiě)入java文件
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到上面生成的類(lèi)的類(lèi)名是由 Activity 名字加上 _ViewBinding 組成的。
第四步:
在 code 中編寫(xiě)門(mén)面代碼:
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;
}
}
上面代碼的邏輯就是通過(guò) findBindingConstructorForClass 方法找到剛剛生成的類(lèi)莺褒。然后在 createBinding 方法中通過(guò)反射去實(shí)例化它掩缓,這當(dāng)中使用了緩存,提高了性能遵岩。
還有上面出現(xiàn)的 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;
}
}
對(duì)應(yīng)著生成的代碼再看剛剛的 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");
}
}
回過(guò)頭來(lái)看看 ButterKnife 是怎么使用 APT 的:
你可能發(fā)現(xiàn)了尘执,最后一個(gè)步驟是在合適的時(shí)機(jī)去調(diào)用代理類(lèi)或門(mén)面對(duì)象舍哄。這就是 APT 的缺點(diǎn)之一,在任意包位置自動(dòng)生成代碼但是運(yùn)行時(shí)卻需要主動(dòng)調(diào)用誊锭。
參考文章:一文應(yīng)用 AOP | 最全選型考量 + 邊剖析經(jīng)典開(kāi)源庫(kù)邊實(shí)踐表悬,美滋滋