2019-03-14 ButterKnife 源码解析和手写

2019-03-14  本文已影响0人  猫KK

ButterKnife 使用

引入 ButterKnife 可以去这里查看最新的版本,我使用的是8.8.0版本,10.0.0版本有兼容问题,没有使用,但是源码基本一样

//gradle 文件中
dependencies {
    implementation 'com.jakewharton:butterknife:8.8.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.0'
}

使用:

public class Main2Activity extends AppCompatActivity {

     //绑定控件,不用再写 findViewById
    @BindView(R.id.tv_test)
    TextView mTv_1;
    @BindView(R.id.tv_test1)
    TextView mTv_2;
    private Unbinder mBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        //绑定对应activity
        mBind = ButterKnife.bind(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //新版本的ButterKnife 解注册,防止内存泄露
        if (mBind != null) {
            mBind.unbind();
        }
    }

    //绑定点击事件
    @OnClick({R.id.tv_test1, R.id.tv_test})
    void onClick(View view) {

    }
}

除了上面的绑定控件、点击事件,还可以绑定 String 、Animation 等一系列

分析:@BindView、@OnClick 是自定义的注解,并且为编译时注解

//CLASS 表示为编译时的注解
@Retention(CLASS) 
//FIELD 表示为变量注解
@Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

// METHOD 表示为方法注解
@Target(METHOD)
//CLASS 表示为编译时的注解
@Retention(CLASS)
@ListenerClass(
    targetType = "android.view.View",
    setter = "setOnClickListener",
    type = "butterknife.internal.DebouncingOnClickListener",
    method = @ListenerMethod(
        name = "doClick",
        parameters = "android.view.View"
    )
)
public @interface OnClick {
  /** View IDs to which the method will be bound. */
  @IdRes int[] value() default { View.NO_ID };
}

只是定义了注解,什么都没做。继续看 ButterKnife.bind(this);

    public static Unbinder bind(@NonNull Activity target) {
    //获取DecorView
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

    private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    //获取Class
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //获取构造方法
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      //通过构造方法反射创建 class
      //所以ButterKnife 也使用了反射
      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);
    }
  }

    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //通过缓存获取
    //BINDINGS 是一个静态的LinkedHashMap
    //使用缓存提高性能
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    //判断缓存是否存在,存在就直接返回
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    //判断 cls 名称,是否可以使用
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
       //通过ClassLoader 获取对应的Class
      //注意该 Class为 xxx_ViewBinding,按照事例为 Main2Activity_ViewBinding
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      //通过 Class 获取构造方法
      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);
    }
    //放入缓存
    BINDINGS.put(cls, bindingCtor);
    //返回
    return bindingCtor;
  }

所以上面的代码为:通过反射创建了一个 Main2Activity_ViewBinding 对象,并将当前 activity 实例传进去

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        mBind = ButterKnife.bind(this);
        //ButterKnife.bind(this) 就相当于 new 一个对象
        mBind = new Main2Activity_ViewBinding(this);
    }

等等,Main2Activity_ViewBinding 在哪里,都没有新建过这个类,这个类是在 build/generatd/source/apt/debug/对应报名 下,看做了什么

public class Main2Activity_ViewBinding implements Unbinder {
  private Main2Activity target;

  private View view2131165338;

  private View view2131165339;

  @UiThread
  public Main2Activity_ViewBinding(final Main2Activity target, View source) {
    this.target = target;

    View view;
    //Utils.findRequiredView 其实就是调用了 findViewById 方法
    view = Utils.findRequiredView(source, R.id.tv_test, "field 'mTv_1' and method 'onClick'");
    target.mTv_1 = Utils.castView(view, R.id.tv_test, "field 'mTv_1'", TextView.class);
    view2131165338 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onClick(p0);
      }
    });
    view = Utils.findRequiredView(source, R.id.tv_test1, "field 'mTv_2' and method 'onClick'");
    target.mTv_2 = Utils.castView(view, R.id.tv_test1, "field 'mTv_2'", TextView.class);
    view2131165339 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onClick(p0);
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {
    Main2Activity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.mTv_1 = null;
    target.mTv_2 = null;

    view2131165338.setOnClickListener(null);
    view2131165338 = null;
    view2131165339.setOnClickListener(null);
    view2131165339 = null;
  }
}

所以其实就是 new Main2Activity_ViewBinding 对象,在构造方法中找到对应的变量做了一遍 findViewById。那么这个 Main2Activity_ViewBinding 类是什么时候创建的呢?通过编译时的注解,在编译的时候通过apt创建出对应的类,下面来模仿手写

手写ButterKnife

可以去 github 上下载ButterKnife 的源码来看,我就直接仿着写:定义三个lib 两个 java lib 一个 android lib


WechatIMG51.jpeg

butterknife 为 android lib 主要存放反射生成 xxx__ViewBinding 方法和对应 findViewById 方法
butterknife_annotations 为 java lib 主要存放注解类型
butterknife_compiler 为 java lib 主要存放编译时自动生成类的类和方法
先来看butterknife_annotations 这个 lib

 //定义ViewBind 注解,用于findViewById
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewBind {
    int value();
}

 //定义OnViewClick 注解,用于绑定view 的监听事件
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnViewClick {
    int[] value();
}

