参考github
ButterKnife是一个视图注入框架。解决问题:去除大量繁重的View对象查findViewById找方法,视图使用注解,通过APT技术,在编译阶段处理注解,生成一个新Class类,它没有用反射技术,性能无损耗。它可以处理View绑定与事件点击等功能,配合As插件,提高效率且代码简洁可读。
一、代码中配置
project的build.gradle加入依赖
classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
module的的build.gradle加入插件。
apply plugin: 'com.jakewharton.butterknife'
module的build.gradle加入依赖。
compile "com.jakewharton:butterknife:8.5.1"
annotationProcessor "com.jakewharton:butterknife-compiler:8.5.1"
Android Studio安装Android ButterKnife Zelezny插件,重启AS,通过右键setContentView方法中的布局文件,然后Generate Butterknife Injections一键生成带Id的视图注解。
二 、注解示例和混淆
绑定视图:@BindView
绑定字符串:@BindString
绑定array数组:@BindArray
绑定color:@BindColor
绑定图片资源:@BindBitmap
点击事件:@OnClick
选中改变事件:@OnCheckedChanged
Item点击事件:@OnItemClick
长按事件:@OnLongClick
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
三、实现原理
编译时的注解解析
声明注解如BindView的生命周期@Retention(CLASS),编译时,编译器扫描所有带有这些注解的类,根据注解,在bulid/generated/source/apt/debug目录下,生成一个新的Xxx_ViewBinding类,该类中有绑定视图对象和事件监听的代码,在运行时会调用这些代码。我们的Activity,ButterKnife.bind(this)方法。
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
根据Activity获取DecorView顶层视图,然后,调用createBinding方法,拿到继承Unbinder的子类构造器Constructor,即生成的Xxx_ViewBinding类构造器,创建对象。
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
try {
//2个参数的构造器,分别是Activity和顶层视图
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
}
}
获取构造器方法。
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
return bindingCtor;
}
String clsName = cls.getName();
try {
//加载类
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
根据当前Activity类的Name,加载Xxx_ViewBinding类,这个类是在编译时自动生成,规则就是当前Activity的名称+ViewBinding,加载类后,拿到构造器,保存一个Map,防止每次Class.forName加载。生成的类构造方法如下。
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.tv_hello_butter, "field 'tvHelloButter' and method 'onViewClicked'");
target.tvHelloButter = Utils.castView(view, R.id.tv_hello_butter, "field 'tvHelloButter'", TextView.class);
view2130968596 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onViewClicked();
}
});
Context context = source.getContext();
Resources res = context.getResources();
target.butter = res.getString(R.string.str_hi_butterknife);
}
使用findViewById方法获取View对象,赋值给我们自己Activity中注解的View对象,赋值前需要cast转换成我们定义的类型,在Activity_ViewBinding类中,保存的是View类型,设置视图监听,调用我们自己Activity定义的监听方法。
上面的onViewClicked监听方法和tv_hello_butter视图id是根据注解处理获取的写入到生成的类里。
此外,其他类型注解如@BindString,@BindColor等,都能通过Resource和id获取到值,赋值给我们的Activity。
四、新类生成
编译的时候生成代码,注解处理器butterknife-compiler,先扫描所有的Java源文件,解析注解,生成对应java源文件,再编译生成字节码文件。
bind方法的本质是根据构造器动态创建新类对象,新类绑定Activity和顶层视图,利用findViewById方法获取View及事件绑定,将对象交给我们的Activity。因此,我们的Activity的视图对象、资源变量和监听就完成了初始化。
(新类是在编译时处理,根据注解创建,新类内部View与id绑定。)
编译后,在build/generated/source/apt/debug能找到MainActivity_ViewBinding的java文件。运行时,即使没有声明这个MainActivity_ViewBinding类也不会报错,Class.forName方法一定会加载该类,编译成字节码之前会生成它的java文件。
五、总结
在编译时,处理@BinderView等注解,在bulid/generated/source/apt/debug目录生成一个Xxx_ViewBinding的类,Xxx就是你的Activity名称。
在ButterKnife.bind(this)方法,拿到你的Activity类对象和顶层视图,根据Activity类名在一个map中查找生成Xxx_ViewBinding类的构造器,该类继承Unbinder。Map没有就去Class.forName加载Xxx_ViewBinding类,然后找到Constructor。根据Constructor创建Binding类实例,构造方法传入你的Activity和顶层视图。
在Xxx_ViewBinding类构造方法中根据顶层视图和id,获取视图对象,视图在你的Activity是注解,在自动生成Binding类中是View引用,同时引用你Activity的实体,赋值给你Activity的视图时,要cast转换你的视图类型。
ButterKnife的bind方法,创建Xxx_ViewBinding对象,初始化你自己Activity内部的注解视图对象,这个视图id是处理注解获取的写入生成的类中。
ButterKnife.bind(this)方法必须在setContentView方法之后执行,父类bind绑定后,子类不需要再bind。
属性布局不能用private or static 修饰,否则会报错。
任重而道远
网友评论