Android ButterKnife 使用及原理解析

本文深入解析ButterKnife的使用方法及实现原理,介绍如何在Android项目中高效绑定View和资源,执行事件监听,以及ButterKnife的工作流程和编译原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.实现原理

  1. ButterKnife没有使用反射这种会造成性能卡顿的方式实现注解,而是使用了Java Annotation Processing技术,就是在Java代码编译成Java字节码的时候就已经处理了@BindView、@OnClick这些注解了

  2. Annotation Processing 是javac中用于编译时扫描和解析Java注解的工具,可以自定义注解,并且自己定义解析器来处理它们。Annotation Processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码;新生成的Java代码最后被编译成Java字节码

  3. 我们引入的其中一个依赖 annotationProcessor ‘com.jakewharton:butterknife-compiler:8.5.1’ 就是ButterKnife自定义的注解解析器

2.工作流程

当编译工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作:

  1. 开始它会扫描Java代码中所有的ButterKnife注解@BindView、@OnClick等

  2. 当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似OuterClass$InnerClass_ViewBinding,这个新生成的类实现了Unbinder接口

  3. 这个ViewBinding类中包含了所有对应的代码,比如@BindView注解对应的findViewById()操作、@OnClick对应了view.setOnClickListener()操作等等

  4. 当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方法流程也都如此

四.其他说明

  1. 由上述bind方法流程可知,Xxx_ViewBinding类会生成在与Xxx类同包下,所以在ViewBinding类中使用Xxx类的变量和方法,需要其相应访问修饰符最少为default,不能为private,这点很重要

  2. bind方法会返回创建的这个Unbinder对象,目的是可以让外部调用其unBind方法进行view的释放,但是感觉并没有大的优势,大部分情况下都会自动回收view

  3. 在使用bind方法开始绑定前,一定要确保view是完整的正确的已经被添加的

  4. ButterKnife也提供了findById(view,id)方法,省去了类型强转,在需要时候也可以使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值