ButterKnife 使用
引入 ButterKnife 可以去这里查看最新的版本,我使用的是8.8.0版本,10.0.0版本有兼容问题,没有使用,但是源码基本一样
//gradle 文件中
dependencies {
implementation 'com.jakewharton:butterknife:8.8.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.0'
}
使用:
public class Main2Activity extends AppCompatActivity {
//绑定控件,不用再写 findViewById
@BindView(R.id.tv_test)
TextView mTv_1;
@BindView(R.id.tv_test1)
TextView mTv_2;
private Unbinder mBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//绑定对应activity
mBind = ButterKnife.bind(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//新版本的ButterKnife 解注册,防止内存泄露
if (mBind != null) {
mBind.unbind();
}
}
//绑定点击事件
@OnClick({R.id.tv_test1, R.id.tv_test})
void onClick(View view) {
}
}
除了上面的绑定控件、点击事件,还可以绑定 String 、Animation 等一系列
分析:@BindView、@OnClick 是自定义的注解,并且为编译时注解
//CLASS 表示为编译时的注解
@Retention(CLASS)
//FIELD 表示为变量注解
@Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
// METHOD 表示为方法注解
@Target(METHOD)
//CLASS 表示为编译时的注解
@Retention(CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
@IdRes int[] value() default { View.NO_ID };
}
只是定义了注解,什么都没做。继续看 ButterKnife.bind(this);
public static Unbinder bind(@NonNull Activity target) {
//获取DecorView
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
//获取Class
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 {
//通过构造方法反射创建 class
//所以ButterKnife 也使用了反射
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);
}
}
private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) {
//通过缓存获取
//BINDINGS 是一个静态的LinkedHashMap
//使用缓存提高性能
Constructor extends Unbinder> bindingCtor = BINDINGS.get(cls);
//判断缓存是否存在,存在就直接返回
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
//判断 cls 名称,是否可以使用
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//通过ClassLoader 获取对应的Class
//注意该 Class为 xxx_ViewBinding,按照事例为 Main2Activity_ViewBinding
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
//通过 Class 获取构造方法
bindingCtor = (Constructor extends Unbinder>) bindingClass.getConstructor(cls, View.class);
if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
//放入缓存
BINDINGS.put(cls, bindingCtor);
//返回
return bindingCtor;
}
所以上面的代码为:通过反射创建了一个 Main2Activity_ViewBinding 对象,并将当前 activity 实例传进去
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mBind = ButterKnife.bind(this);
//ButterKnife.bind(this) 就相当于 new 一个对象
mBind = new Main2Activity_ViewBinding(this);
}
等等,Main2Activity_ViewBinding 在哪里,都没有新建过这个类,这个类是在 build/generatd/source/apt/debug/对应报名 下,看做了什么
public class Main2Activity_ViewBinding implements Unbinder {
private Main2Activity target;
private View view2131165338;
private View view2131165339;
@UiThread
public Main2Activity_ViewBinding(final Main2Activity target, View source) {
this.target = target;
View view;
//Utils.findRequiredView 其实就是调用了 findViewById 方法
view = Utils.findRequiredView(source, R.id.tv_test, "field 'mTv_1' and method 'onClick'");
target.mTv_1 = Utils.castView(view, R.id.tv_test, "field 'mTv_1'", TextView.class);
view2131165338 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick(p0);
}
});
view = Utils.findRequiredView(source, R.id.tv_test1, "field 'mTv_2' and method 'onClick'");
target.mTv_2 = Utils.castView(view, R.id.tv_test1, "field 'mTv_2'", TextView.class);
view2131165339 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick(p0);
}
});
}
@Override
@CallSuper
public void unbind() {
Main2Activity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.mTv_1 = null;
target.mTv_2 = null;
view2131165338.setOnClickListener(null);
view2131165338 = null;
view2131165339.setOnClickListener(null);
view2131165339 = null;
}
}
所以其实就是 new Main2Activity_ViewBinding 对象,在构造方法中找到对应的变量做了一遍 findViewById。那么这个 Main2Activity_ViewBinding 类是什么时候创建的呢?通过编译时的注解,在编译的时候通过apt创建出对应的类,下面来模仿手写
手写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
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'
}
新建 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 getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
for (Class extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set> getSupportedAnnotations() {
Set> 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')
}
依赖完成之后,运行app工程,在build下查看是否有打印
在这里面如果能打印日志出来,说明配置没有问题,继续走下一步,否则,需要查看配置是不是有问题
如果上面的没有问题,接下来来编写 process 方法,我直接上代码,
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取 ViewBind 的Element
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ViewBind.class);
//获取 OnViewClick Element
Set extends Element> clickElements = roundEnvironment.getElementsAnnotatedWith(OnViewClick.class);
//新建 Map 用来保存不同类的内容
//一个 TypeElement 对应一个有注解的 activity
Map> map = new LinkedHashMap<>();
//遍历 ViewBind 的 Element
for (Element element : elements) {
//注解的变量名
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 elementList = map.get(typeElement);
if (elementList == null) {
elementList = new ArrayList<>();
map.put(typeElement, elementList);
}
ElementBindSet bindSet = new ElementBindSet();
bindSet.mElement = element;
bindSet.type = 0;
elementList.add(bindSet);
}
//遍历 OnViewClick
for (Element clickElement : clickElements) {
TypeElement typeElement = (TypeElement) clickElement.getEnclosingElement();
List elementList = map.get(typeElement);
if (elementList == null) {
elementList = new ArrayList<>();
map.put(typeElement, elementList);
}
ElementBindSet bindSet = new ElementBindSet();
bindSet.mElement = clickElement;
bindSet.type = 1;
elementList.add(bindSet);
}
//遍历 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.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 elementList = map.get(typeElement);
//通过 ClassName 获取 com.butterknife.Utils
//Utils 是自己定义的工具类,用来做 findViewById 操作
ClassName utils = ClassName.get("com.butterknife", "Utils");
//通过 ClassName 获取 view
ClassName view = ClassName.get("android.view", "View");
//获取 DebouncingOnClickListener
//DebouncingOnClickListener 是一个实现点击事件接口的抽象类
ClassName debouncingOnClickListener = ClassName.get("com.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 (ElementBindSet 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;
}
代码很长,只需要了解语法,跟着 ButterKnife 生成的 xxx _ViewBinding 类来敲,很简单的,下面来完善butterknife这个lib 的内容,其实就是增加几个方法
新增 Butter 类,用来和 ButterKnife 区别
public class Butter {
static final Map, Constructor extends Unbinder>> BINDINGS = new LinkedHashMap<>();
public static Unbinder bind(Activity activity) {
Class> aClass = activity.getClass();
Constructor extends Unbinder> constructor = findBindingConstructorForClass(aClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
try {
return constructor.newInstance(activity);
} 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;
}
}
其实就是照抄 ButterKnife 的代码,需要注意的是我们定义的只有一个参数的构造函数,所以传一个参数来获取Constructor,然后就是剩下需要的类
//定义 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 findViewById(Activity activity, int id) {
return activity.findViewById(id);
}
}
大功告成,最后来修改前面的 Main2Activity ,换上我们自己的注解
public class Main2Activity extends AppCompatActivity {
@ViewBind(R.id.tv_test)
TextView mTv_1;
@ViewBind(R.id.tv_test1)
TextView mTv_2;
private Unbinder mBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// mBind = ButterKnife.bind(this);
// mBind = new Main2Activity_ViewBinding(this);
mBind = Butter.bind(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBind != null) {
mBind.unbind();
}
}
@OnViewClick({R.id.tv_test1, R.id.tv_test})
void onClick(View view) {
}
}
完美运行。。。。。。