再来看 butterknife_compiler

    dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    //引入 butterknife_annotations 使用里面定义的注解
    implementation project(':butterknife_annotations')
    //下面这两个是专门用来编译生成类的
    implementation 'com.squareup:javapoet:1.10.0'
    implementation 'com.google.auto.service:auto-service:1.0-rc4'
}

新建 ButterKnifeProcessor

//注意增加 AutoService 注解
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
    //重写方法,返回版本号
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    
    //获取Filer 和 ElementUtils
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mElementUtils = processingEnvironment.getElementUtils();
    }

    //添加注解的类型
    //仿照 ButterKnife 源码写的
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(ViewBind.class);
        annotations.add(OnViewClick.class);
        return annotations;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //编译时,就会走这个方法里面
        //测试是否走到这里面
        System.out.println("----------------->");
    }
}

经过上面,我们大概书写完成,先来测试,判断是否会走到输出的地方,现在 app 工程下增加依赖

    dependencies {
        //依赖三个我们新建的lib
        implementation project(':butterknife')
        implementation project(':butterknife_annotations')
        //注意,这里使用annotationProcessor 这是新版本 apt 的使用方式
        annotationProcessor project(':butterknife_compiler')
    }

依赖完成之后,运行app工程,在build下查看是否有打印


WechatIMG415.jpeg

