Butterknife源碼分析

0、相關文章:

文章1:butterknife源碼分析:代碼分析(參考此文,此文的butterknife版本跟10.2.1相近)

Butterknife源碼分析

https://www.wanandroid.com/article/query?k=butterknife

Android主流三方庫源碼分析(七、深入理解ButterKnife源碼)

1、Butterknife原理

講到butterknife的原理。這裏不得不提一下一般這種注入框架都是運行時註解,即聲明註解的生命週期爲RUNTIME,然後在運行的時候通過反射完成注入,這種方式雖然簡單,但是這種方式多多少少會有性能的損耗。那麼有沒有一種方法能解決這種性能的損耗呢?   沒錯,答案肯定是有的,那就是Butterknife用的APT(Annotation Processing Tool)編譯時解析技術

APT大概就是你聲明的註解的生命週期爲CLASS,然後繼承AbstractProcessor類。繼承這個類後,在編譯的時候,編譯器會掃描所有帶有你要處理的註解的類,然後再調用AbstractProcessor的process方法,對註解進行處理,那麼我們就可以在處理的時候,動態生成綁定事件或者控件的java代碼,然後在運行的時候,直接調用bind方法完成綁定。

其實這種方式的好處是我們不用再一遍一遍地寫findViewById和onClick了,這個框架在編譯的時候幫我們自動生成了這些代碼,然後在運行的時候調用就行了。

2、源碼解析

版本:

    //butterknife
    implementation 'com.jakewharton:butterknife:10.2.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'

拿到源碼的第一步是從我們調用的地方來突破,那我們就來看看程序裏面是怎樣調用它的呢?

    @BindView(R.id.btn100)
    Button btn100;    

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

    @OnClick(R.id.btn100)
    public void onViewClicked() {
        
    }

我們直接就從 ButterKnife.bind(this)入手吧,點進來看看:

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

根據activity得到DecorView,再傳遞到bind。

  @NonNull @UiThread
  public static Unbinder bind(@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);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

上面的代碼裏,獲取到Constructor後,再運用反射生成實例,在實例裏的findView操作就會被調用到。接下來看看如何獲取到Constructor的。

  @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?>     
     cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    // 過濾掉系統相關的類
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      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);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

從上面可以看到Constructor獲取的過程,根據className得到className_ViewBinding,就可以得到Constructor。並且會將得到的Constructor緩存起來,避免反射的性能問題。

這樣一來,ButterKnife.bind(this)傳遞進去的MainActivity會通過反射生成MainActivity_ViewBinding實例。在這個實例的構造函數內,進行findViewById、setOnclickListener等操作
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章