美文网首页
ButterKnife源码解析

ButterKnife源码解析

作者: shuixingge | 来源:发表于2016-08-11 12:41 被阅读189次

    参考1
    参考2
    参考3
    参考4

    一:基本原理

    编译时注解+APT
    编译时注解:Rentention为CLASS的直接。保留时间是编译期。
    APT(Annotation Processing Tool)编译时解析技术)。
    声明的注解的生命周期为CLASS,然后创建注解处理器,即继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,处理完后,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind
    方法完成绑定。

    二:bind过程

    (1)@Bind注解

    @Retention(CLASS) @Target(FIELD)
    public @interface Bind {
      /** View ID to which the field will be bound. */
      int[] value();
    }
    

    (2)ButterKnife.bind()

    public static void bind(Activity target) {
        bind(target, target, Finder.ACTIVITY);
      }
    static void bind(Object target, Object source, Finder finder) {
        Class<?> targetClass = target.getClass();
        try {
          if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
          ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
          if (viewBinder != null) {
            viewBinder.bind(finder, target, source);
          }
        } catch (Exception e) {
          throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
        }
      }
    

    (1) 获取Activity的class对象
    (2)通过findViewBinderForClass()找到该Activity对应的ViewBinder
    (3)调用ViewBinder.bind()方法

    (3) ButterKnife.findViewBinderForClass()

     private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
          throws IllegalAccessException, InstantiationException {
        ViewBinder<Object> viewBinder = BINDERS.get(cls);
        if (viewBinder != null) {
          if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
          return viewBinder;
        }
        String clsName = cls.getName();
        if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
          if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
          return NOP_VIEW_BINDER;
        }
        try {
          Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
          //noinspection unchecked
          viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
          if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
        } catch (ClassNotFoundException e) {
          if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
          viewBinder = findViewBinderForClass(cls.getSuperclass());
        }
        BINDERS.put(cls, viewBinder);
        return viewBinder;
      }
    

    (1)从缓存LinkedHashMap中取ViewBinder,若能取出,则说明ViewBinder已经生成过。
    (2) 否则 得到ViewBinder的子类的类名(MainActivity$$ViewBinder),然后通过反射的形式创建(MainActivity$$ViewBinder)的实例,并存入缓存。
    Tips: ViewBinder

      public interface ViewBinder<T> {
        void bind(Finder finder, T target, Object source);
        void unbind(T target);
      }
    

    举例说明

    public class MainActivity extends AppCompatActivity {
        @Bind(R.id.text_view)
        TextView textView;
    
        @OnClick(R.id.text_view)
        void onClick(View view) {
            textView.setText("我被click了");
        }
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
            textView.setText("我还没有被click");
        }
    }
    
    

    反编译代码
    源代码就多了一个类MainActivity$$ViewBinder

    public class MainActivity$$ViewBinder<T extends MainActivity>
                 implements ButterKnife.ViewBinder<T>{
        public void bind(ButterKnife.Finder paramFinder,
               final T paramT, Object paramObject) {
            View localView = (View)paramFinder.findRequiredView(paramObject,
                 2131492944, "field 'textView' and method 'onClick'");
            paramT.textView = ((TextView)paramFinder.castView(localView,
                 2131492944, "field 'textView'"));
            localView.setOnClickListener(new DebouncingOnClickListener() {
                public void doClick(View paramAnonymousView) {
                    paramT.onClick(paramAnonymousView);
                }
            });
        }
    
        public void unbind(T paramT) {
            paramT.textView = null;
        }
    }
    

    首先调用了Finder的findRequiredView方法,其实这个方法最后经过处理就是调用了findView方法,拿到相应的view,然后再赋值给paramT.textView,paramT就是那个要绑定的Activity
    (4)ButterKnife.Finder(enum)

    public enum Finder {
        VIEW {
          @Override protected View findView(Object source, int id) {
            return ((View) source).findViewById(id);
          }
    
          @Override public Context getContext(Object source) {
            return ((View) source).getContext();
          }
        },
        ACTIVITY {
          @Override protected View findView(Object source, int id) {
            return ((Activity) source).findViewById(id);
          }
    
          @Override public Context getContext(Object source) {
            return (Activity) source;
          }
        },
        DIALOG {
          @Override protected View findView(Object source, int id) {
            return ((Dialog) source).findViewById(id);
          }
    
          @Override public Context getContext(Object source) {
            return ((Dialog) source).getContext();
          }
        };
    

    三:APT编译期生成代码的流程

    ButterknifeProcessor:注解处理器继承于AbstractProcessor

    (1)在实现AbstractProcessor后,process()方法是必须实现的
    (2)一般会实现getSupportedAnnotationTypes()和getSupportedSourceVersion();这两个方法一个返回支持的注解类型,一个返回支持的源码版本,参考上面的代码,写法基本是固定的。
    (3)除此以外,我们还会选择复写init()方法,该方法传入一个参数processingEnv,可以帮助我们去初始化一些辅助类:
    Filer mFileUtils: 跟文件相关的辅助类,生成JavaSourceCode.
    Elements mElementUtils:跟元素相关的辅助类,帮助我们去获取一些元素相关的信息。
    Messager mMessager:跟日志相关的辅助类。

    (1) ButterknifeProcessor.int()

     @Override 
      public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
    
        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
      }
    
    
    

    (2) ButterknifeProcessor.getSupportAnnotationTypes()
    BindArray:一个类对象,代表具体某个类的代理类生成的全部信息

    @Override public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
    
        types.add(BindArray.class.getCanonicalName());
        types.add(BindBitmap.class.getCanonicalName());
        types.add(BindBool.class.getCanonicalName());
        types.add(BindColor.class.getCanonicalName());
        types.add(BindDimen.class.getCanonicalName());
        types.add(BindDrawable.class.getCanonicalName());
        types.add(BindInt.class.getCanonicalName());
        types.add(BindString.class.getCanonicalName());
        types.add(BindView.class.getCanonicalName());
        types.add(BindViews.class.getCanonicalName());
    
        for (Class<? extends Annotation> listener : LISTENERS) {
          types.add(listener.getCanonicalName());
        }
    
        return types;
      }
    

    (2) ButterknifeProcessor.process()
    这个方法的作用主要是扫描、评估和处理我们程序中的注解,然后生成Java文件。即ViewBinder。

    @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
         //下面这一句解析注解
        Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
      //解析完成以后,需要生成的代码结构已经都有了,它们都存在于每一个 BindingClass 当中
        for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingClass bindingClass = entry.getValue();
    
          try {
            bindingClass.brewJava().writeTo(filer);
          } catch (IOException e) {
            error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
                e.getMessage());
          }
        }
    
        return true;
      }
    

    (2) ButterknifeProcessor.findAndParseTargets()
    (1)扫描所有具有注解的类,然后根据这些类的信息生成BindingClass,最后生成以TypeElement为键,BindingClass为值的键值对。
    (2)循环遍历这个键值对,根据TypeElement和BindingClass里面的信息生成对应的java类。例如AnnotationActivity生成的类即为Cliass$$ViewBinder类。

     private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
        Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
        Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
        // Process each @BindArray element.
        for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
            parseResourceArray(element, targetClassMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindArray.class, e);
          }
        }
    
        // Process each @BindBitmap element.
        for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
          if (!SuperficialValidation.validateElement(element)) continue;
          try {
            parseResourceBitmap(element, targetClassMap, erasedTargetNames);
          } catch (Exception e) {
            logParsingError(element, BindBitmap.class, e);
          }
        } 
                               ......                     
    

    四:Javapoet:生成Java代码

    (1)以代码拼接的方式
    (2)TypeSpec代表类,MethodSpec代表方法(Builder模式)

    TypeSpec.Builder result = TypeSpec.classBuilder(className)
     //添加修饰符为 public,生成的类是 public 的
         .addModifiers(PUBLIC)
         .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
    
     MethodSpec.Builder result = MethodSpec.methodBuilder("bind")
             .addAnnotation(Override.class)
             .addModifiers(PUBLIC)
             .addParameter(FINDER, "finder", FINAL)
             .addParameter(TypeVariableName.get("T"), "target", FINAL)
             .addParameter(Object.class, "source");
    

    五:ButterKnife模块

    ioc-annotation 用于存放注解等,Java模块
    ioc-compiler 用于编写注解处理器,Java模块
    ioc-api 用于给用户提供使用的API,本例为Andriod模块
    ioc-sample 示例,本例为Andriod模块


    ButterKnife模块

    六:解析注解的流程(收集注解的流程)

    (1)roundEnv.getElementsAnnotatedWith(BindView.class)拿到所有@BindView注解的成员变量
    (2)循环每一个成员变量,进行检查。
    (3)获取该成员变量的Element对应得类TypeElement及全路径名.
    (4)创建类(proxyInfo)封装一个类的注解信息。
    (4)把该成员变量对应的注解存入proxyInfo(key:id,value:variableElement)

    private Map<String, ProxyInfo> mProxyMap = new HashMap<String, ProxyInfo>();
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
        mProxyMap.clear();
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
        //一、收集信息
        for (Element element : elements){
            //检查element类型
            if (!checkAnnotationUseValid(element)){
                return false;
            }
            //field type
            VariableElement variableElement = (VariableElement) element;
            //class type
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();//TypeElement
            String qualifiedName = typeElement.getQualifiedName().toString();
    
            ProxyInfo proxyInfo = mProxyMap.get(qualifiedName);
            if (proxyInfo == null){
                proxyInfo = new ProxyInfo(mElementUtils, typeElement);
                mProxyMap.put(qualifiedName, proxyInfo);
            }
            BindView annotation = variableElement.getAnnotation(BindView.class);
            int id = annotation.value();
            proxyInfo.mInjectElements.put(id, variableElement);
        }
        return true;
    }
    

    相关文章

      网友评论

          本文标题:ButterKnife源码解析

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