ButterKnife源码解析

参考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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容

  • 前言 Jake Wharton大神的Butterknife可谓是造福广大Android开发者, 再也不用重复写fi...
    海之韵Baby阅读 752评论 0 0
  • 最近项目不是很忙,因为项目用到了butterknife框架,所以进行了下系统的研究。研究下来呢发现这个框架真的是吊...
    我小时候真的可狠了阅读 300评论 0 2
  • 本文主要介绍Android之神JakeWharton的一个注解框架,听说现在面试官现在面试都会问知不知道JakeW...
    Zeit丶阅读 962评论 4 6
  • 使用Butterknife的主要目的是消除关于view实例化的样板代码,这是一个专为View类型的依赖注入框架。D...
    9bc96fd72f8e阅读 328评论 0 5
  • 你 和我一样 喜欢手帐吗? 以前给一个闺蜜做了一年的手帐 感觉要被自己感动 好吧!背景不...
    半只兔子吱阅读 362评论 0 2