美文网首页
ButterKnife原理

ButterKnife原理

作者: gczxbb | 来源:发表于2019-03-01 16:47 被阅读0次

    参考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 修饰,否则会报错。


    任重而道远

    相关文章

      网友评论

          本文标题:ButterKnife原理

          本文链接:https://www.haomeiwen.com/subject/fabduqtx.html