ButterKnife源碼淺析

ButterKnife源碼分析


1. ButterKnife 簡介

1.1 代碼結(jié)構(gòu)

  • butterknife ;android library model 提供android使用的API
  • butterknife-annotations; java-model砸烦,使用時的注解
  • butterknife-compiler钻心;java-model杆麸,編譯時用到的注解的處理器
  • butterknife-gradle-plugin;自定義的gradle插件胧卤,輔助生成有關(guān)代碼
  • butterknife-integration-test蠢护;該項目的測試用例
  • butterknife-lint;該項目的lint檢查

1.2 流程簡述

[圖片上傳失敗...

2. ButterKnife解析

2.1

對控件進(jìn)行綁定:

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.btn1)
    Button btn;
    @BindView(R.id.tv1)
    TextView tv;

    @OnClick(R.id.btn1)
    public void btnClick() {
        Toast.makeText(this, "aa", Toast.LENGTH_SHORT).show();
    }

2.2 編譯唆迁,生成Java代碼

2.3.1

在Java代碼的編譯時期鸭丛,javac 會調(diào)用java注解處理器來生成輔助代碼竞穷。生成的代碼就在 build/generated/source/apt

public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
  protected T target;

  private View view2131427416;

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

    View view;
    view = Utils.findRequiredView(source, R.id.btn1, "field 'btn' and method 'btnClick'");
    //這里的 target 其實就是我們的 Activity
    //這個castView就是將得到的View轉(zhuǎn)化成具體的子View
    target.btn = Utils.castView(view, R.id.btn1, "field 'btn'", Button.class);
    view2131427416 = view;
    //為按鈕設(shè)置點擊事件
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.btnClick();
      }
    });
    target.tv = Utils.findRequiredViewAsType(source, R.id.tv1, "field 'tv'", TextView.class);
  }

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

    target.btn = null;
    target.tv = null;

    view2131427416.setOnClickListener(null);
    view2131427416 = null;

    this.target = null;
  }

方法說明:
findRequiredView:這個方法就是找到布局文件配置的View。世人唾棄的findViewById方法就是在這里自動調(diào)用的鳞溉。

   View view = source.findViewById(id);
    if (view != null) {
      return view;
    }

2.3.2

ButterKnife注解處理器
用來解析注解并生成對應(yīng)java代碼

ButterKnife注解處理器里包含下面幾個重要的方法:

  • init() :初始化瘾带,得到Elements、Types熟菲、Filer等工具類
  • getSupportedAnnotationTypes() :描述注解處理器需要處理的注解
  • process() :掃描分析注解看政,生成代碼。這個是ButterKnife的核心抄罕,下面著重分析允蚣。

process方法:

  • 獲得TypeElement -> BindingSet的映射關(guān)系
  • 運用JavaPoet框架來生成形式為xxxx_ViewBinding代碼
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //查找所有的注解信息,并形成BindingClass保存到 map中
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    
    // 生成MainActivity_ViewBinding代碼
     for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

      JavaFile javaFile = bindingClass.brewJava();
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    

findAndParseTargets方法
省略掉無關(guān)的代碼呆贿,findAndParseTargets分成三部分:

  • scanForRClasses:建立view與R的id的關(guān)系,將R.id轉(zhuǎn)換成MainActivity_ViewBinding中的view嚷兔。
  • 解析各種注解(這里以BindView為例)森渐。
  • 根據(jù)buildMap來生成bindingMap。
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    
     // 建立view與R的id的關(guān)系
    scanForRClasses(env);
    
     // 解析查找所有包含BindView注解的element
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    
    
// 將Map.Entry<TypeElement, BindingSet.Builder>轉(zhuǎn)化為Map<TypeElement, BindingSet>

    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();
      //判斷是否有父類
      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          builder.setParent(parentBinding);
          bindingMap.put(type, builder.build());
        } else {
          // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
          entries.addLast(entry);
        }
      }
    }

    return bindingMap;
  }