在这里面如果能打印日志出来,说明配置没有问题,继续走下一步,否则,需要查看配置是不是有问题
如果上面的没有问题,接下来来编写 process 方法,我直接上代码,

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //获取 ViewBind 的Element
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ViewBind.class);
        //获取 OnViewClick Element
        Set<? extends Element> clickElements = roundEnvironment.getElementsAnnotatedWith(OnViewClick.class);
        //新建 Map 用来保存不同类的内容
        //一个 TypeElement 对应一个有注解的 activity
        Map<TypeElement, List<ElementBindSet>> map = new LinkedHashMap<>();
        //遍历 ViewBind 的 Element
        for (Element element : elements) {
            //注解的变量名
            String simpleName = element.getSimpleName().toString();
            //注解ID
            int value = element.getAnnotation(ViewBind.class).value();
            //对应所在class Element
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            //保存进 map 中
            //ElementBindSet 是一个自定义的类,里面保存有 Element 和 type 字段
            //用于区分是 ViewBind 还是 OnViewClick
            List<ElementBindSet> elementList = map.get(typeElement);
            if (elementList == null) {
                elementList = new ArrayList<>();
                map.put(typeElement, elementList);
            }
            ElementBindSet bindSet = new ElementBindSet();
            bindSet.mElement = element;
            bindSet.type = 0;
            elementList.add(bindSet);
        }
        //遍历 OnViewClick
        for (Element clickElement : clickElements) {
            TypeElement typeElement = (TypeElement) clickElement.getEnclosingElement();
            List<ElementBindSet> elementList = map.get(typeElement);
            if (elementList == null) {
                elementList = new ArrayList<>();
                map.put(typeElement, elementList);
            }
            ElementBindSet bindSet = new ElementBindSet();
            bindSet.mElement = clickElement;
            bindSet.type = 1;
            elementList.add(bindSet);
        }
        //遍历 map 有几个 TypeElement 说明要创建几个类
        for (TypeElement typeElement : map.keySet()) {
            //获取包名
            String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();
            //获取类名
            String className = typeElement.getSimpleName().toString();
            //通过 ClassName 来获取 定义的 Unbinder 接口
            ClassName unbinder = ClassName.get("com.butterknife", "Unbinder");
            //通过 ClassName 获取类
            ClassName target = ClassName.get(packageName, className);
            //创建 unbind 方法,在里面编写代码
            MethodSpec.Builder unbind_builder = MethodSpec.methodBuilder("unbind")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(TypeName.VOID)
                    .addStatement("$N target = this.target", className)
                    .addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")")
                    .addStatement("this.target = null");
            //创建构造方法
            MethodSpec.Builder construction_builder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(target, "target", Modifier.FINAL)
                    .addStatement("this.$N = $N", "target", "target");
            //得到对应 TypeElement 中的 ElementBindSet
            List<ElementBindSet> elementList = map.get(typeElement);
            //通过 ClassName 获取 com.butterknife.Utils
            //Utils 是自己定义的工具类,用来做 findViewById 操作
            ClassName utils = ClassName.get("com.butterknife", "Utils");
            //通过 ClassName 获取 view
            ClassName view = ClassName.get("android.view", "View");
            //获取 DebouncingOnClickListener
            //DebouncingOnClickListener 是一个实现点击事件接口的抽象类
            ClassName debouncingOnClickListener = ClassName.get("com.butterknife", "DebouncingOnClickListener");
            //新建对应 xxx_ViewBinding 的  TypeSpec.Builder 用于生成类代码
            TypeSpec.Builder bindingBuilder = TypeSpec.classBuilder(className + "_ViewBinding")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addSuperinterface(unbinder);
            //判断是否包含 OnViewClick
            if (checkHaveClick(elementList)) {
                //如果包含,就在构造方法中增加 View view 代码
                construction_builder.addStatement("$N view", view.toString());
            }
            //遍历所有 ElementBindSet 即所有的注解
            for (ElementBindSet element : elementList) {
                //如果为 ViewBind
                if (element.type == 0) {
                    //获取该注解的 参数名
                    String simpleName = element.mElement.getSimpleName().toString();
                    //获取注解 ID 值
                    int id = element.mElement.getAnnotation(ViewBind.class).value();
                    //在构造方法中添加代码
                    // target.参数名 = Utils.findViewById(target,id);
                    construction_builder.addStatement("target.$N = $N.findViewById(target, $N)",
                            simpleName, utils.toString(), String.valueOf(id));
                    //在 unbind 方法中添加代码
                    //target.参数名 = null
                    unbind_builder.addStatement("target.$N = null", simpleName);
                }
                //如果为 OnViewClick
                if (element.type == 1) {
                    //获取注解 ID 所有值
                    int[] onClickIds = element.mElement.getAnnotation(OnViewClick.class).value();
                    for (int onClickId : onClickIds) {
                        bindingBuilder.addField(view, "view" + onClickId);
                        construction_builder.addStatement("view = $N.findViewById(target, $N)", utils.toString(), String.valueOf(onClickId));
                        construction_builder.addStatement("view" + onClickId + "= view");
                        //新增监听点击的代码
                        MethodSpec.Builder onClickBuilder = MethodSpec.methodBuilder("doClick")
                                .addAnnotation(Override.class)
                                .addModifiers(Modifier.PUBLIC)
                                .returns(TypeName.VOID)
                                .addParameter(view, "p0")
                                .addStatement("target.$N(p0)", element.mElement.getSimpleName());
                        TypeSpec onClick = TypeSpec.anonymousClassBuilder("")
                                .superclass(debouncingOnClickListener)
                                .addMethod(onClickBuilder.build())
                                .build();
                        construction_builder.addStatement("view.setOnClickListener($N)", onClick.toString());
                        //在 unbind 方法中清空
                        unbind_builder.addStatement("view" + onClickId + ".setOnClickListener(null)");
                        unbind_builder.addStatement("view" + onClickId + "=null");
                    }
                }
            }
            //在类中添加对应的方法和构造方法
            bindingBuilder.addMethod(unbind_builder.build())
                    .addMethod(construction_builder.build())
                    .addField(target, "target");
            try {
                //同噶javaFile生成对应的类
                JavaFile javaFile = JavaFile.builder(packageName, bindingBuilder.build()).build();
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

代码很长,只需要了解语法,跟着 ButterKnife 生成的 xxx _ViewBinding 类来敲,很简单的,下面来完善butterknife这个lib 的内容,其实就是增加几个方法
新增 Butter 类,用来和 ButterKnife 区别

    public class Butter {
    static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

    public static Unbinder bind(Activity activity) {
        Class<?> aClass = activity.getClass();
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(aClass);
        if (constructor == null) {
            return Unbinder.EMPTY;
        }
        try {
            return constructor.newInstance(activity);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } 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);
        }
        return Unbinder.EMPTY;
    }

    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        if (bindingCtor != null || BINDINGS.containsKey(cls)) {
            return bindingCtor;
        }
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")
                || clsName.startsWith("androidx.")) {
            return null;
        }
        try {
            Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
            //需要注意的是,我们定义的只有一个参数的构造函数,所以传一个参数
            bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls);
        } 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;
    }
}

其实就是照抄 ButterKnife 的代码,需要注意的是我们定义的只有一个参数的构造函数,所以传一个参数来获取Constructor,然后就是剩下需要的类

    //定义 Unbinder 接口,照抄
    public interface Unbinder {
    void unbind();

    Unbinder EMPTY = new Unbinder() {
        @Override
        public void unbind() {

        }
    };
}
   
    //定义 DebouncingOnClickListener 用于点击事件,照抄
    public abstract class DebouncingOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
        doClick(v);
    }

    public abstract void doClick(View v);
}

    //utils 就是做了 findViewById
    public class Utils {
    public static <T extends View> T findViewById(Activity activity, int id) {
        return activity.findViewById(id);
    }
}

大功告成,最后来修改前面的 Main2Activity ,换上我们自己的注解

public class Main2Activity extends AppCompatActivity {

    @ViewBind(R.id.tv_test)
    TextView mTv_1;
    @ViewBind(R.id.tv_test1)
    TextView mTv_2;
    private Unbinder mBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
//        mBind = ButterKnife.bind(this);
//        mBind = new Main2Activity_ViewBinding(this);
        mBind = Butter.bind(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mBind != null) {
            mBind.unbind();
        }
    }

    @OnViewClick({R.id.tv_test1, R.id.tv_test})
    void onClick(View view) {

    }
}

完美运行。。。。。。

上一篇下一篇

猜你喜欢

热点阅读