前言
話說在android這座大山里,有一座廟(方塊公司-square),廟里住著一個神-jake(我是這么叫的嘻嘻)。
不要小看這個小jake,這個神可是為android應用開發(fā)們提供了強有力的幫助。比如流行的開源庫okhttp,retrofit,leakcanary,butterknife 等等都是出于他之手。小弟佩服的不要不要的...,可以說是為android的應用開發(fā)效率和耦合性提高了一個臺階啊。
其它的大神我也是佩服的不要不要的...嘻嘻
聲明
這一系列的文章是對ButterKnife的源碼進行分析的,涉及的細節(jié)比較多,但也比較廣。一次看不懂no 問題,慢慢來嘛,大家互相討論學習,嘻嘻
如有不對的地方,還望指導。
最后會給出自己實現(xiàn)的一個demo,主要是原理和思想哦?。?!
代碼結構
我們這里對ButterKnife的最新版本8.4.0進行分析。
我們先down下來看下代碼的結構,可以看到代碼結構分的還是很好的。
- butterknife ;android library model 提供android使用的API
- butterknife-annotations; java-model,使用時的注解
- butterknife-compiler;java-model,編譯時用到的注解的處理器
- butterknife-gradle-plugin;自定義的gradle插件,輔助生成有關代碼
- butterknife-integration-test;該項目的測試用例
- butterknife-lint;該項目的lint檢查
- sample;demo
可以看到大神的代碼很風騷的,很清晰啊。
這里重點分析butterknife-compiler及butterknife
原理圖
你可以看完了再回來看這個圖,會更明白。
使用方法
這里不再給出。不過會在原代碼分析的時候給出一些注意的地方。
我們拿官方的demo-SimpleActivity
編譯完后最后生成的文件為:SimpleActivity_ViewBinding
路徑在:
內容:
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {
protected T target;
private View view2130968578;
private View view2130968579;
@UiThread
public SimpleActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);
target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class);
view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
view2130968578 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.sayHello();
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p0) {
return target.sayGetOffMe();
}
});
view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'");
target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class);
view2130968579 = view;
((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
target.onItemClick(p2);
}
});
target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class);
target.headerViews = Utils.listOf(
Utils.findRequiredView(source, R.id.title, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"),
Utils.findRequiredView(source, R.id.hello, "field 'headerViews'"));
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.title = null;
target.subtitle = null;
target.hello = null;
target.listOfThings = null;
target.footer = null;
target.headerViews = null;
view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578 = null;
((AdapterView<?>) view2130968579).setOnItemClickListener(null);
view2130968579 = null;
this.target = null;
}
}
我們看到了我們熟悉的代碼,雖然比較亂(因為是生成的),
可以看出 在構造中findview 在unbind中進行置null處理,讓告訴gc在合適的機會回收占用的內存 ;但是這是后面真正生成代碼我們看不到的,沒關系 嘻嘻。
整體的原理-(編譯時期-注解處理器)
在java代碼的編譯時期,javac 會調用java注解處理器來進行處理。因此我們可以定義自己的注解處理器來干一些事情。一個特定注解的處理器以 java 源代碼(或者已編譯的字節(jié)碼)作為輸入,然后生成一些文件(通常是.java文件)作為輸出。因此我們可以在用戶已有的代碼上添加一些方法,來幫我們做一些有用的事情。這些生成的 java 文件跟其他手動編寫的 java 源代碼一樣,將會被 javac 編譯。(個人參考及個人理解)
定義處理器 繼承AbstractProcessor
在java中定義自己的處理器都是繼承自AbstractProcessor
前3個方法都試固定寫法,主要是process方法。
public class MyProcessor extends AbstractProcessor {
//用來指定你使用的 java 版本。通常你應該返回 SourceVersion.latestSupported()
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//會被處理器調用,可以在這里獲取Filer,Elements,Messager等輔助類,后面會解釋
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
//這個方法返回stirng類型的set集合,集合里包含了你需要處理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add("com.example.MyAnnotation");
return annotataions;
}
//核心方法,這個一般的流程就是先掃描查找注解,再生成 java 文件
//這2個步驟設計的知識點細節(jié)很多。
@Override
public boolean process(Set<? extends TypeElement> annoations,
RoundEnvironment env) {
return false;
}
}
注冊你的處理器
要像jvm調用你寫的處理器,你必須先注冊,讓他知道。怎么讓它知道呢,其實很簡單,google 為我們提供了一個庫,簡單的一個注解就可以。
首先是依賴
compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
//...省略非關鍵代碼
}
基本概念
- Elements:一個用來處理Element的工具類
- Types:一個用來處理TypeMirror的工具類
- Filer:你可以使用這個類來創(chuàng)建.java文件
源碼分析
分析之前呢先要有寫基本的概念
可以看到AutoService注解
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
//...
}
(1)init 方法,這個主要是獲取一些輔助類
private Filer mFiler; //文件相關的輔助類
private Elements mElementUtils; //元素相關的輔助類
private Messager mMessager; //日志相關的輔助類
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
try {
trees = Trees.instance(processingEnv);
} catch (IllegalArgumentException ignored) {
}
}
(2)getSupportedSourceVersion()方法就是默認的,獲取你該處理器使用的java版本
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
(4)接下來就是process方法,這個方法是核心方法。
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//1.查找所有的注解信息,并形成BindingClass(是什么 后面會講) 保存到 map中
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
//2.遍歷步驟1的map 的生成.java文件也就是上文的 類名_ViewBinding 的java文件
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
JavaFile javaFile = bindingClass.brewJava();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}
return true;
}
咦,很明顯,2步走啊。是的沒錯, 滿滿套路?。?!大家都回農(nóng)村吧,嘻嘻
下面我們仔細走一下該方法流程。
- 每個注解的查找與解析-findAndParseTargets
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
scanForRClasses(env);
//...下面是每個注解的解析
// 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 @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
// we don't SuperficialValidation.validateElement(element)
// so that an unresolved View type can be generated by later processing rounds
try {
parseBindView(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
//....
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, targetClassMap, erasedTargetNames);
}
// Try to find a parent binder for each.
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);
if (parentType != null) {
BindingClass bindingClass = entry.getValue();
BindingClass parentBindingClass = targetClassMap.get(parentType);
bindingClass.setParent(parentBindingClass);
}
}
return targetClassMap;
}
首先我們先看一下參數(shù) RoundEnvironment 這個是什么呢?個人理解是注解框架里的一個工具什么工具呢?一個可以在處理器處理該處理器 用來查詢注解信息的工具,當然包含你在getSupportedAnnotationTypes注冊的注解。
接下來呢?
創(chuàng)建了一個LinkedHashMap保證了先后的順序就是先注解的先生成java文件,其實也沒有什么先后無所謂。最后將其返回。
我們這里只分析一個具有代表性的BindView注解,其它的都是一樣的,連代碼都一毛一樣。
在這之前先看一下Element這個類我們看一下官方注釋
* Represents a program element such as a package, class, or method.
* Each element represents a static, language-level construct
* (and not, for example, a runtime construct of the virtual machine).
在注解處理器中,我們掃描 java 源文件,源代碼中的每一部分都是Element的一個特定類型。換句話說:Element代表程序中的元素,比如說 包,類,方法。每一個元素代表一個靜態(tài)的,語言級別的結構.
比如:
public class ClassA { // TypeElement
private int var_0; // VariableElement
public ClassA() {} // ExecuteableElement
public void setA( // ExecuteableElement
int newA // TypeElement
) {
}
}
可以看到類為TypeElement,變量為VariableElement,方法為ExecuteableElement
這些都是Element的子類,自己可以看下源碼,的確如此的。
TypeElement aClass ;
for (Element e : aClass.getEnclosedElements()){ //獲取所有的子節(jié)點
Element parent = e.getEnclosingElement(); // 獲取父節(jié)點
}
Elements代表源代碼,TypeElement代表源代碼中的元素類型,例如類。然后,TypeElement并不包含類的相關信息。你可以從TypeElement獲取類的名稱,但你不能獲取類的信息,比如說父類。這些信息可以通過TypeMirror獲取。你可以通過調用element.asType()來獲取一個Element的TypeMirror。
這個是對于理解源碼的基礎。
繼續(xù),我們看到了for循環(huán)查找所有包含BindView的注解。
parseBindView(element, targetClassMap, erasedTargetNames);
把element和targetClassMap傳入
private void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//1.檢查用戶使用的合法性
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
if (elementType.getKind() == TypeKind.ERROR) {
note(element, "@%s field with unresolved type (%s) "
+ "must elsewhere be generated as a View or interface. (%s.%s)",
BindView.class.getSimpleName(), elementType, enclosingElement.getQualifiedName(),
element.getSimpleName());
} else {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
}
// 不合法的直接返回
if (hasError) {
return;
}
//2.獲取id 值
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
// 3.獲取 BindingClass,有緩存機制, 沒有則創(chuàng)建,下文會仔細分析
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass != null) {
ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));
if (viewBindings != null && viewBindings.getFieldBinding() != null) {
FieldViewBinding existingBinding = viewBindings.getFieldBinding();
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
BindView.class.getSimpleName(), id, existingBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
} else {
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
}
//4 生成FieldViewBinding 實體
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
FieldViewBinding binding = new FieldViewBinding(name, type, required);
//5。加入到 bindingClass 成員變量的集合中
bindingClass.addField(getId(id), binding);
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
element.getEnclosingElement();是什么呢?是父節(jié)點。就是上面我們說的。
基本的步驟就是上面的5步
1.檢查用戶使用的合法性
// Start by verifying common generated code restrictions.
boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
|| isBindingInWrongPackage(BindView.class, element);
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
String targetThing, Element element) {
boolean hasError = false;
// 得到父節(jié)點
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
//判斷修飾符,如果包含private or static 就會拋出異常。
// Verify method modifiers.
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
error(element, "@%s %s must not be private or static. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//判斷父節(jié)點是否是類類型的,不是的話就會拋出異常
//也就是說BindView 的使用必須在一個類里
// Verify containing type.
if (enclosingElement.getKind() != CLASS) {
error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
//判斷父節(jié)點如果是private 類,則拋出異常
// Verify containing class visibility is not private.
if (enclosingElement.getModifiers().contains(PRIVATE)) {
error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
return hasError;
}
上面的代碼里遇見注釋了,這里說一下也就是我們在使用bindview注解的時候不能用使用
類不能是private修飾 ,可以是默認的或者public
//in adapter
private static final class ViewHolder {
//....
}
//成員變量不能是private修飾 ,可以是默認的或者public
@BindView(R.id.word)
private TextView word;
接下來還有一個方法isBindingInWrongPackage。
這個看名字也才出來個大概 就是不能在android ,java這種源碼的sdk中使用。如果你的包名是以android或者java開頭就會拋出異常。
private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
Element element) {
//得到父節(jié)點
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
if (qualifiedName.startsWith("android.")) {
error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
if (qualifiedName.startsWith("java.")) {
error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName);
return true;
}
return false;
}
到這里合法性檢查就完了,如果你使用不當,就會拋出異常。
// 。。。 異常拋出了
// 不合法的直接返回
if (hasError) {
return;
}
這里說明一點,在處理器中拋出異常,你不能直接像平常寫java代碼一樣new thow xxx 一樣,這樣拋出去的異常不太好看。所以java處理器幫我們提供了一個輔助類Messager,這個可以幫助我們
/**
* Returns the messager used to report errors, warnings, and other
* notices.
*
* @return the messager
*/
Messager getMessager();
比如檢查使用合法性拋出的異常信息-error方法最后都會調用
private void error(Element element, String message, Object... args) {
//Kind.ERROR 級別,就像你使用android的log一樣
printMessage(Kind.ERROR, element, message, args);
}
private void note(Element element, String message, Object... args) {
printMessage(Kind.NOTE, element, message, args);
}
private void printMessage(Kind kind, Element element, String message, Object[] args) {
if (args.length > 0) {
message = String.format(message, args);
}
processingEnv.getMessager().printMessage(kind, message, element);
}
ok,到這里說了這么多才完成了檢查。我們接著parseBindView的步驟2 獲取值 ,這里就不多說了。好了,先休息下吧...
下一篇我們接著看。
深入理解ButterKnife源碼并掌握原理(二)