首先记住2个结论:
- BF使用的是编译时注解而非运行时注解,没有使用反射,性能更优
- BF不是没有调用
findViewById(),而是通过生成类的方式帮我们做了这样的工作
接下来我们自己实现一个最简单的ButterKnife框架,就能够大致了解它的原理。
先看看使用方法:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
public TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mTv.setText("####");
}
}
和BF一样的使用方法。
1. 自定义BindView 注解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
@Documented
public @interface BindView {
int value();
}
注解的作用顾名思义,注释和解析其标注的对象,这里是TextView,相当于给TextView打了一个标签,便于在代码里获取和解析它。
2. ButterKnife.bind(this) 方法
/**
* @param activity
*/
public static void bind(Activity activity) {
if (activity != null) {
try {
// 反射拿到 MainActivity_ViewBinding 类对象
Class<?> bindClass = Class.forName(activity.getClass().getCanonicalName() + "_ViewBinding");
Class<?> activityClass = Class.forName(activity.getClass().getCanonicalName());
// 这里传入activityClass,是为了区分重载的构造函数
// MainActivity_ViewBinding(MainActivity target)
Constructor<?> constructor = bindClass.getDeclaredConstructor(activityClass);
constructor.newInstance(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过反射构造了MainActivity_ViewBinding对象,这个MainActivity_ViewBinding就是是通过APT自动生成的,接下来只要搞懂是怎么生成的就完事了。
-
先注册一个自定义注解处理器,新建src\main\resources\META-INF\services\javax.annotation.processing.Processor文件,这个写法是固定的,这样程序就会执行我们自己定义的注解注解处理程序,这里是
BindProcessor。com.lu.lib_processor.BindProcessor -
完成
BindProcessor代码编写
public class BindProcessor extends AbstractProcessor {
private Filer mFiler;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mElementUtils = processingEnv.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//------------获取注解-----------
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//LinkedHashMap输出和输入的顺序相同,先输入就先输出
Map<Element, List<Element>> elementsMap = new LinkedHashMap<>();
for (Element element : elements) {
//这里会把所有跟注解有关的field全部拿到,包括各个类中的field,也就是说在编译时,
// 项目中所有涉及到这个注解的地方的所有field都在这个Set中返回了,我们需要手动进行分类
System.out.println("[lu]###" + element.getSimpleName());// [lu]###mTv
//得到的enclosingElement是这个field所在类的类名
Element enclosingElement = element.getEnclosingElement();
// ------------enclosingElement-----------MainActivity
System.out.println("------------enclosingElement-----------" + enclosingElement.getSimpleName());
//以类名为key值,存储一个类中所有的field到集合elementsMap中
List<Element> bindViewElements = elementsMap.get(enclosingElement);
if (bindViewElements == null) {
bindViewElements = new ArrayList<>();
elementsMap.put(enclosingElement, bindViewElements);
}
bindViewElements.add(element);
}
// 遍历elementsMap (LinkedHashMap)
for (Map.Entry<Element, List<Element>> entry : elementsMap.entrySet()) {
Element enclosingElement = entry.getKey();// MainActivity
List<Element> bindViewElements = entry.getValue();// field: TextView mTv;
ClassName unbinderClassName = ClassName.get("com.lu.lib_annotation", "Unbinder");
// ------------Unbinder-----------Unbinder
System.out.println("------------Unbinder-----------" + unbinderClassName.simpleName());
//得到类名的字符串
String activityName = enclosingElement.getSimpleName().toString();
ClassName activityClassName = ClassName.bestGuess(activityName);
//拼装这一行代码:public final class xxx_ViewBinding implements Unbinder
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName + "_ViewBinding")
//类名前添加public final
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
//添加类的实现接口
.addSuperinterface(unbinderClassName)// implements Unbinder
//添加一个成员变量,这个名字target是仿照butterknife
.addField(activityClassName, "target", Modifier.PRIVATE);
//实现 unbinder的方法
/*
@Override
@CallSuper
public final void unbind() {
target.mTv = null;
}
*/
//CallSuper这个注解不像Override可以直接拿到,需要用这种方式
ClassName callSuperClass = ClassName.get("android.support.annotation", "CallSuper");
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
//和你创建的Unbinder中的方法名保持一致
.addAnnotation(Override.class)
.addAnnotation(callSuperClass)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC);
/*
MainActivity_ViewBinding(MainActivity target) {
this.target = target;
target.mTv = (TextView) target.findViewById(2131165312);
}
*/
//添加构造函数
MethodSpec.Builder constructMethodBuilder = MethodSpec.constructorBuilder().addParameter(activityClassName, "target");
constructMethodBuilder.addStatement("this.target = target");
for (Element bindViewElement : bindViewElements) {
String fieldName = bindViewElement.getSimpleName().toString();
//在构造方法中添加初始化代码
ClassName utilsClassName = ClassName.get("com.lu.lib_annotation", "Utils");
ClassName tv = ClassName.get("android.widget", "TextView");
BindView annotation = bindViewElement.getAnnotation(BindView.class);
if (annotation != null) {
int resId = annotation.value();
constructMethodBuilder.addStatement("target.$L = ($T) target.findViewById($L)"
, fieldName, tv, resId);
//在unbind方法中添加代码 target.textView1 = null;
//不能用addCode,因为它不会在每一行代码后加分号和换行
unbindMethodBuilder.addStatement("target.$L = null", fieldName);
}
}
classBuilder.addMethod(constructMethodBuilder.build());// 添加构造函数
classBuilder.addMethod(unbindMethodBuilder.build());// 添加unbind()方法
// 写入代码
try {
//得到包名
String packageName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
JavaFile.builder(packageName, classBuilder.build())
//添加类的注释
.addFileComment("butterknife 自动生成").build().writeTo(mFiler);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindView.class);
return annotations;
}
}
重要部分都有注释,应该不难理解,其他的查查资料搞懂JavaPoet的使用就可以了。
完整代码参考