在注解處理器中冒晰,我們掃描 java 源文件同衣,源代碼中的每一部分都是Element的一個特定類型。換句話說:Element代表程序中的元素壶运,比如說 包耐齐,類,方法蒋情。每一個元素代表一個靜態(tài)的埠况,語言級別的結(jié)構(gòu),這些都是Element的子類
比如:

public class ClassA { // TypeElement
    private int var_0; // VariableElement
    public ClassA() {} // ExecuteableElement

    public void setA( // ExecuteableElement
            int newA // TypeElement
    ) {
    }
}

TypeMirror:
TypeMirror表示 Java 編程語言中的類型。這些類型包括基本類型棵癣、聲明類型(類和接口類型)询枚、數(shù)組類型、類型變量和 null 類型浙巫。還可以表示通配符類型參數(shù)金蜀、executable 的簽名和返回類型,以及對應(yīng)于包和關(guān)鍵字 void 的偽類型的畴。

parseBindView方法
parseBindView先檢測是否有錯誤渊抄,然后將name(變量名,例如tvTitle)丧裁、type(類名护桦,例如TextView)、required(是否有@nullable注解)封裝成FieldViewBinding放到builder這個Map里煎娇。

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
      
    // 得到包含注解所屬的TypeElement二庵,例如MainActivity
    //判斷修飾符,如果包含private or static //就會拋出異常缓呛。
    //判斷父節(jié)點如果是private 類催享,則拋出異常
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    //1. isInaccessibleViaGeneratedCode檢驗enclosingElement(MainActivity)是類、不是private哟绊,檢驗element不是private活著static
    // isBindingInWrongPackage檢驗enclosingElement的包名是不是系統(tǒng)相關(guān)的類
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element);

    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    
    // 判斷element是View的子類或者接口
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      if (elementType.getKind() == TypeKind.ERROR) {
        note(element, "@%s field with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), qualifiedName, simpleName);
        hasError = true;
      }
    }
     // 不合法的直接返回
    if (hasError) {
      return;
    }

    //2.獲取id 值
    int id = element.getAnnotation(BindView.class).value();
    
    //// 3.獲取 BindingClass因妙,有緩存機(jī)制, 沒有則創(chuàng)建
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      // 檢查是否綁定過此id
      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);
    }
    //4. 生成FieldViewBinding 實體 
    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);
    
    //5.FieldViewBinding加入到 bindingClass 成員變量的集合中
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

上面代碼可分為5個步驟

1. 檢查用戶使用的合法性

 private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
      String targetThing, Element element) {
    boolean hasError = false;
    // 得到父節(jié)點
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

 //判斷修飾符,如果包含private or static 就會拋出異常票髓。
    // Verify method modifiers.
    Set<Modifier> modifiers = element.getModifiers();
    if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
      error(element, "@%s %s must not be private or static. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

        //判斷父節(jié)點是否是類類型的攀涵,不是的話就會拋出異常
        //也就是說BindView 的使用必須在一個類里
    // Verify containing type.
    if (enclosingElement.getKind() != CLASS) {
      error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

     //判斷父節(jié)點如果是private 類,則拋出異常
    // Verify containing class visibility is not private.
    if (enclosingElement.getModifiers().contains(PRIVATE)) {
      error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    return hasError;
  }
  
     //不能再以android開頭的包中使用
      if (qualifiedName.startsWith("android.")) {
      error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }
    //不能再以java開頭的包中使用
    if (qualifiedName.startsWith("java.")) {
      error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }

2. 獲取自己傳入的id值

3. 獲取BindingClass洽沟,如果緩存中沒有就新建一個實例以故。

4. 獲取注解名字和控件類型,生成FieldViewBinding裆操。

5. 添加到bindingClass集合中

2.3.3 運用JavaPoet框架來生成代碼

在生成Map<TypeElement,BindingSet>后怒详,下一步就是遍歷Map,調(diào)用每一個類的brewJava()方法鳄乏,根據(jù)TypeElement(MainActivity)利用Filer工具類來生成文件TypeElement_ViewBinding(MainActivity_ViewBinding)。

  private TypeSpec createBindingClass() {

  1. 生成類名
  //bindingClassName.simpleName() 就是我們在findAndParseTargets方法形成的類的名字:xxx_ViewBinding名字
  xxx也就是使用butterknife的類名

    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
    //增加類修飾符public
        .addModifiers(PUBLIC);

 //使用butterknife的類是 final類
    TypeName targetType;
    if (isFinal) {
     //增加類修飾符FINAL
      result.addModifiers(FINAL);
      targetType = targetTypeName;
    } else {
    //不是final類棘利,增加泛型參數(shù)T
      targetType = TypeVariableName.get("T");
      result.addTypeVariable(TypeVariableName.get("T", targetTypeName));
    }

//如果有父類直接繼承
    if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(getParentBinding(), targetType));
    } else {
    //增加實現(xiàn)接口
      result.addSuperinterface(UNBINDER);
       //增加成員變量
      result.addField(targetType, "target", isFinal ? PRIVATE : PROTECTED);
    }

}


2. 生成構(gòu)造函數(shù)

    if (!bindNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor(targetType));
    }
    result.addMethod(createBindingConstructor(targetType));

  3. 生成unbind方法橱野。
    if (hasViewBindings() || !hasParentBinding()) {
      result.addMethod(createBindingUnbindMethod(result, targetType));
    }

    return result.build();
  }

