ButterKnife优势
强大的View绑定和Click事件处理功能,简化繁琐的代码编写
可以支持Adapter中的VIewHolder绑定问题
采用编译时通过注解生成代码,对运行时没有侵入,对比反射方式,效率倍高
代码清晰,可读性强
ButterKnife使用
在android module的build.gradle配置文件中,引入如下依赖 apt 已经被annotationProcessor 取代了 不要忘了了加
dependencies {
/* butterknife */
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
}
ButterKnife使用实例
a 注入视图
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.fab)
FloatingActionButton fab;
b 注入事件
@OnClick(R.id.fab)
public void show(View view){
Snackbar.make(view, "Replace with your own action",Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
<meta charset="utf-8">
ButterKnife原理分析
可能很多人都觉得ButterKnife在bind(this)方法执行的时候通过反射获取MainActivity中所有的带有@BindView注解的属性并且获得注解中的R.id.xxx值,最后还是通过反射拿到Activity.findViewById()方法获取View,并赋值给MainActivity中的某个属性。这是一种原始的使用反射的方式,缺点是反射影响App性能,造成卡顿,并且会产生大量的临时对象,频繁的引发GC。
ButterKnife显然没有使用这种方式,它用了Java Annotation Processing技术,就是在Java代码编译成Java字节码的时候就已经处理了@Bind、@OnClick(ButterKnife还支持很多其他的注解)这些注解了。
Java Annotation Processing是java中用于编译时扫描和解析Java注解的工具 也就是我们说到的apt技术
你可以你定义注解,并且自己定义解析器来处理它们。Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法
下图是Java 编译代码的整个过程,可以帮助我们很好理解注解解析的过程:
image
ButterKnife工作原理(知识点!)
当你编译使用了ButterKnife框架的应用程序时,ButterKnifeProcessor类的process()方法开始工作,会执行以下操作:
①.编译期间通过反射扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等
②.当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似<className>$$ViewBinder,这个新生成的类实现了ViewBinder<T>接口
③.这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等
④.最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法
原理分析介绍到这里 看是手写一个吧
手写ButterKnife
可以去 github 上下载ButterKnife 的源码来看,我就直接仿着写定义三个lib 两个 java lib 一个 android lib
butterknife 为 android lib 主要存放反射生成 xxx__ViewBinding 方法和对应 findViewById 方法
butterknife_annotations 为 java lib 主要存放注解类型
butterknife_compiler 为 java lib 主要存放编译时自动生成类的类和方法
先来看butterknife_annotations 这个 lib
//定义ViewBind 注解,用于findViewById
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewBind {
int value();
}
//定义OnViewClick 注解,用于绑定view 的监听事件
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnViewClick {
int[] value();
}
再来看 butterknife_compiler 记得加上annotationProcessor 不然成功不了
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
// //引入 butterknife_annotations 使用里面定义的注解
implementation project(':butterknife_annotations')
//下面这两个是专门用来编译生成类的
implementation 'com.squareup:javapoet:1.10.0'
implementation 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
implementation files('build/libs/butterknife_compiler.jar')
}
新建 ButterKnifeProcessor
//注意增加 AutoService 注解
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
//重写方法,返回版本号
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//获取Filer 和 ElementUtils
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
}
//添加注解的类型
//仿照 ButterKnife 源码写的
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(ViewBind.class);
annotations.add(OnViewClick.class);
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//编译时,就会走这个方法里面
//测试是否走到这里面
System.out.println(">>>>>>>>>>>>>>>>>");
}
}
现在接着 app 工程下增加依赖
dependencies {
//依赖三个我们新建的lib
implementation project(':butterknife')
implementation project(':butterknife_annotations')
//注意,这里使用annotationProcessor 这是新版本 apt 的使用方式
annotationProcessor project(':butterknife_compiler')
}
依赖完成之后,rebuild项目,在build下查看是否有打印
image.png
在这里面如果能打印日志出来,说明配置没有问题,继续走下一步,否则,需要查看配置是不是有问题
重点来了,接下来来编写 process 方法,我直接上代码,
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//获取 ViewBind 的element
Set<? extends Element> viewBindElement = roundEnv.getElementsAnnotatedWith(ViewBind.class);
//获取 onViewClick的element
Set<? extends Element> clickdElement = roundEnv.getElementsAnnotatedWith(onViewClick.class);
Map<TypeElement, List<BindingSet>> map= new LinkedHashMap<>();
for (Element element : viewBindElement) {
//注解的变量名
String simpleName = element.getSimpleName().toString();
//注解的id
int value = element.getAnnotation(ViewBind.class).value();
//对应所在class Element
TypeElement typeElement= (TypeElement)element.getEnclosingElement();
//保存进 map 中
//ElementBindSet 是一个自定义的类,里面保存有 Element 和 type 字段
//用于区分是 ViewBind 还是 OnViewClick
List<BindingSet> sets=map.get(typeElement);
if (sets==null){
sets=new ArrayList<>();
map.put(typeElement,sets);
}
sets.add(new BindingSet(element,0));
}
//遍历clickdElement
for (Element element : clickdElement) {
TypeElement elementType= (TypeElement) element.getEnclosingElement();
List<BindingSet> setList = map.get(elementType);
if (setList==null){
setList=new ArrayList<>();
map.put(elementType,setList);
}
setList.add(new BindingSet(element,1));
}
//遍历 map 有几个 TypeElement 说明要创建几个类
for (TypeElement typeElement : map.keySet()) {
//获取包名
String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();
//获取类名
String className = typeElement.getSimpleName().toString();
//通过 ClassName 来获取 定义的 Unbinder 接口
ClassName unbinder = ClassName.get("com.example.shengdian.butterknife", "Unbinder");
//通过 ClassName 获取类
ClassName target = ClassName.get(packageName, className);
//创建 unbind 方法,在里面编写代码
MethodSpec.Builder unbind_builder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addStatement("$N target = this.target", className)
.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")")
.addStatement("this.target = null");
//创建构造方法
MethodSpec.Builder construction_builder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(target, "target", Modifier.FINAL)
.addStatement("this.$N = $N", "target", "target");
//得到对应 TypeElement 中的 ElementBindSet
List<BindingSet> elementList = map.get(typeElement);
//通过 ClassName 获取 com.butterknife.Utils
//Utils 是自己定义的工具类,用来做 findViewById 操作
ClassName utils = ClassName.get("com.example.shengdian.butterknife", "Utils");
//通过 ClassName 获取 view
ClassName view = ClassName.get("android.view", "View");
//获取 DebouncingOnClickListener
//DebouncingOnClickListener 是一个实现点击事件接口的抽象类
ClassName debouncingOnClickListener = ClassName.get("com.example.shengdian.butterknife", "DebouncingOnClickListener");
//新建对应 xxx_ViewBinding 的 TypeSpec.Builder 用于生成类代码
TypeSpec.Builder bindingBuilder = TypeSpec.classBuilder(className + "_ViewBinding")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(unbinder);
//判断是否包含 OnViewClick
if (checkHaveClick(elementList)) {
//如果包含,就在构造方法中增加 View view 代码
construction_builder.addStatement("$N view", view.toString());
}
//遍历所有 ElementBindSet 即所有的注解
for (BindingSet element : elementList) {
//如果为 ViewBind
if (element.type == 0) {
//获取该注解的 参数名
String simpleName = element.mElement.getSimpleName().toString();
//获取注解 ID 值
int id = element.mElement.getAnnotation(ViewBind.class).value();
//在构造方法中添加代码
// target.参数名 = Utils.findViewById(target,id);
construction_builder.addStatement("target.$N = $N.findViewById(target, $N)",
simpleName, utils.toString(), String.valueOf(id));
//在 unbind 方法中添加代码
//target.参数名 = null
unbind_builder.addStatement("target.$N = null", simpleName);
}
//如果为 OnViewClick
if (element.type == 1) {
//获取注解 ID 所有值
int[] onClickIds = element.mElement.getAnnotation(onViewClick.class).value();
for (int onClickId : onClickIds) {
bindingBuilder.addField(view, "view" + onClickId);
construction_builder.addStatement("view = $N.findViewById(target, $N)", utils.toString(), String.valueOf(onClickId));
construction_builder.addStatement("view" + onClickId + "= view");
//新增监听点击的代码
MethodSpec.Builder onClickBuilder = MethodSpec.methodBuilder("doClick")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(view, "p0")
.addStatement("target.$N(p0)", element.mElement.getSimpleName());
TypeSpec onClick = TypeSpec.anonymousClassBuilder("")
.superclass(debouncingOnClickListener)
.addMethod(onClickBuilder.build())
.build();
construction_builder.addStatement("view.setOnClickListener($N)", onClick.toString());
//在 unbind 方法中清空
unbind_builder.addStatement("view" + onClickId + ".setOnClickListener(null)");
unbind_builder.addStatement("view" + onClickId + "=null");
}
}
}
//在类中添加对应的方法和构造方法
bindingBuilder.addMethod(unbind_builder.build())
.addMethod(construction_builder.build())
.addField(target, "target");
try {
//同噶javaFile生成对应的类
JavaFile javaFile = JavaFile.builder(packageName, bindingBuilder.build()).build();
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private boolean checkHaveClick(List<BindingSet> elementList) {
for (BindingSet elementBindSet : elementList) {
if (elementBindSet.getType()==1){
return true;
}
}
return false;
}
public class BindingSet implements Serializable {
Element mElement;
int type;
public BindingSet(Element mElement, int type) {
this.mElement = mElement;
this.type = type;
}
public Element getmElement() {
return mElement;
}
public void setmElement(Element mElement) {
this.mElement = mElement;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
下面来完善butterknife这个lib 的内容,其实就是增加几个方法
新增 Butter 类、Unbinder 以及DebouncingOnClickListener 和Utils
ublic final class Butter {
static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@NonNull
@UiThread
public static Unbinder bind(@NonNull Activity target) {
Class<?> aClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(aClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
try {
return constructor.newInstance(target);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} 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);
}
return Unbinder.EMPTY;
}
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
return null;
}
try {
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//需要注意的是,我们定义的只有一个参数的构造函数,所以传一个参数 可以多个
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls);
} catch (ClassNotFoundException e) {
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
}
//定义 Unbinder 接口,照抄
public interface Unbinder {
void unbind();
Unbinder EMPTY = new Unbinder() {
@Override
public void unbind() {
}
};
}
//定义 DebouncingOnClickListener 用于点击事件,照抄
public abstract class DebouncingOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
doClick(v);
}
public abstract void doClick(View v);
}
//utils 就是做了 findViewById
public class Utils {
public static <T extends View> T findViewById(Activity activity, int id) {
return activity.findViewById(id);
}
}
大功告成,换上我们自己的注解
最后得感谢 这篇文章
网友评论