Android高級開發進階之路2——手寫butterknife(註解,註解處理器,類加載器)
首先我們來簡單講講ButterKnife的工作過程:
引入庫:
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
使用:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_butterknife)
TextView tvButterknife;
//...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
當我們執行build操作的之後,Android工程經過編譯器的編譯,java文件會被生成class文件,ButterKnife會在以下下目錄生成相應的文件,並且幫我們findviewbyid。
那麼這個文件是怎麼跟Activity綁定關係的呢?
是通過Activity中的ButterKnife.bind(this);來進行綁定的。
public final class ButterKnife {
/**
* BindView annotated fields and methods in the specified {@code target} using the {@code source}
* {@link Dialog} as the view root.
*
* @param target Target class for view binding.
* @param source Dialog on which IDs will be looked up.
*/
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
View sourceView = source.getWindow().getDecorView();
return createBinding(target, sourceView);
}
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
} 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);
}
}
}
------------------------------------------------------------------------------------------------------------------
好了,下面我們來看一下如何手寫一個ButterKnife?
首先我們要明白兩個概念:註解和註解處理器
註解:相當於一個牌子
註解處理器:相當於一個識別牌子的機器
- 新建註解庫
- 註解庫中新建註解BindView
- 新建IBinder接口類,主要的作用就是接口化編程,令編譯器生成的class文件轉化成改接口的實現類
- 新建註解編譯器庫,庫中的build.gradle文件必須引入兩個庫(
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' compileOnly 'com.google.auto.service:auto-service:1.0-rc4' )
- 新建註解編譯器,複寫其中重要的4個方法init();getSupportedAnnotationTypes();getSupportedSourceVersion();process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)在process方法中使用filer對象生成一個類文件。要注意的地方是:這個文件必須extends AbstractProcessor並且,這個類一定要使用@AutoService(Processor.class)進行註解。
- 在註解庫中新建一個MyButterKnife類,用於綁定Activity對象。通過Class.forName("{註解編譯器生成的類文件完整的ClassName}")進行類加載,得到勢力,把activity對象傳入其中來綁定關係。
- 在Activity中使用註解,並且用MyButterKnife在setContent之後綁定關係
新建-註解庫
庫裏聲明註解BindView
@Target(ElementType.FIELD)//聲明註解作用域
@Retention(RetentionPolicy.SOURCE)//聲明註解聲明週期
public @interface BindView {
int value();
}
定義一個IBind<T>接口,生成的class文件需要實現的這個接口,定義一個方法來接收Activity對象
public interface IBind<T> {
void bind(T target);
}
新建-註解編譯器庫
重點:新建一個註解編譯器類AnnotationCompiler,具體說明看註釋。
/**
* 註解處理器 生成Activity對應的類來綁定view
*/
@AutoService(Processor.class)//必須通過該註解來註冊註解處理器,否則無效
public class AnnotationCompiler extends AbstractProcessor {
//生成文件的對象
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
/**
* 聲明這個註解處理器需要處理的註解
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
/**
* 聲明當情註解處理器支持的java版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
/**
* 在這個方法裏生成我們需要的文件
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//拿到應用裏面所有用到BindView的節點,
//關於節點,有類節點,成員變量節點,方法節點,一個樹形結構來的
Set<? extends Element> elementsAnnotation = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//將所有的節點根據不同的文件分開
Map<String, List<Element>> map = new HashMap<>();
for (Element element :
elementsAnnotation) {
//獲取成員變量的節點
VariableElement variableElement = (VariableElement) element;
//獲取類節點
Element elementClass = variableElement.getEnclosingElement();
String className = elementClass.getSimpleName().toString();
if (map.get(className) == null) {
map.put(className, new ArrayList<Element>());
}
map.get(className).add(variableElement);
}
if (map.size() > 0) {
//k開始寫文件
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String activityName = iterator.next();
List<Element> variableElements = map.get(activityName);
//通過獲取成員變量的節點獲取到上一個節點,也就是類節點
TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
//通過類節點,獲取到類的包名
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
Writer writer = null;
//創建文件
try {
JavaFileObject sourceFile
= filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
writer.write("package "+packageName+";\n");
writer.write("import com.bluetree.annotation_lib.IBind;\n");
writer.write("public class "+activityName+"_ViewBinding implements IBind<"+packageName + "." + activityName+"> {\n");
writer.write(" @Override\n");
writer.write(" public void bind("+packageName + "." + activityName+" target) {\n");
for (Element varableEle :
variableElements) {
//獲取變量名
String varableName = varableEle.getSimpleName().toString();
//獲取id
int id = varableEle.getAnnotation(BindView.class).value();
//獲取變量類型
TypeMirror typeMorror = varableEle.asType();
writer.write(" target."+varableName+" = ("+typeMorror+")target.findViewById("+id+");\n");
}
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
e.printStackTrace();
}
finally {
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
}
使用類加載技術引用
在註解庫中加入工具類,用於綁定Activity
/**
* 通過接口,綁定activity和註解編譯器生成的class文件建立關係
* 涉及到
*/
public class MyButterKnife {
public static void bind(Object activity) {
String name = activity.getClass().getName() + "_ViewBinding";
try {
// Class.forName("ClassName")方式會執行類加載的加載、鏈接、初始化三個步驟
Class<?> aClass = Class.forName(name);
IBind iBinder = (IBind) aClass.newInstance();
iBinder.bind(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在Activity中使用我們前面定義好的註解
public class SampleAnnotationActivity extends AppCompatActivity {
@BindView(R.id.textView)
TextView textView;
@BindView(R.id.button2)
Button button2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample_annotation);
MyButterKnife.bind(SampleAnnotationActivity.this);
button2.setText("333");
}
}
整理了一下比較少用的操作:
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
public SourceVersion getSupportedSourceVersion() { return processingEnv.getSourceVersion(); }
public Set<String> getSupportedAnnotationTypes() { Set<String> types = new HashSet<>(); types.add(BindView.class.getCanonicalName()); return types; }
public SourceVersion getSupportedSourceVersion() { return processingEnv.getSourceVersion(); }
//關於節點,有類節點,成員變量節點,方法節點,一個樹形結構來的 Set<? extends Element> elementsAnnotation = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//通過類節點,獲取到類的包名 String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
//創建文件 JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding"); writer = sourceFile.openWriter();
//獲取id int id = varableEle.getAnnotation(BindView.class).value(); //獲取變量類型 TypeMirror typeMorror = varableEle.asType();