一:基本原理
編譯時注解+APT
編譯時注解:Rentention為CLASS的直接施禾。保留時間是編譯期。
APT(Annotation Processing Tool)編譯時解析技術(shù))屎媳。
聲明的注解的生命周期為CLASS,然后創(chuàng)建注解處理器抹蚀,即繼承AbstractProcessor類剿牺。繼承這個類后企垦,在編譯的時候环壤,編譯器會掃描所有帶有你要處理的注解的類,然后再調(diào)用AbstractProcessor的process方法钞诡,對注解進行處理郑现,處理完后,動態(tài)生成綁定事件或者控件的java代碼荧降,然后在運行的時候接箫,直接調(diào)用bind
方法完成綁定。
二:bind過程
(1)@Bind注解
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
int[] value();
}
(2)ButterKnife.bind()
public static void bind(Activity target) {
bind(target, target, Finder.ACTIVITY);
}
static void bind(Object target, Object source, Finder finder) {
Class<?> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
if (viewBinder != null) {
viewBinder.bind(finder, target, source);
}
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}
(1) 獲取Activity的class對象
(2)通過findViewBinderForClass()找到該Activity對應的ViewBinder
(3)調(diào)用ViewBinder.bind()方法
(3) ButterKnife.findViewBinderForClass()
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
throws IllegalAccessException, InstantiationException {
ViewBinder<Object> viewBinder = BINDERS.get(cls);
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder;
}
String clsName = cls.getName();
if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER;
}
try {
Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
//noinspection unchecked
viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
viewBinder = findViewBinderForClass(cls.getSuperclass());
}
BINDERS.put(cls, viewBinder);
return viewBinder;
}
(1)從緩存LinkedHashMap中取ViewBinder朵诫,若能取出辛友,則說明ViewBinder已經(jīng)生成過。
(2) 否則 得到ViewBinder的子類的類名(MainActivity$$ViewBinder),然后通過反射的形式創(chuàng)建(MainActivity$$ViewBinder)的實例废累,并存入緩存邓梅。
Tips: ViewBinder
public interface ViewBinder<T> {
void bind(Finder finder, T target, Object source);
void unbind(T target);
}
舉例說明
public class MainActivity extends AppCompatActivity {
@Bind(R.id.text_view)
TextView textView;
@OnClick(R.id.text_view)
void onClick(View view) {
textView.setText("我被click了");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
textView.setText("我還沒有被click");
}
}
反編譯代碼
源代碼就多了一個類MainActivity$$ViewBinder
public class MainActivity$$ViewBinder<T extends MainActivity>
implements ButterKnife.ViewBinder<T>{
public void bind(ButterKnife.Finder paramFinder,
final T paramT, Object paramObject) {
View localView = (View)paramFinder.findRequiredView(paramObject,
2131492944, "field 'textView' and method 'onClick'");
paramT.textView = ((TextView)paramFinder.castView(localView,
2131492944, "field 'textView'"));
localView.setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View paramAnonymousView) {
paramT.onClick(paramAnonymousView);
}
});
}
public void unbind(T paramT) {
paramT.textView = null;
}
}
首先調(diào)用了Finder的findRequiredView方法,其實這個方法最后經(jīng)過處理就是調(diào)用了findView方法邑滨,拿到相應的view日缨,然后再賦值給paramT.textView,paramT就是那個要綁定的Activity
(4)ButterKnife.Finder(enum)
public enum Finder {
VIEW {
@Override protected View findView(Object source, int id) {
return ((View) source).findViewById(id);
}
@Override public Context getContext(Object source) {
return ((View) source).getContext();
}
},
ACTIVITY {
@Override protected View findView(Object source, int id) {
return ((Activity) source).findViewById(id);
}
@Override public Context getContext(Object source) {
return (Activity) source;
}
},
DIALOG {
@Override protected View findView(Object source, int id) {
return ((Dialog) source).findViewById(id);
}
@Override public Context getContext(Object source) {
return ((Dialog) source).getContext();
}
};
三:APT編譯期生成代碼的流程
ButterknifeProcessor:注解處理器繼承于AbstractProcessor
(1)在實現(xiàn)AbstractProcessor后掖看,process()方法是必須實現(xiàn)的
(2)一般會實現(xiàn)getSupportedAnnotationTypes()和getSupportedSourceVersion()匣距;這兩個方法一個返回支持的注解類型,一個返回支持的源碼版本哎壳,參考上面的代碼毅待,寫法基本是固定的。
(3)除此以外归榕,我們還會選擇復寫init()方法恩静,該方法傳入一個參數(shù)processingEnv,可以幫助我們?nèi)コ跏蓟恍┹o助類:
Filer mFileUtils: 跟文件相關的輔助類,生成JavaSourceCode.
Elements mElementUtils:跟元素相關的輔助類蹲坷,幫助我們?nèi)カ@取一些元素相關的信息驶乾。
Messager mMessager:跟日志相關的輔助類。
(1) ButterknifeProcessor.int()
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
}
(2) ButterknifeProcessor.getSupportAnnotationTypes()
BindArray:一個類對象循签,代表具體某個類的代理類生成的全部信息
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindArray.class.getCanonicalName());
types.add(BindBitmap.class.getCanonicalName());
types.add(BindBool.class.getCanonicalName());
types.add(BindColor.class.getCanonicalName());
types.add(BindDimen.class.getCanonicalName());
types.add(BindDrawable.class.getCanonicalName());
types.add(BindInt.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
types.add(BindView.class.getCanonicalName());
types.add(BindViews.class.getCanonicalName());
for (Class<? extends Annotation> listener : LISTENERS) {
types.add(listener.getCanonicalName());
}
return types;
}
(2) ButterknifeProcessor.process()
這個方法的作用主要是掃描级乐、評估和處理我們程序中的注解,然后生成Java文件县匠。即ViewBinder风科。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//下面這一句解析注解
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
//解析完成以后,需要生成的代碼結(jié)構(gòu)已經(jīng)都有了乞旦,它們都存在于每一個 BindingClass 當中
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
try {
bindingClass.brewJava().writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
(2) ButterknifeProcessor.findAndParseTargets()
(1)掃描所有具有注解的類贼穆,然后根據(jù)這些類的信息生成BindingClass,最后生成以TypeElement為鍵,BindingClass為值的鍵值對兰粉。
(2)循環(huán)遍歷這個鍵值對故痊,根據(jù)TypeElement和BindingClass里面的信息生成對應的java類。例如AnnotationActivity生成的類即為Cliass$$ViewBinder類玖姑。
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// Process each @BindArray element.
for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceArray(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindArray.class, e);
}
}
// Process each @BindBitmap element.
for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseResourceBitmap(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindBitmap.class, e);
}
}
......
四:Javapoet:生成Java代碼
(1)以代碼拼接的方式
(2)TypeSpec代表類愕秫,MethodSpec代表方法(Builder模式)
TypeSpec.Builder result = TypeSpec.classBuilder(className)
//添加修飾符為 public,生成的類是 public 的
.addModifiers(PUBLIC)
.addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
MethodSpec.Builder result = MethodSpec.methodBuilder("bind")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(FINDER, "finder", FINAL)
.addParameter(TypeVariableName.get("T"), "target", FINAL)
.addParameter(Object.class, "source");
五:ButterKnife模塊
ioc-annotation 用于存放注解等焰络,Java模塊
ioc-compiler 用于編寫注解處理器戴甩,Java模塊
ioc-api 用于給用戶提供使用的API,本例為Andriod模塊
ioc-sample 示例闪彼,本例為Andriod模塊
六:解析注解的流程(收集注解的流程)
(1)roundEnv.getElementsAnnotatedWith(BindView.class)拿到所有@BindView注解的成員變量
(2)循環(huán)每一個成員變量甜孤,進行檢查。
(3)獲取該成員變量的Element對應得類TypeElement及全路徑名.
(4)創(chuàng)建類(proxyInfo)封裝一個類的注解信息。
(4)把該成員變量對應的注解存入proxyInfo(key:id缴川,value:variableElement)
private Map<String, ProxyInfo> mProxyMap = new HashMap<String, ProxyInfo>();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
mProxyMap.clear();
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
//一囱稽、收集信息
for (Element element : elements){
//檢查element類型
if (!checkAnnotationUseValid(element)){
return false;
}
//field type
VariableElement variableElement = (VariableElement) element;
//class type
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();//TypeElement
String qualifiedName = typeElement.getQualifiedName().toString();
ProxyInfo proxyInfo = mProxyMap.get(qualifiedName);
if (proxyInfo == null){
proxyInfo = new ProxyInfo(mElementUtils, typeElement);
mProxyMap.put(qualifiedName, proxyInfo);
}
BindView annotation = variableElement.getAnnotation(BindView.class);
int id = annotation.value();
proxyInfo.mInjectElements.put(id, variableElement);
}
return true;
}