3. ButterKnife.bind(this)

編譯生成代碼后,在bind方法中調(diào)用善玫。
ButterKnife.bind的一系列方法都會調(diào)用createBinding方法

   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;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source);
      }
      ...
      

這里通過獲取Class獲取XXX_ViewBinding的Constructor構(gòu)造方法水援,然后調(diào)用constructor.newInstance(target, source)創(chuàng)建生成代碼的對象。

findBindingConstructorForClass方法

private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
  //緩存中查找茅郎,找到直接返回
    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 {
    //構(gòu)造一個class蜗元,可以看類名就是我們生成的。
      Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
      //noinspection unchecked
        // 獲取我們的構(gòu)造函數(shù)
      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);
    }
     //加入到緩存中
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

這里的BINGDINGS是一個Constructor的Map緩存系冗。

4. ButterKnife學(xué)習(xí)曲線

[圖片上傳失敗...(image-4f410b-1534244273443)]

參考資料

http://blog.csdn.net/u012933743/article/details/54972050

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奕扣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子掌敬,更是在濱河造成了極大的恐慌惯豆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奔害,死亡現(xiàn)場離奇詭異楷兽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)华临,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門芯杀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人雅潭,你說我怎么就攤上這事揭厚。” “怎么了扶供?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵筛圆,是天一觀的道長。 經(jīng)常有香客問我诚欠,道長顽染,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任轰绵,我火速辦了婚禮,結(jié)果婚禮上尼荆,老公的妹妹穿的比我還像新娘左腔。我一直安慰自己,他們只是感情好捅儒,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布液样。 她就那樣靜靜地躺著振亮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鞭莽。 梳的紋絲不亂的頭發(fā)上坊秸,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音澎怒,去河邊找鬼褒搔。 笑死,一個胖子當(dāng)著我的面吹牛喷面,可吹牛的內(nèi)容都是我干的星瘾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惧辈,長吁一口氣:“原來是場噩夢啊……” “哼琳状!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盒齿,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤念逞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后边翁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肮柜,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年倒彰,在試婚紗的時候發(fā)現(xiàn)自己被綠了审洞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡待讳,死狀恐怖芒澜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情创淡,我是刑警寧澤痴晦,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站琳彩,受9級特大地震影響誊酌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜露乏,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一碧浊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘟仿,春花似錦箱锐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浩聋。三九已至,卻和暖如春臊恋,著一層夾襖步出監(jiān)牢的瞬間衣洁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工抖仅, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留坊夫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓岸售,卻偏偏與公主長得像践樱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子凸丸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

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