編譯期註解框架淺析
簡介
由於Android開發已經進入一定規模,所以開發效率和代碼的簡潔開始引發人們的注意,而android對於性能要求比較高,所以基於反射已經無法滿足,所以編譯期註解也就火了起來。
首先理解下編譯期註解的原理,編譯期註解就是在代碼編譯後會生成一些全新的代碼,而在執行階段,就會使用這些生成的代碼。所以編譯期註解的本質就是“生成代碼”,如何生成,說了可能都不相信,是拿字符串拼接出的。所以編譯期註解不是解決一些問題的方法而僅僅是爲了簡化代碼的方案。如果你願意多寫一些代碼,完全可以不使用它。
編譯期註解要素
1.註解
2.處理註解的類(也是這個類生成代碼)
3.api接口,這裏要注意了編譯期註解怎麼也是一個靜態動作,不是執行時候做的,所以你想讓生成的代碼工作肯定得調api了
白話流程
這裏指的是我們的代碼寫完之後,框架的執行流程
首先,編譯期代碼的時候,會生成一份規定格式的代碼(後面簡稱”苦逼類”),然後當我們調用某個api的時候,就會通過反射去生成那個”苦逼類”,然後去執行對應的方法,但執行哪個方法呢,肯定是有個接口類了,無論我們生成哪個”苦逼類”都會掉哪個接口方法。
代碼操練
由於我比較懶,就不寫了,找到butterknife的幾個核心類的核心方法,貼出來,到時候大家修改寫也能寫個。
首先是註解:
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
處理註解的類:
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
trees = Trees.instance(processingEnv);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class);
return types;
}
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
for (JavaFile javaFile : bindingClass.brewJava()) {
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
}
return true;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
ok,主要就是這四個方法,其中生成代碼,使用了com.squareup.javapoet.JavaFile這個類幫助完成。
執行api:
public final class ButterKnife {
public static Unbinder bind(@NonNull Activity target) {
Class<?> targetClass = target.getClass();
ViewBinder<Object> viewBinder = BINDERS.get(cls);
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder.bind(Finder.DIALOG, target, source);
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER.bind(Finder.DIALOG, target, source);
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
Class<?> viewBindingClass = Class.forName(clsName + "_ViewBinder");
//noinspection unchecked
viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
BINDERS.put(cls, viewBinder);
return viewBinder.bind(Finder.DIALOG, target, source);;
}
}
其實就是通過類名反射生成一個”苦逼類”對象,然後執行它的bind方法。
說好的接口:
public interface ViewBinder<T> {
Unbinder bind(Finder finder, T target, Object source);
}
最後讓我們來看下我生成的一個“苦逼類”源碼
// Generated code from Butter Knife. Do not modify!
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.Finder;
import butterknife.internal.ViewBinder;
import java.lang.IllegalStateException;
import java.lang.Object;
import java.lang.Override;
public class TestActivity$$ViewBinder<T extends TestActivity> implements ViewBinder<T> {
@Override
public Unbinder bind(Finder finder, T target, Object source) {
return new InnerUnbinder<>(target, finder, source);
}
protected static class InnerUnbinder<T extends TestActivity> implements Unbinder {
protected T target;
protected InnerUnbinder(T target, Finder finder, Object source) {
this.target = target;
target.mtv = finder.findRequiredViewAsType(source, 2131492981, "field 'mtv'", TextView.class);
}
@Override
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.mtv = null;
this.target = null;
}
}
}
大家可以看到是實現了上面那個接口吧。
ok,原理其實並不複雜,希望大家能多用編譯期註解時框架,寫出更多簡潔的代碼。