源代码
GitHub源代码
本文目标
理解ButterKnife的原理和手写简易核心代码
基本使用
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv1)
TextView textView1;
@BindView(R.id.tv2)
TextView textView2;
private Unbinder mUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = ButterKnife.bind(this);
textView1.setText("今天天气真晴朗");
textView2.setText("适合去泡妹");
}
@Override
protected void onDestroy() {
mUnbinder.unbind();
super.onDestroy();
}
}
我们只需要在要初始化的字段上标记@BindView然后ButterKnife.bind(this),在页面销毁的时候也记得销毁一下就ok了,我们今天就从最简单的开始写一个ButterKnife
如果还不是很会可以去 github 详细看下如何使用
ButterKnife 原理分析
当我们点击运行的时候,就在app->build->source->apt文件夹下能找到这些代码:
// butterknife 自动生成
package com.butterknife.write;
import android.support.annotation.CallSuper;
import com.butterknife.Unbinder;
import com.butterknife.Utils;
import java.lang.Override;
public final class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
MainActivity_ViewBinding(MainActivity target) {
this.target = target;
target.textView1 = Utils.findViewById(target, 2131165324);
target.textView2 = Utils.findViewById(target, 2131165325);
}
@Override
@CallSuper
public final void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");;
target.textView1 = null;
target.textView2 = null;
}
}
看到这里我想很多人都应该懂了,其实当我们每次点击运行的时候, 都会去扫描运行时的注解,然后自动生成这么一个类,是自动生成的不是我们自己写的。规则大概就像上面这样,xxx_ViewBinding 作为类名,实现 Unbinder 在 xxx_ViewBinding 的构造函数里面去 findViewById 或者 setOnclickListener。我们只要在 MainActivity 中去实例化一个 MainActivity_ViewBinding 对象,那么我们 MainActivity 里面的所有属性就都会被赋值了。这个目前来看完全没有涉及到任何反射,那么我们只需要去了解怎么生成代码岂不就行了。我们按照这个思路,自己动手来写一个 ButterKnife 。
apt 生成代码
怎么自动生成代码,这个其实在 thinking in java 这本书中有提到,这个属于 mirror 板块的知识。实在不行嘞,我们可以参考 ButterKnife 的写法。我们新建一个 Java 工程,注意一定要是一个 Java 工程,否则待会有可能找不到类。
新建 ButterKnifeProcessor 继承 AbstractProcessor
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private Messager messager;
private Filer mFiler;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "=========init============");
}
/**
* 1.指定处理的版本
* 支持的jdk版本,一般返回支持的最新的
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//2. 给到需要处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
/**
* 能处理的注解类型,里面接受的是一个注解,说明可以同时处理多个,butterknife就是支持多个,
* 同时处理BindView和OnClick注解,如果不使用注解的话,
* 你也可以重写getSupportedAnnotationTypes方法,getSupportedAnnotationTypes返回的是一个set<String>集合,里面添加需要处理的集合信息
*
* @return
*/
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
// 需要解析的自定义注解 BindView OnClick
annotations.add(BindView.class);
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 调试打印
messager.printMessage(Diagnostic.Kind.NOTE, "=======process============");
return false;
}
}
- @AutoService 这个注解能帮助我们自动注册,对应com.google.auto.service:auto-service:1.0-rc2如果不使用这个注解的话,需要手动去注册,
步骤是,在butterKnife-compilermodule的main目录下创建resources/META-INF/services文件夹,
再创建javax.annotation.processing.Processor文件,文件中写BindViewProcessor的全类名
上面就这么简单,点击运行apk,如果你能看到下面这个打印,代表配置这方面是没有问题的,如果没有看到任何打印,说明没起作用,要先找找问题:
如果我们在代码的任何地方有注解,都会来到 process 这个方法里面,我们只要在这个方法里面生成代码就可以,这里就不做详细的介绍。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 调试打印
messager.printMessage(Diagnostic.Kind.NOTE, "=======process============");
// process 方法代表的是,有注解就都会进来 ,但是这里面是一团乱麻
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
/*for (Element element : elements) {
Element enclosingElement = element.getEnclosingElement();
System.out.println("------------------------>"+element.getSimpleName().toString()+" "+enclosingElement.getSimpleName().toString());
}*/
// 解析 属性 activity -> List<Element>
// 1个Activity对应一个List的Element,该Element的名字在本例中就是textView1 textView2
Map<Element, List<Element>> elementsMap = new LinkedHashMap<>();
for (Element element : elements) {
Element enclosingElement = element.getEnclosingElement();
List<Element> viewBindElements = elementsMap.get(enclosingElement);
if (viewBindElements == null) {
viewBindElements = new ArrayList<>();
elementsMap.put(enclosingElement, viewBindElements);
}
viewBindElements.add(element);
}
// 生成代码
for (Map.Entry<Element, List<Element>> entry : elementsMap.entrySet()) {
Element enclosingElement = entry.getKey();
List<Element> viewBindElements = entry.getValue();
// public final class xxxActivity_ViewBinding implements Unbinder
String activityClassNameStr = enclosingElement.getSimpleName().toString();
ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
ClassName unbinderClassName = ClassName.get("com.butterknife","Unbinder");
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr+"_ViewBinding")
.addModifiers(Modifier.FINAL,Modifier.PUBLIC).addSuperinterface(unbinderClassName)
.addField(activityClassName,"target",Modifier.PRIVATE);
// 实现 unbind 方法
// android.support.annotation.CallSuper
ClassName callSuperClassName = ClassName.get("android.support.annotation","CallSuper");
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC,Modifier.FINAL)
.addAnnotation(callSuperClassName);
unbindMethodBuilder.addStatement("$T target = this.target",activityClassName);
unbindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\");");
// 构造函数
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(activityClassName,"target");
// this.target = target;
constructorMethodBuilder.addStatement("this.target = target");
// findViewById 属性
for (Element viewBindElement : viewBindElements) {
// target.textView1 = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'textView1'", TextView.class);
// target.textView1 = Utils.findViewById(source, R.id.tv1);
String filedName = viewBindElement.getSimpleName().toString();
ClassName utilsClassName = ClassName.get("com.butterknife","Utils");
int resId = viewBindElement.getAnnotation(BindView.class).value();
constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target, $L)",filedName,utilsClassName,resId);
// target.textView1 = null;
unbindMethodBuilder.addStatement("target.$L = null",filedName);
}
classBuilder.addMethod(unbindMethodBuilder.build());
classBuilder.addMethod(constructorMethodBuilder.build());
// 生成类,看下效果
try {
String packageName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
JavaFile.builder(packageName,classBuilder.build())
.addFileComment("butterknife 自动生成")
.build().writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
System.out.println("翻车了!");
}
}
return false;
}
最后我们看下生成的类文件,MainActivity_ViewBinding就是了。
反射
现在MainActivity_ViewBinding已经生成了,我们这个时候需要用反射找到该类,该如何去找呢?
可以用拼接字符串的方法得到这个类的类名,然后在反射就拿到字节码对象了,然后在bindConstructor.newInstance(activity);的时候就会去findViewByid了
public class ButterKnife {
public static Unbinder bind(Activity activity) {
// xxxActivity_ViewBinding viewBinding = new xxxActivity_ViewBinding(this);
try {
Class<? extends Unbinder> bindClassName = (Class<? extends Unbinder>)
Class.forName(activity.getClass().getName() + "_ViewBinding");
// 构造函数
Constructor<? extends Unbinder> bindConstructor = bindClassName.getDeclaredConstructor(activity.getClass());
//创建的时候就把字段findViewById了
Unbinder unbinder = bindConstructor.newInstance(activity);
// 返回 Unbinder
return unbinder;
} catch (Exception e) {
e.printStackTrace();
}
return Unbinder.EMPTY;
}
}
public class Utils {
public static <T extends View> T findViewById(Activity activity,int viewId){
return (T) activity.findViewById(viewId);
}
}
public interface Unbinder {
@UiThread
void unbind();
Unbinder EMPTY = new Unbinder() {
@Override
public void unbind() {
}
};
}
总结
主要采用编译时注解,说白了就是用apt生成代码,然后通过这些代码去进行findViewById和进行字段的赋值