ButterKnife原理以及源代碼分析


一、概述

  相信絕大多數Android開發都用過ButterKnife這個框架,因為其老牌且知名。其通過注解來綁定視圖,把開發從煩瑣的findViewById中解放出來。

  ButterKnife有兩種實現形式:

  1.使用注解編譯器來實現,其結果就是編譯略微耗時,但其運行基本無損。

  2.完全使用反射來實現,其結果就是編譯基本無損,但是運行時就比較吃性能了。案例源代碼

  一般情況下我們都會選擇使用注解編譯器來實現,因為其對於性能來說無損。

  基本原理:

  1.使用BufferKnife.bind(target)方法拿到目標類的Class對象(target.getClass())

  2.根據class獲取目標Class的包名+類名,根據拿到的包名+類名+_ViewBinding構造一個構造函數,然后傳入targe和target.getWindow().getDecorView(),實例化構造函數。此時bind方法的任務就完成了。

  3.利用注解編譯器AnnotationProcessor獲取用指定注解標注的類,並解析用注解標注的方法和屬性,例如:BindView、OnClick等。

  4.利用JavaPoet根據注解編譯器解析出來的信息生成輔助類(包名+類名+ _ViewBinding),而在第二步中實例化的那個對象就是利用JavaPoet生成的。

  

二、使用方法

  使用方法非常的簡單,我就注解貼一個小例子上去了。

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv_content)
    TextView tv_content;
    @BindView(R.id.btn_click)
    Button btn_click;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);


    }

    @OnClick(R.id.btn_click)
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_click:
                Toast.makeText(this, "執行了點擊事件", Toast.LENGTH_LONG).show();
                break;
        }
    }
}

  

三、源代碼分析

  根據上面的示例我們知道,ButterKnife的入口是bind方法,那我們就直接從bind方法開始講,看看其都了些什么事情。

 @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
  }

  在bind方法中會根據Activity獲取Activity所綁定的Window對象,然后利用Window獲取頂層視圖DecorView,並將Activity和DecorView向下傳遞

@NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
  //獲取Activity的Class對象 Class<?> targetClass = target.getClass(); //構造一個包名+類名+_ViewBinding的構造函數 Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);    ...省略了一些代碼 //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { //實例化新類的構造函數,在這個構造函數中會根據傳遞進去的View、Object或者Activity,解析屬性或者方法上的注解(BindView或者OnClick注解)進行相應的findViewById和setonClick操作,從而完成最終的綁定 return constructor.newInstance(target, source);   ....省略了一些代碼 } }

 以上源碼首先會獲取目標類的class對象,然后通過findBindingConstructorForClass(targetClass)方法獲取一個包名+類名_ViewBinding的構造函數,然后實例化這個構造函數,然后把targe和頂層視圖view傳遞進去。

接下來看下findBindingConstructorForClass(targetClass)方法

@Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //先從緩存中拿,如果緩存中有就直接使用,如果沒有就創建一個
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      return bindingCtor;
    }
    //獲取類名全稱
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {//為了兼容androidx
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      //獲取cls的classloader並加載cls.getName()+_ViewBinding從而創建一個新類的Constructor的Class。而加載的這個新的類是項目在編譯期間用注解編輯器生成的。
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      //從bindingClass類中獲取到Constructor對象
      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);
    }
    //把心類的構造函數放入緩存中一遍下次使用,key=subscriber.getClass(),value=包名+subscriber類名_ViewBinding
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

 在findBindingConstructorForClass(targetClass)內部會首先從緩存中查找是否已經有目標類的Class為key的Constructor對象,如果有就直接用,如果沒有就提出目標類的包名+類名,然后在后面拼接_ViewBinding,利用classLoader,加載這個新類的對象。然后根據這個類對象獲取其構造函數,然后把構造函數放入BINDINGS緩存,最終返回新建的Constructor對象。分析到這里ButterKnife.bind方法的流程已經全部走完了。其實核心及一句話,實例化新類的構造函數。

  這個“包名+類名+_ViewBinding”的類其實是在編譯代碼的時候利用AnnotationProcessor+JavaPoet生成的。具體代碼如下:

  生成的類在:build->generated->ap_generated_sources->debug->out->項目包名->MainActivity_ViewBinding

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view7f070042;

  @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;
    target.tv_content = Utils.findRequiredViewAsType(source, R.id.tv_content, "field 'tv_content'", TextView.class);
    view = Utils.findRequiredView(source, R.id.btn_click, "field 'btn_click' and method 'onClick'");
    target.btn_click = Utils.castView(view, R.id.btn_click, "field 'btn_click'", Button.class);
    view7f070042 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onClick(p0);
      }
    });
  }

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

    target.tv_content = null;
    target.btn_click = null;

    view7f070042.setOnClickListener(null);
    view7f070042 = null;
  }
}

  在MainActivity_ViewBinding內部會給用BindView標記的視圖屬性賦值,其最終還是通過findViewById實現的。

 public static View findRequiredView(View source, @IdRes int id, String who) {
  //非常熟悉的一行代碼,findViewById來查找View並給View賦值 View view = source.findViewById(id); if (view != null) { return view; } String name = getResourceEntryName(source, id); throw new IllegalStateException("Required view '" + name + "' with ID " + id + " for " + who + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'" + " (methods) annotation."); }

  看到findViewById方法是不是很熟悉,其實本質上依然是通過findViewById來查找view的,只是這部分代碼不需要程序員去做了而已。框架在編譯時自動幫我們生成好了。

  再來看下點擊事件:

 view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onClick(p0);
      }
    });

  上述代碼的target就是MainActivity,而onClick就是我們上面小例子中定義的onClick函數,這個地方就是標准的設置點擊事件的地方,通過View的點擊事件回調target.onClick函數,從而讓demo中的點擊事件能夠執行。

  以上都只分析了ButterKnife的常規流程,其實ButterKnife的核心是AnnotationProcessor(注解編譯器)和JavaPoet。AnnotationProcessor用來將注解標注的類一一找出來,然后解析注解標注的屬性、方法。然后JavaPoet利用注解編譯器找出注解標注的屬性和方法進行生成對應的輔助類。例如:MainActivity_ViewBinding.java就是AnnotationProcessor和JavaPoet合作搞出來的。有了這個輔助類之后我們就可以從大量的findViewById和setOnClickListener中解放出來,因為輔助類已經提前幫我們做好了。

  下面先講一下AnnotationProcessor

  先來看一下ButterKnife源代碼的目錄結構:

 

   ButterKnife的注解處理器由ButterKnifeProcessor來完成的,其繼承了AbstractProcessor抽象類,源碼如下:

@AutoService(Processor.class)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
@SuppressWarnings("NullAway") // TODO fix all these...
public final class ButterKnifeProcessor extends AbstractProcessor {

  要想使用AbstractProcessor則必須要是實現其抽象方法

  1.init(ProcessingEnvironment env)

  init方法完成sdk版本的判斷以及相關幫助類的初始化如:Elements:注解處理器運行掃描文件時獲取元素相關信息,如: 包(PackageElement)類(TypeElement)成員變量(VariableElement)方法(ExecutableElement)。、Types、Filer、Trees

  2.getSupportedAnnotationTypes()

  返回一個Set<String> 代表要處理的類的名稱集合

  3.getSupportedSourceVersion()

  返回當前系統支持的Java版本

  4.getSupportedOptions()

  返回注解處理器可以處理的注解操作

  5.process()

  完成目標類信息的收集以及生成相對應的輔助類

  根據以上ButterKnifeProcessor的五個方法我們分別看一下其源代碼,把從代碼生成到實例化這一條線給串聯起來

  init()方法

