zoukankan      html  css  js  c++  java
  • O3-开源框架使用之Butterknife 8.8.1及源码浅析

    零、前言

    我最喜欢的框架,没有之一:
    编译期生成代码的方式,对运行时没有任何副作用。
    加上AndroidStudio快捷键,简直好用之至。

    添加依赖:
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    
    混淆
    ## butterknife start
    -keep class butterknife.** { *; }
    -dontwarn butterknife.internal.**
    -keep class **$$ViewBinder { *; }
    
    -keepclasseswithmembernames class * {
        @butterknife.* <fields>;
    }
    
    -keepclasseswithmembernames class * {
        @butterknife.* <methods>;
    }
    ## butterknife end
    

    一、Activity中使用

    1.基础使用:
    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.id_tv)
        TextView mIdTv;//绑定视图
        @BindView(R.id.id_btn)
        Button mIdBtn;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //1.绑定Activity
            ButterKnife.bind(this);
        }
    
        @OnClick(R.id.id_btn)//单机事件
        public void onViewClicked() {
            Log.e(TAG, "onViewClicked: ");
        }
    
        @OnLongClick(R.id.id_btn)//长按事件
        public boolean onViewLongClicked() {
            Log.e(TAG, "onViewLongClicked: ");
    
            //和原生一样,返回true,抬起时不触发单机
            return true;
        }
    }
    
    
    多事件:
    @OnClick({R.id.id_btn, R.id.imageView})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.id_btn:
                break;
            case R.id.imageView:
                break;
        }
    }
    

    二、Fragment中使用:

    public class MyFragment extends Fragment {
    
        @BindView(R.id.imageView)
        ImageView mImageView;
        Unbinder unbinder;
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fg_test, container, false);
            //绑定View
            unbinder = ButterKnife.bind(this, view);
            return view;
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            unbinder.unbind();
        }
    
        //销毁时解绑View
        @OnClick(R.id.imageView)
        public void onViewClicked() {
            Toast.makeText(getContext(), "hello", Toast.LENGTH_LONG).show();
        }
    }
    

    附:使用Fragment

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(R.id.id_fl,new MyFragment());
    ft.commit();
    

    还有其他很多注解,感觉都没用太大用,下面看一下源码是怎么工作的


    三、源码浅析:

    1、首先来看这句话都进行了哪些事:ButterKnife.bind(this);

    ---B0:butterknife.ButterKnife#bind(android.app.Activity)

    bind有6个重载的方法:这里使用的是一参Activity的bind方法

      @NonNull @UiThread
      public static Unbinder bind(@NonNull Activity target) {
      //获取Activity对应窗口上的最顶端布局
        View sourceView = target.getWindow().getDecorView();
        //调用createBinding方法,见--B1
        return createBinding(target, sourceView);
      }
    
    --B1:butterknife.ButterKnife#createBinding

    这算一个非常核心的方法,6个bind()方法都是调用它

    private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
        //获取target的class
        Class<?> targetClass = target.getClass();
        if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
        //获取绑定Class的构造函数,见--B2
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        //如果构造函数是空的,返回EMPTY的Unbinder枚举
        if (constructor == null) {
          return Unbinder.EMPTY;
        }
    
        //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
        try {
        //返回使用构造函数创建MainActivity_ViewBinding实例:
          return constructor.newInstance(target, source);
        } catch (IllegalAccessException e) {
          throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InstantiationException e) {
          throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InvocationTargetException e) {
          Throwable cause = e.getCause();
          if (cause instanceof RuntimeException) {
            throw (RuntimeException) cause;
          }
          if (cause instanceof Error) {
            throw (Error) cause;
          }
          throw new RuntimeException("Unable to create binding instance.", cause);
        }
      }
    
    --B2:butterknife.ButterKnife#findBindingConstructorForClass

    通过字节码文件获取类的构造函数

     @Nullable @CheckResult @UiThread
      private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
      
      //BINDINGS的声明:可见是一个LinkedHashMap,以class为键,构造函数为值。
      //static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
        //从map中拿传入的cls的构造函数
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        //如果不为空
        if (bindingCtor != null) {
          if (debug) Log.d(TAG, "HIT: Cached in binding map.");
          //就返回拿到的构造函数
          return bindingCtor;
        }
        //否则,获取字节码文件的名字:如:com.toly1994.butterknifetest.MainActivity
        String clsName = cls.getName();
        //如果名字的字符串,是以android.或java.开头的
        if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
          if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
          //返回空
          return null;
        }
        try {
        //加载com.toly1994.butterknifetest.MainActivity_ViewBinding类生成Clazz对象bindingClass:见:--B3
          Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
          //noinspection unchecked
          //获取自动生成的MainActivity_ViewBinding中的两参构造函数
          bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
          if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
        } catch (ClassNotFoundException e) {
          if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
          bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
        } catch (NoSuchMethodException e) {
          throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
        }
        //将cls和获取到的构造函数放入map
        BINDINGS.put(cls, bindingCtor);
        return bindingCtor;
      }
    
    --B3:见鬼,哪来的什么MainActivity_ViewBinding让我加载?

    Butter Knife会自动创建这个类,我们来看看它的庐山真面目

    9414344-a455518b1214892d.png
    MainActivity_ViewBinding.png

    可见bind方法,主要是把XxxActivity创建一个XxxActivity_ViewBinding,并创建一个XxxActivity_ViewBinding对象


    2、现在焦点就在MainActivity_ViewBinding的身上

    com.toly1994.butterknifetest.MainActivity_ViewBinding
    // Generated code from Butter Knife. Do not modify!
    
    public class MainActivity_ViewBinding implements Unbinder {
    //持有一个MainActivity的引用
      private MainActivity target;
    //持有一个View的引用
      private View view2131165244;
    
    //一参构造:调用两参构造
      @UiThread
      public MainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
      
    //两参构造:
      @UiThread
      public MainActivity_ViewBinding(final MainActivity target, View source) {
     //target赋值
        this.target = target;
        View view;
        //将target对象中的mIdTv赋值为:findRequiredViewAsType(视图,id,字段介绍,类名)方法:见--B4
        target.mIdTv = Utils.findRequiredViewAsType(source, R.id.id_tv, "field 'mIdTv'", TextView.class);
        //findRequiredView找到按钮,见:--B4-1
        view = Utils.findRequiredView(source, R.id.id_btn, "field 'mIdBtn' and method 'onViewClicked'");
        //view强转后为target对象中的mIdBtn赋值
        target.mIdBtn = Utils.castView(view, R.id.id_btn, "field 'mIdBtn'", Button.class);
        view2131165244 = view;
        //为按钮设置监听:见--B5
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
          //这里是调用的target也就是Activity中的onViewClicked方法
          //应该知道为什么可以简便的写点击事件了吧
            target.onViewClicked();
          }
        });
      }
    
      @Override
      @CallSuper
      //解绑:置空操作
      public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
        target.mIdTv = null;
        target.mIdBtn = null;
        view2131165244.setOnClickListener(null);
        view2131165244 = null;
      }
    }
    
    
    --B4:butterknife.internal.Utils#findRequiredViewAsType

    根据类型查询需要的View
    这个who只是在抛异常的时候告诉你,是谁异常

      public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
          Class<T> cls) {
         //findRequiredView(视图,id,字段介绍):见--B4-1
        View view = findRequiredView(source, id, who);
        //castView(视图,id,字段, Class):见--B4-2
        return castView(view, id, who, cls);
      }
    
    --B4-1:findRequiredView(视图,id,字段介绍)

    看到findViewById有没有小激动

      public static View findRequiredView(View source, @IdRes int id, String who) {
      //真正的findViewById操作
        View view = source.findViewById(id);
        if (view != null) {
        //如果视图不为空就返回找到的视图
          return view;
        }
        //视图为空,就抛出一个IllegalStateException异常:
        String name = getResourceEntryName(source, id);
        throw new IllegalStateException("Required view '"
            + name
            + "' with ID "
            + id
            + " for "
            + who
            + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
        
    
    --B4-2:castView(视图,id,字段, Class)
    public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
      try {
      //将View强转为T类型,T类型是Class<T>中的泛型,即findRequiredViewAsType中传入的类型
        return cls.cast(view);
      } catch (ClassCastException e) {
        String name = getResourceEntryName(view, id);
        throw new IllegalStateException("View '"
            + name
            + "' with ID "
            + id
            + " for "
            + who
            + " was of the wrong type. See cause for more info.", e);
      }
    }
    

    cast()方法是Clazz的一个公共方法:由下可见它反会一个由传入值强转成的T类型对象

        @SuppressWarnings("unchecked")
        public T cast(Object obj) {
            if (obj != null && !isInstance(obj))
                throw new ClassCastException(cannotCastMsg(obj));
            return (T) obj;
        }
    
    --B5:这是butterknife源码中的一个类:

    继承自:View.OnClickListener

    public abstract class DebouncingOnClickListener implements View.OnClickListener {
    //是否可用
      static boolean enabled = true;
    
    //可以再次使用
      private static final Runnable ENABLE_AGAIN = new Runnable() {
        @Override public void run() {
          enabled = true;
        }
      };
    
      @Override public final void onClick(View v) {
      //如果可用
        if (enabled) {
        //设置为不可用
          enabled = false;
          //
          v.post(ENABLE_AGAIN);
          doClick(v);//模板方法
        }
      }
    
      public abstract void doClick(View v);
    }
    
    

    后记、

    1.声明:

    [1]本文由张风捷特烈原创,转载请注明
    [2]欢迎广大编程爱好者共同交流
    [3]个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    [4]你的喜欢与支持将是我最大的动力

    2.连接传送门:

    更多安卓技术欢迎访问:安卓技术栈
    我的github地址:欢迎star
    简书首发,腾讯云+社区同步更新
    张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com

    3.联系我

    QQ:1981462002
    邮箱:1981462002@qq.com
    微信:zdl1994328

    4.欢迎关注我的微信公众号,最新精彩文章,及时送达:
    9414344-c474349cd3bd4b82.jpg
    公众号.jpg
  • 相关阅读:
    Oracle中创建视图
    SQL Server 2012 Express安装图解
    oracle学习笔记
    Oracle中视图的创建和处理方法
    DDL、DML和DCL的理解
    ROS学习--如何结合launch文件使用参数服务器
    stm32多块开发板can总线互联卡死问题
    ROS CAN总线设备接入(二)can总线数据提取和以ros topic形式发布
    ROS CAN总线设备接入(一)Linux动态库的显式调用
    ROS .so载入undefined reference to `dlopen'问题
  • 原文地址:https://www.cnblogs.com/toly-top/p/9781872.html
Copyright © 2011-2022 走看看