Android ButterKnife 使用及原理解析
ButterKnife是编译器型的注解工具,还有一种依赖注入型的注解工具RoboGuice,可以参考这篇文章。
一.引入依赖
dependencies {
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}
二.简单使用
1.绑定view、views、resources
public class ButterKnifeActivity extends BaseActivity {
@Nullable//告知ButterKnife该view可能为null,做相应的判空处理
@BindView(R.id.custom_btn)//让该view指向id为custom_btn的view,且为Button类型
Button customBtn;
@BindString(R.string.butter_knife_text)//让该变量值等于id为butter_knife_text的string资源
String text;
@BindColor(R.color.color50)//让该变量等于id为color50的color资源
int color;
@Nullable//每个button可能为null
@BindViews({R.id.btns1, R.id.btns2})//将id为btns1和btns2的两个Button绑定到一个List集合中
List<Button> buttons;
}
由demo可知,ButterKnife可以去绑定各种类型资源和view,并可以同时获取多个view到集合
2.view事件绑定
public class ButterKnifeActivity extends BaseActivity {
@Optional//告知ButterKnife该id的view可能为null,做setOnClickListener时做判空处理
@OnClick(R.id.custom_btn)//指定id为custom_btn的view的点击事件为该方法,且支持最多一个参数,参数类型为实际view类型或其父类
public void onClick(Button btn) {
//do something
}
@Optional//告知ButterKnife这些views可能为null,做setOnClickListener时做判空处理
@OnClick({R.id.btns1, R.id.btns2})//指定id为btns1和btns2的view的点击事件为该方法
public void onBtnsClick(Button button) {
//do something
}
@Optional
@OnItemClick(R.id.listView)//将id为listView的AdapterView的OnItemClickListener设置为该方法,参数可选
public void onItemClick(int pos, long id) {
//do something
}
@Optional
@OnItemSelected(R.id.listView)//id为listView的AdapterView,将其OnItemSelectedListener的onItemSelected方法设置为该方法,参数可选
public void onItemSelected(int pos, long id) {
//do something
}
@Optional
@OnItemSelected(value = R.id.listView, callback = OnItemSelected.Callback.NOTHING_SELECTED)//id为listView的AdapterView,将其OnItemSelectedListener的onNothingSelected方法设置为该方法(通过callback指定是具体哪个事件(方法))
public void onNothingSelected() {
//do something
}
}
//自定义view内部的事件
public class ButterKnifeLayout extends LinearLayout {
//没有指定id不能加optional
@OnClick//将当前view的点击事件指定为该方法
public void onClick() {
//do something
}
}
由demo可知,ButterKnife可以通过注解给view或views绑定各种事件监听,并可以通过注解的value来指定不同的监听方法
3.设置view或一组view的操作
public class ButterKnifeFragment extends Fragment {
@Nullable
@BindViews({R.id.btns1, R.id.btns2})
List<Button> buttons;
//给views统一设置text的ACTION对象
static final ButterKnife.Action<Button> ACTION_DOWN = new ButterKnife.Action<Button>() {
@Override
public void apply(@NonNull Button view, int index) {
view.setText("ACTION_DOWN");
}
};
//给views统一设置字体颜色的SETTER对象
static final ButterKnife.Setter<Button, Integer> TEXT_COLOR = new ButterKnife.Setter<Button, Integer>() {
@Override
public void set(@NonNull Button button, Integer value, int index) {
button.setTextColor(value);
}
};
@Optional
@OnClick(R.id.fragment_bk_btn)
public void onBtnClick() {
ButterKnife.apply(buttons, ACTION_DOWN);//对一组button执行ACTION对象
ButterKnife.apply(buttons, TEXT_COLOR, Color.RED);//对一组button执行SETTER对象
ButterKnife.apply(buttons, View.ALPHA, 0.5f);//对一组button执行Property对象,改变view的属性
}
}
由demo可知,ButterKnife可以对一组、或单个view对象执行一组统一操作,这些对象可以是ACTION对象,SETTER对象,或Property对象,都可以由我们自己实现
4.执行ButterKnife的绑定
//(1)Activity的ButterKnife
public class ButterKnifeActivity extends BaseActivity {
@BindView(R.id.fragment_bk_btn)
Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_butter_knife);//要先设置view
ButterKnife.bind(this);//执行ButterKnife绑定操作,将activity的view绑定到this的注解上
//use customBtn,text
}
}
//(2)Fragment的ButterKnife
public class ButterKnifeFragment extends Fragment {
@BindView(R.id.fragment_bk_btn)
Button btn;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_butter_knife, container, false);
ButterKnife.bind(this, view);//执行ButterKnife绑定操作,将fragment的view绑定到this的注解上
//use btn
return view;
}
}
//(3)ViewHolder的ButterKnife
static class ViewHolder {
@BindView(R.id.fragment_bk_btn)
Button btn;
private ViewHolder(@NonNull View view) {
ButterKnife.bind(this, view);//执行ButterKnife绑定操作,将view绑定到this的注解上
//use button,bkText
}
}
//(4)自定义view的ButterKnife
public class ButterKnifeLayout extends LinearLayout {
@BindView(R.id.bk_layout_btn)
TextView textView;
public ButterKnifeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(getContext()).inflate(R.layout.bk_layout_btn, this, true);
//ButterKnife.bind(this);//可以在子view加入后开始bind,此时可以拿到正确的子view,视情况而定
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.bind(this);//也可以在xml文件解析完成后,所有子view生成后bind,视情况而定
}
}
由demo可知,ButterKnife通过bind方法开始执行绑定操作,它提供了多个重写方法,可以将指定的view绑定与任意需要注解的Object,包括Fragment、ViewHolder等都是这个意思;也提供了Activity、Dialog、View为参数的重载方法,对应的直接使用其view绑定到其自身
三.实现原理
1.实现原理
-
ButterKnife没有使用反射这种会造成性能卡顿的方式实现注解,而是使用了Java Annotation Processing技术,就是在Java代码编译成Java字节码的时候就已经处理了@BindView、@OnClick这些注解了
-
Annotation Processing 是javac中用于编译时扫描和解析Java注解的工具,可以自定义注解,并且自己定义解析器来处理它们。Annotation Processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码;新生成的Java代码最后被编译成Java字节码
-
我们引入的其中一个依赖 annotationProcessor ‘com.jakewharton:butterknife-compiler:8.5.1’ 就是ButterKnife自定义的注解解析器
2.工作流程
当编译工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作:
-
开始它会扫描Java代码中所有的ButterKnife注解@BindView、@OnClick等
-
当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似OuterClass$InnerClass_ViewBinding,这个新生成的类实现了Unbinder接口
-
这个ViewBinding类中包含了所有对应的代码,比如@BindView注解对应的findViewById()操作、@OnClick对应了view.setOnClickListener()操作等等
-
当ButterKnife.bind()执行时,ButterKnife会去加载对应的ViewBinding类,创建其实例,执行其所有绑定操作
3.bind流程及实例
这里以最常见的Activity的绑定bind(activity)为例讲解:
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();//设置完setContentView后,拿到的decorView就是activity完整的根view
return createBinding(target, sourceView);//这里会返回创建的Unbinder对象,用于外部释放使用,下面会说
}
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);//找到对应类的构造器
if (constructor == null) {
return Unbinder.EMPTY;
}
try {
return constructor.newInstance(target, source);//创建实例
} catch (IllegalAccessException e) {
...
}...
}
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);//缓存
if (bindingCtor != null) {
return bindingCtor;
}
...
try {
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");//找到对应类的class对象---Xxx_ViewBinding
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);//反射获取构造器对象
} 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;
}
由代码可知,activity执行bind时,会拿到activity对应的decorView根view去绑定;
首先会根据名字Xxx_ViewBinding找到编译时生成的对应的类,获取其相应构造器,将view传入进行绑定;
那么生成的ViewBinding类是什么样的以及如何绑定的呢:
//原Activity
public class ButterKnifeActivity extends BaseActivity {
@Nullable
@BindView(R.id.custom_btn)
Button customBtn;
@BindString(R.string.butter_knife_text)
String text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_butter_knife);
ButterKnife.bind(this);
}
@Optional
@OnClick(R.id.custom_btn)
public void onClick(Button btn) {
}
}
//ButterKnifeActivity_ViewBinding
public class ButterKnifeActivity_ViewBinding implements Unbinder {
private ButterKnifeActivity target;
private View view2131624110;
@UiThread
public ButterKnifeActivity_ViewBinding(ButterKnifeActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public ButterKnifeActivity_ViewBinding(final ButterKnifeActivity target, View source) {
this.target = target;
View view;
view = source.findViewById(R.id.custom_btn);
target.customBtn = Utils.castView(view, R.id.custom_btn, "field 'customBtn'", Button.class);
if (view != null) {
view2131624110 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.onClick(Utils.<Button>castParam(p0, "doClick", 0, "onClick", 0));
}
});
}
Context context = source.getContext();
Resources res = context.getResources();
target.text = res.getString(R.string.butter_knife_text);
}
@Override
public void unbind() {
ButterKnifeActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.customBtn = null;
if (view2131624110 != null) {
view2131624110.setOnClickListener(null);
view2131624110 = null;
}
}
}
由代码可知,生成的ViewBinding类在初始化绑定时,就是根据findViewById找到view并赋值给Activity的相应变量;并给view设置相应的onClickListener,回调为activity的指定方法;资源则是用context.getResource来取值
其他的bind方法流程也都如此
四.其他说明
-
由上述bind方法流程可知,Xxx_ViewBinding类会生成在与Xxx类同包下,所以在ViewBinding类中使用Xxx类的变量和方法,需要其相应访问修饰符最少为default,不能为private,这点很重要
-
bind方法会返回创建的这个Unbinder对象,目的是可以让外部调用其unBind方法进行view的释放,但是感觉并没有大的优势,大部分情况下都会自动回收view
-
在使用bind方法开始绑定前,一定要确保view是完整的正确的已经被添加的
-
ButterKnife也提供了findById(view,id)方法,省去了类型强转,在需要时候也可以使用