 @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
     ...省略了上面的代碼
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
       ...省略了下面的代碼
    }

  這個目錄結構就非常清晰了,它只做了兩件事情,初始化TypeUtils和Filer。

  getSupportedAnnotationTypes()

   @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

  把可用的注解都放入LinkedHashSet中返回。

  接下來直接看process這個方法比較關鍵,ps:這里我們只分析BindView注解,其他的都是類似的大家可以自行分析

  @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();
       //利用JavaPoet生成輔助類
            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;
    }

  我們看下findAndParseTargets都干了些什么事情

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 {
                parseBindView(element, builderMap, erasedTargetNames);
            } catch (Exception e) {
                logParsingError(element, BindView.class, e);
            }
        }

        
        Map<TypeElement, ClasspathBindingSet> classpathBindings =
                findAllSupertypeBindings(builderMap, erasedTargetNames);

        // Associate superclass binders with their subclass binders. This is a queue-based tree walk
        // which starts at the roots (superclasses) and walks to the leafs (subclasses).
        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, classpathBindings.keySet());
            if (parentType == null) {
                bindingMap.put(type, builder.build());
            } else {
                BindingInformationProvider parentBinding = bindingMap.get(parentType);
                if (parentBinding == null) {
                    parentBinding = classpathBindings.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;
    }

  首先將掃描到的注解相關的信息保存的builderMap中,然后將這些信息重新組合返回一個key=TypeElement,value=BindingSet的Map集合。其中TypeElement代表使用了ButterKnife的類,而BindingSet存儲的是生成類的基本信息以及注解元素的基本信息。其中parseView方法用來解析使用了BindView注解的元素。

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
                               Set<TypeElement> erasedTargetNames) {
        // 首先要注意,此時element是VariableElement類型的,即成員變量
        // enclosingElement是當前元素的父類元素,一般就是我們使用ButteKnife時定義的View類型成員變量所在的類,可以理解為之前例子中的MainActivity
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

        // 進行相關校驗
        // 1、isInaccessibleViaGeneratedCode(),先判斷當前元素的是否是private或static類型,
        // 再判斷其父元素是否是一個類以及是否是private類型。
        // 2、isBindingInWrongPackage(),是否在系統相關的類中使用了ButteKnife注解
        boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
                || isBindingInWrongPackage(BindView.class, element);

        // TypeMirror表示Java編程語言中的一種類型。 類型包括基元類型,聲明的類型(類和接口類型),數組類型,類型變量和空類型。 
        // 還表示了通配符類型參數,可執行文件的簽名和返回類型,以及與包和關鍵字void相對應的偽類型。
        TypeMirror elementType = element.asType();
        // 如果當前元素是類的成員變量
        if (elementType.getKind() == TypeKind.TYPEVAR) {
            TypeVariable typeVariable = (TypeVariable) elementType;
            elementType = typeVariable.getUpperBound();
        }
        Name qualifiedName = enclosingElement.getQualifiedName();
        Name simpleName = element.getSimpleName();
        // 判斷當前元素是否是 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;
        }

        // 獲得元素使用BindView注解時設置的屬性值,即 View 對應的xml中的id
        int id = element.getAnnotation(BindView.class).value();
        // 嘗試獲取父元素對應的BindingSet.Builder
        BindingSet.Builder builder = builderMap.get(enclosingElement);
        // QualifiedId記錄了當前元素的包信息以及id
        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 {
            // 創建一個新的BindingSet.Builder並返回,並且以enclosingElement 為key添加到builderMap中
            builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
        }

        String name = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        // 判斷當前元素是否使用了Nullable注解
        boolean required = isFieldRequired(element);
        // 創建一個FieldViewBinding,它包含了元素名、類型、是否是Nullable
        // 然后和元素id一同添加到BindingSet.Builder
        builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

        // 記錄當前元素的父類元素
        erasedTargetNames.add(enclosingElement);
    }

  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM