ButterKnife源碼解析

參考1
參考2
參考3
參考4

一:基本原理

編譯時注解+APT
編譯時注解:Rentention為CLASS的直接施禾。保留時間是編譯期。
APT(Annotation Processing Tool)編譯時解析技術(shù))屎媳。
聲明的注解的生命周期為CLASS,然后創(chuàng)建注解處理器抹蚀,即繼承AbstractProcessor類剿牺。繼承這個類后企垦,在編譯的時候环壤,編譯器會掃描所有帶有你要處理的注解的類,然后再調(diào)用AbstractProcessor的process方法钞诡,對注解進行處理郑现,處理完后,動態(tài)生成綁定事件或者控件的java代碼荧降,然后在運行的時候接箫,直接調(diào)用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)調(diào)用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已經(jīng)生成過。
(2) 否則 得到ViewBinder的子類的類名(MainActivity$$ViewBinder),然后通過反射的形式創(chuàng)建(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;
    }
}

首先調(diào)用了Finder的findRequiredView方法,其實這個方法最后經(jīng)過處理就是調(diào)用了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)在實現(xiàn)AbstractProcessor后掖看,process()方法是必須實現(xiàn)的
(2)一般會實現(xiàn)getSupportedAnnotationTypes()和getSupportedSourceVersion()匣距;這兩個方法一個返回支持的注解類型,一個返回支持的源碼版本哎壳,參考上面的代碼毅待,寫法基本是固定的。
(3)除此以外归榕,我們還會選擇復寫init()方法恩静,該方法傳入一個參數(shù)processingEnv,可以幫助我們?nèi)コ跏蓟恍┹o助類:
Filer mFileUtils: 跟文件相關的輔助類,生成JavaSourceCode.
Elements mElementUtils:跟元素相關的輔助類蹲坷,幫助我們?nèi)カ@取一些元素相關的信息驶乾。
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);
  //解析完成以后,需要生成的代碼結(jié)構(gòu)已經(jīng)都有了乞旦,它們都存在于每一個 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)掃描所有具有注解的類贼穆,然后根據(jù)這些類的信息生成BindingClass,最后生成以TypeElement為鍵,BindingClass為值的鍵值對兰粉。
(2)循環(huán)遍歷這個鍵值對故痊,根據(jù)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)循環(huán)每一個成員變量甜孤,進行檢查。
(3)獲取該成員變量的Element對應得類TypeElement及全路徑名.
(4)創(chuàng)建類(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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市二跋,隨后出現(xiàn)的幾起案子战惊,更是在濱河造成了極大的恐慌,老刑警劉巖扎即,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吞获,死亡現(xiàn)場離奇詭異,居然都是意外死亡谚鄙,警方通過查閱死者的電腦和手機各拷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闷营,“玉大人烤黍,你說我怎么就攤上這事∩得耍” “怎么了速蕊?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長娘赴。 經(jīng)常有香客問我规哲,道長,這世上最難降的妖魔是什么诽表? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任唉锌,我火速辦了婚禮,結(jié)果婚禮上竿奏,老公的妹妹穿的比我還像新娘袄简。我一直安慰自己,他們只是感情好泛啸,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布绿语。 她就那樣靜靜地躺著,像睡著了一般平痰。 火紅的嫁衣襯著肌膚如雪汞舱。 梳的紋絲不亂的頭發(fā)上伍纫,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天宗雇,我揣著相機與錄音,去河邊找鬼莹规。 笑死赔蒲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舞虱,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼欢际,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了矾兜?” 一聲冷哼從身側(cè)響起损趋,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎椅寺,沒想到半個月后浑槽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡返帕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年桐玻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荆萤。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡镊靴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出链韭,到底是詐尸還是另有隱情偏竟,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布敞峭,位于F島的核電站苫耸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏儡陨。R本人自食惡果不足惜褪子,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骗村。 院中可真熱鬧嫌褪,春花似錦、人聲如沸胚股。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琅拌。三九已至缨伊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間进宝,已是汗流浹背刻坊。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留党晋,地道東北人谭胚。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓徐块,卻偏偏與公主長得像,于是被迫代替她去往敵國和親灾而。 傳聞我的和親對象是個殘疾皇子胡控,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 前言 Jake Wharton大神的Butterknife可謂是造福廣大Android開發(fā)者, 再也不用重復寫fi...
    海之韻Baby閱讀 793評論 0 0
  • 最近項目不是很忙,因為項目用到了butterknife框架旁趟,所以進行了下系統(tǒng)的研究昼激。研究下來呢發(fā)現(xiàn)這個框架真的是吊...
    我小時候真的可狠了閱讀 315評論 0 2
  • 本文主要介紹Android之神JakeWharton的一個注解框架,聽說現(xiàn)在面試官現(xiàn)在面試都會問知不知道JakeW...
    Zeit丶閱讀 978評論 4 6
  • 使用Butterknife的主要目的是消除關于view實例化的樣板代碼锡搜,這是一個專為View類型的依賴注入框架癣猾。D...
    9bc96fd72f8e閱讀 346評論 0 5
  • 你 和我一樣 喜歡手帳嗎? 以前給一個閨蜜做了一年的手帳 感覺要被自己感動 好吧余爆!背景不...
    半只兔子吱閱讀 377評論 0 2