Android ButterKnife源碼分析

一、先看看ButterKnife的簡單使用

1. 導入ButterKnife
  implementation 'com.jakewharton:butterknife:8.8.1'
  annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
2. MainActivity(綁定當前view)
    @BindView(R.id.btn)
    Button mButton;
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
        mButton.setText("test");
    }
    
    @OnClick(R.id.btn)
    public void btnOnclick() {
        Intent intent = new Intent(MainActivity.this, Main3Activity.class
        );
        startActivity(intent);
    }

二藕施、源碼分析(ButterKnife.java)

1. 看看bind做了什么事件吞获,其實就是返回一個Unbinder對象谦秧。這個對象是一個(clsName + "_ViewBinding")的類污茵,實現(xiàn)Unbinder。(這里使用的反射)
 @NonNull @UiThread
    public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
     View sourceView = source.getWindow().getDecorView();
     return createBinding(target, sourceView);
    }
    
    
    private static Unbinder createBinding(@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;
     }
     //反射創(chuàng)建Unbinder
     return constructor.newInstance(target, source);
     
    }
    
    
    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
     // 緩存的LinkedHashMap 查找當前的activity是否緩存
     Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    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 {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      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);
    }
     // ViewBinder的子類的類名(MainActivity$$ViewBinder)阵面,然后通過反射的形式創(chuàng)建(MainActivity$$ViewBinder)的實例,并存入緩存
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
2. 看看Unbinder是什么
 public interface Unbinder {
  @UiThread void unbind();

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

3. 看看生成的MainActivity_ViewBinding.java是什么樣子的(在build/generated/source/apt文件夾)
  public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view2131165218;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    // 主要就是 View view = source.findViewById(id);
    view = Utils.findRequiredView(source, R.id.btn, "field 'mButton' and method 'btnOnclick'");
    target.mButton = Utils.castView(view, R.id.btn, "field 'mButton'", Button.class);
    view2131165218 = view;
    // 設置點擊事件
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.btnOnclick();
      }
    });
  }

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

    target.mButton = null;

    view2131165218.setOnClickListener(null);
    view2131165218 = null;
  }
}
4. 這個類是怎么生成的,使用了APT(編譯時解析技術),對于注解在編譯時候解析是通過我們自定義一個繼承AbstractProcessor的類來完成的样刷。所以我們找找ButterKnife的源碼仑扑,在butterknife-compile (ButterKnifeProcessor.java)
  @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      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 false;
  }
5. 可以看出這個類主要就是做了兩件事,首先是通過findAndParseTargets方法找出該類里所有注解置鼻,然后通過binding.brewJava方法將這些注解進行處理镇饮。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        // 創(chuàng)建一個BindingSet抽象類,然后將我們注解信息處理后保存在里面
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
 
   // .........

    return bindingMap;
  }
6. 通過binding.brewJava方法將這些注解進行處理箕母。(BindingSet.java)通過JavaFile創(chuàng)建出新的類储藐。而這個新生成的className+_ViewBinding“”類,我們可以在ButterKnife.bind時候獲取到它嘶是,然后就通過這個類里的方法钙勃,實現(xiàn)我們控件和監(jiān)聽方法的綁定了。
    JavaFile brewJava(int sdk, boolean debuggable) {
    TypeSpec bindingConfiguration = createType(sdk, debuggable);
     //采用了JavaPoet聂喇,一個非常強大的代碼生成工具辖源。根據(jù)我們的注解內(nèi)容,通過TypeSpec類和MethodSpec類構(gòu)造出對應的方法希太,然后根據(jù)之前創(chuàng)建BindingSet抽象類時候創(chuàng)建的新類名克饶。
    return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

三、總結(jié)

  • 使用編譯時注解跛十,在編譯時期自動生成Unbinder(APT)彤路,這個對象自動生成了我們需要的動畫、監(jiān)聽事件等等芥映。

  • 在我們的Activity中使用Bind進行綁定洲尊,我們就執(zhí)行了自動生成的代碼。而且返回一個Unbinder奈偏。

  • ButterKnife不能將控件和方法設置為private或者static坞嘀,是因為在className_ViewBinder類會直接調(diào)用該控件和方法進行賦值。

  • 編譯時注解+APT

  • 編譯時注解:@Retention(CLASS)惊来。

  • APT(Annotation Processing Tool)編譯時解析技術丽涩。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市裁蚁,隨后出現(xiàn)的幾起案子矢渊,更是在濱河造成了極大的恐慌,老刑警劉巖枉证,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矮男,死亡現(xiàn)場離奇詭異,居然都是意外死亡室谚,警方通過查閱死者的電腦和手機毡鉴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門崔泵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猪瞬,你說我怎么就攤上這事憎瘸。” “怎么了陈瘦?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵幌甘,是天一觀的道長。 經(jīng)常有香客問我甘晤,道長含潘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任线婚,我火速辦了婚禮遏弱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘塞弊。我一直安慰自己漱逸,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布游沿。 她就那樣靜靜地躺著饰抒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诀黍。 梳的紋絲不亂的頭發(fā)上袋坑,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音眯勾,去河邊找鬼枣宫。 笑死,一個胖子當著我的面吹牛吃环,可吹牛的內(nèi)容都是我干的也颤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼郁轻,長吁一口氣:“原來是場噩夢啊……” “哼翅娶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起好唯,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤竭沫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骑篙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體输吏,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年替蛉,在試婚紗的時候發(fā)現(xiàn)自己被綠了贯溅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡躲查,死狀恐怖它浅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镣煮,我是刑警寧澤姐霍,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站典唇,受9級特大地震影響镊折,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜介衔,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一恨胚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炎咖,春花似錦赃泡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绸栅,卻和暖如春级野,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粹胯。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工蓖柔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矛双。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓渊抽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親议忽。 傳聞我的和親對象是個殘疾皇子懒闷,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

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

  • 原理: APT(Annotation Processing Tool)編譯時解析技術(現(xiàn)在已經(jīng)改成了谷歌的更強大的...
    gogoingmonkey閱讀 236評論 0 2
  • 序言 繼上次介紹了EventBus之后,今天我們來介紹另外一個好用的三方庫ButterKnife栈幸,一個依賴注入框架...
    左大人閱讀 761評論 0 4
  • 最近項目不是很忙愤估,因為項目用到了butterknife框架,所以進行了下系統(tǒng)的研究速址。研究下來呢發(fā)現(xiàn)這個框架真的是吊...
    我小時候真的可狠了閱讀 319評論 0 2
  • butterknife注解框架相信很多同學都在用玩焰,但是你真的了解它的實現(xiàn)原理嗎?那么今天讓我們來看看它到底是怎么實...
    打不死的小強qz閱讀 645評論 0 4
  • 既然都快撞到南墻了不撞撞那怎么行芍锚。 靜下來昔园,給自己時間思考蔓榄,像孤獨和沐浴時一樣,純粹干凈的思考默刚∩#看吧,世界這么美好...
    木二心_閱讀 178評論 0 1