• 07、Android进阶--ButterKnife原理解析


    ButterKnife原理

    ButterKnife自定义了很多我们常用的注解,比如@BindView和@OnClick。现在先来看@BindView的源码,如下所示:

    @Retention(RUNTIME) @Target(FIELD)
    public @interface BindView {
      /** View ID to which the field will be bound. */
      @IdRes int value();
    }
    

    Retention(CLASS)表明@BindView 注解是编译时注解,@Target(FIELD)则表明@BindView注解应用于成员变量。

    接下来使用@BindView注解来绑定TextView 控件,后文会用到这些代码,如下所示:

    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.tv_text)
        TextView tvText;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
        }
    }
    

    ButterKnifeProcessor源码分析

    要处理注解需要注解处理器,ButterKnife的注解处理器是ButterKnifeProcessor,它的主要处理逻辑都在process方法中,源码如下所示:

    @AutoService(Processor.class)
    public final class ButterKnifeProcessor extends AbstractProcessor {
    	......
        @Override
        public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
            // findAndParseTargets方法主要用于 查找和解析注解。
            Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
            // 遍历findAndParseTargets方法返回的Map集合
            for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
                TypeElement typeElement = entry.getKey();
                BindingSet binding = entry.getValue();
    			// 得到BindingSet的值,并调用了它的brewJava方法
                JavaFile javaFile = binding.brewJava(sdk, debuggable);
                try {
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
                }
            }
            return true;
        }
    	......
    }
    

    ButterKnifeProcessor继承自AbstractProcessor,我们再来看看findAndParseTargets方法:

    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
        ......
        for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
              try {
                  parseBindView(element, builderMap, erasedTargetNames);
              } catch (Exception e) {
                  logParsingError(element, BindView.class, e);
              }
        }
        ......
        return bindingMap;    
    }      
    

    findAndParseTargets 方法会查找所有 ButterKnife 的注解来进行解析,这里只看处理@BindView注解的部分。然后查看parseBindView方法,如下所示

    private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
                               Set<TypeElement> erasedTargetNames) {
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
        /**
         * isInaccessibleViaGeneratedCode方法里面检查了3个点,它们分别是:
         * 1、方法修饰符不能为private和static;
         * 2、包含类型不能为非Class;
         * 3、包含类的修饰符不能是private。
         */
        */
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
                || isBindingInWrongPackage(BindView.class, element);
    
        ......
    
        if (hasError) {
            return;
        }
        // 获取注解的标注的值。
        int id = element.getAnnotation(BindView.class).value();
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        Id resourceId = elementToId(element, BindView.class, id);
        // 判断是否存在BindingSet.Builder的值,若没有则创建,有则复用。
        if (builder != null) {
            String existingBindingName = builder.findExistingBindingName(resourceId);
            if (existingBindingName != null) {
                error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
                        BindView.class.getSimpleName(), id, existingBindingName,
                        enclosingElement.getQualifiedName(), element.getSimpleName());
                return;
            }
        } else {
            builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }
    
        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
        // 将注解修饰的类型的信息存储在 FieldViewBinding中,并将FieldViewBinding传入 BindingSet.Builder的addField方法中。
        builder.addField(resourceId, new FieldViewBinding(name, type, required));
        erasedTargetNames.add(enclosingElement);
    }
    

    可以看出,该方法主要用于检查类型和修饰符以及包名等,然后将注解修饰的类型的信息进行存储。

    接下来我们回到process方法,brewJava方法将使用注解的类生成一个JavaFile,然后将该JavaFile输出成Java文件。(javaFile.writeTo(filer))

    在build-generared-source-apt目录下可以找到生成的Java文件,这里生成的文件名为MainActivity_ViewBinding。

    ButterKnife的bind方法

    为了使用ButterKnife,我们需要用ButterKnife.bind()方法来绑定上下文。现在先来查看bind方法:

    @NonNull @UiThread
    public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return bind(target, sourceView);
    }
    

    bind 方法有很多重载方法,上面的代码只是其中的一种,也就是传入 Activity的情况。得到Activity的DecorView,并将DecorView和Activity传入bind方法中:

    @NonNull @UiThread
    public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        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;
        }
    
        try {
            // MainActivity_ViewBinding实例,并将Activity和其DecorView作为参数传入构造函数
            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);
        }
    }
    

    调用了findBindingConstructorForClass方法获取Activity构造函数,方法如下所示:

    @Nullable @CheckResult @UiThread
    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
        // 先从BINDINGS中获取对应Class的Constructor实例,BINDINGS是一个Class为key、Constructor为value的Map。
        Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
        if (bindingCtor != null || BINDINGS.containsKey(cls)) {
            if (debug) Log.d(TAG, "HIT: Cached in binding map.");
            return bindingCtor;
        }
        String clsName = cls.getName();
        if (clsName.startsWith("android.") || clsName.startsWith("java.")
            || clsName.startsWith("androidx.")) {
            if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
            return null;
        }
        try {
            // 通过反射来生成Class类,这个Class类就是我们此前生成的MainActivity_ViewBinding。
            Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
            // 调用getConstructor方法将Class转换为Constructor,
            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);
        }
        // 处将Constructor作为value,Class作为key存储在BINDINGS中,最后返回该Constructor。
        BINDINGS.put(cls, bindingCtor);
        return bindingCtor;
    }
    

    代码注释中使用到了反射的方式来创建MainActivity_ViewBinding,反射会影响性能,但是因为有 BINDERS的存在(一个类只会在第一次反射生成,以后会从BINDERS中去取),也可以解决一些性能问题。

    生成的辅助类分析

    MainActivity_ViewBinding.java,在appuildgeneratedap_generated_sourcesdebugoutcomlegendutterknife文件夹下,代码如下所示:

    public class MainActivity_ViewBinding implements Unbinder {
        private MainActivity target;
        // source值就是MainActivity的 DecorView,而target值为MainActivity 
        @UiThread
        public MainActivity_ViewBinding(MainActivity target) {
            this(target, target.getWindow().getDecorView());
        }
    
        @UiThread
        public MainActivity_ViewBinding(MainActivity target, View source) {
            this.target = target;
            // 调用了Utils的findRequiredViewAsType方法并将source值、R.id.tv_text等参数传入,findRequiredViewAsType方法
            target.tvText = Utils.findRequiredViewAsType(source, R.id.tv_text, "field 'tvText'", TextView.class);
        }
    
        @Override
        @CallSuper
        public void unbind() {
            MainActivity target = this.target;
            if (target == null) throw new IllegalStateException("Bindings already cleared.");
            this.target = null;
    
            target.tvText = null;
        }
    }
    

    在前面我们已经知道在 bind方法中调用 newInstance 方法生成 MainActivity_ViewBinding实例时传入的source值是MainActivity的DecorView。而target值为MainActivity。

    注释处,findRequiredViewAsType传入source的值和R.id.tv_text等参数,让我们看看该方法:

    public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
                                               Class<T> cls) {
        View view = findRequiredView(source, id, who);
        return castView(view, id, who, cls);
    }
    

    该方法调用findRequiredView方法,代码如下所示:

    public static View findRequiredView(View source, @IdRes int id, String who) {
        View view = source.findViewById(id);
        if (view != null) {
            return view;
        }
    	......
    }
    

    findRequiredView方法调用了DecorView的findViewById方法并将R.id.tv_text对应的View返回。再看看castView方法:

    public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
        try {
            return cls.cast(view);
        } catch (ClassCastException e) {
            String name = getResourceEntryName(view, id);
    		......
        }
    }
    

    castView方法会将View强制转换为TextView并返回。再次回到MainActivity_ViewBinding辅助类,这个返回的TextView会赋值给target,也就是MainActivity,这样我们在MainActivity中就可以使用这个TextView。

    总结

    ButterKnifeProcessor主要用于查找所有的注解,并生成相应的xxxx_ViewBingding类,然后在ButterKnife的bind方法来通过反射创建xxx_ViewBinding类的同时,将Activity的引用和DecorView的引用通过构造函数的方式传入,通过DecorView的findViewById方法拿到View的实例,然后在castView方法中进行强转并返回。

  • 相关阅读:
    在页面生命周期执行时 Page 对象在 SaveState 阶段都发生了什么事?
    接收Firfox RESTClient #Post请求
    c# 单例模式[Singleton]之深夜闲聊
    JQuery 之 Ajax 异步和同步浅谈
    [模板]数学整合
    Yandex插件使用说明——Slager_Z
    模板练习(LUOGU)
    数学整合 新(LUOGU)
    [NOI.AC]DELETE(LIS)
    [NOI.AC]COUNT(数学)
  • 原文地址:https://www.cnblogs.com/pengjingya/p/15000393.html
Copyright © 2020-2023  润新知