写代码的时候,总会要不厌其烦的写findViewById,butterknife这个框架的作用就是利用注解来简化view查找过程。 先了解下注解的几个关键词: Target:限定使用范围 1.CONSTRUCTOR:用于描述构造器 2.FIELD:用于描述域 3.LOCAL_VARIABLE:用于描述局部变量 4.METHOD:用于描述方法 5.PACKAGE:用于描述包 6.PARAMETER:用于描述参数 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明 Retention:被保留的时间长短 1.SOURCE:在源文件中有效(即源文件保留) 2.CLASS:在class文件中有效(即class保留) 3.RUNTIME:在运行时有效(即运行时保留) JDK 5中引入了源代码中的注解(annotation)这一机制。注解使得Java源代码中不但可以包含功能性的实现代码,还可以添加元数据。注解的功能类似于代码中的注释,所不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。 只要知道注解大概就是可以方便的进行程序配置。 具体参考: https://docs.oracle.com/javase/tutorial/java/annotations/index.html http://computerdragon.blog.51cto.com/6235984/1210969 http://www.infoq.com/cn/articles/cf-java-annotation
最开始版本的butterknife使用反射来实现
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ViewInject { int value(); }像这样定义一个注解,然后通过遍历Activity或者Fragment中的成员变量来实现findViewById的过程
private static void injectView(Activity activity) { Class clazz = activity.getClass(); //获得activity的所有成员变量 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { //获得每个成员变量上面的ViewInject注解,没有的话,就会返回null ViewInject viewInject = field.getAnnotation(ViewInject.class); if (viewInject != null) { int viewId = viewInject.value(); View view = activity.findViewById(viewId); try { field.setAccessible(true); field.set(activity, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }但是这种方式的性能很低,特别是对类似Android这样的系统,对性能要求很苛刻的情况,最好不要用反射。所以Jake大神进行了修改。 新版本的Butterknife用的APT(Annotation Processing Tool)编译时解析技术。 APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind方法完成绑定。 先说下butterknife的几个模块: ‘:butterknife’ 静态绑定方法入口,查找生成类并存入缓存 ‘:butterknife-annotations’ 定义所有的注解类 ‘:butterknife-compiler’ 注解解析 ‘:butterknife-gradle-plugin’ 插件 ‘:butterknife-lint’ ‘:butterknife-integration-test’ 测试模块
定义注解,注意,这儿Retention是CLASS
@Retention(CLASS) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); }这个注解表明@BindView 是用来修饰 field 的,并且保留至编译时刻。内部有一个默认int类型的属性 value ,用来表示 View 的 id ,即平时程序中的 R.id.xxx
定义了注解之后,butterknife-compiler模块负责进行解析 核心类是ButterKnifeProcessor,它继承自AbstractProcessor,是专门处理annotation的类。 重写process方法后,在方法中butterknife进行的具体处理是
跟着代码具体看一下处理过程:
最后将 builderMap 转换为了 bindingMap 并返回。 到这儿准备工作已经完成,接下来就是根据准备好的bindingMap 生成辅助类,比如说 MainActivity_ViewBinding
public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public MainActivity_ViewBinding(MainActivity target, View source) { this.target = target; View view; target.textView = Utils.findRequiredViewAsType(source, R.id.text_view, "field 'textView'", TextView.class); target.toolbar = Utils.findRequiredViewAsType(source, R.id.toolbar, "field 'toolbar'", Toolbar.class); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.textView = null; target.toolbar = null; } }那这个是如何生成的呢,看process中的代码
JavaFile javaFile = binding.brewJava(sdk);是通过https://github.com/square/javapoet 来生成代码的 核心方法在brewJava中调用了 createType(sdk),具体使用方法参考javapoet官网
生成类之后如何使用呢,butterknife调用了静态方法bind,在bind方法中调用了createBinding,这个方法是根据 target 创建对应的 targetClassName_ViewBinding对象 。在 targetClassName_ViewBinding 的构造器中会把对应的 View 进行绑定(具体可以查看上面的 MainActivity_ViewBinding )。而在 findBindingConstructorForClass(Class cls) 方法中也使用了 Class.forName() 反射来查找 Class ,这也是无法避免的。但是仅限于一个类的第一次查找,之后都会从 BINDINGS 缓存中获取。
Demo地址:https://github.com/fanturbo/ButterKnifeStudy