1. 程式人生 > >butterknife 10.1.0 原始碼分析

butterknife 10.1.0 原始碼分析

開發十年,就只剩下這套架構體系了! >>>   

專案結構

butterknife-runtime 
butterknife 
butterknife-annotations
butterknife-compiler 
butterknife-gradle-plugin
//以下是輔助
butterknife-integration-test
butterknife-lint
butterknife-reflect

專案依賴圖:

 

如何使用:

1.先在專案根路徑 build.gradle 裡新增

classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'

2.在app module build.gradle 裡新增

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.jakewharton:butterknife:10.1.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
    implementation 'com.google.android:support-v4:r7'
}

butterknife-gradle-plugin 這裡是butterknife外掛 對應專案=>butterknife-gradle-plugin

com.jakewharton:butterknife:10.1.0 這裡是使用的人呼叫的butterknife包 =>butterknife & butterknife-annotations & butterknife-runtime

com.jakewharton:butterknife-compiler 這個是註解解析器 => butterknife-compiler

 

其實這個開源專案還是很好分析的:

1.先是從使用者通過註釋新增程式進行使用,也就是使用註解。

2. 專案在進行構建的時候 通過 annotationProcessor 將對應的註解進行轉換(JavaPoet) 成對應的java ->最終轉成.class 

3.程式在執行的時候通過呼叫 bk的相關函式,完成任務

source code ->.java ->.class -> .dex

(source code ->.java 程式碼編譯期解析註解,並且生成新的 Java 檔案,減少手動的程式碼輸入) 

核心用到的庫:

javapoet:是用來生成.java檔案

auto-service:是google提供的,註解 Processor,對其生成配置資訊

APT:Annotation Processing Tool  用於編譯期處理註解的api元件,提供2部分:

    1、用於模型化Java 程式語言結構的模型化api,包括com.sun.mirror包下的mirror api,javax.lang.model包下的element api 及其他輔助工具類。

    2、javax.annotation.processing包下用於編寫註解處理器的註解處理api。

https://docs.oracle.com/javase/7/docs/technotes/guides/apt/index.html

[java8 使用Pluggable Annotation Processing API 移除了apt]

   

關於反射的瞭解

      

執行時間是納秒

這裡是通過類的反射做的 ,但通過快取機制 減少效能損失

按專案 進行原始碼拆解:

butterknife 這庫是給應用引用的。

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

ButterKnife.bind(this) 經過一系列的呼叫,最終調到以下函式

/**
 **bindView使用@code source在指定的@code target中註釋的欄位和方法
 *
 * @param target Target class for view binding.
 * @param source View root on which IDs will be looked up.
 */
@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 {
     //例項化:執行它的建構函式 也就是 下文的MainActivity_ViewBinding()
    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);
  }
}

 

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { 
  //BINDINGS 是繫結的快取,因為下面.getClassLoader().loadClass 類的反射,建立快取,會盡量減少效能損失
  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 {
    //載入clsName "_ViewBinding".java 這個是 【butterknife-compiler】註釋編譯器 在專案編譯時生成的。
    //在後面的會說明
    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;
}

生成以下 ***_ViewBinding 

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view7f05001c;

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

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
   ……
  }

  @Override
  @CallSuper
  public void unbind() {
   ……
  }
}

butterknife-compiler 這庫在專案構建的時候呼叫的。主要就是將 註解 解析=>生成.java檔案。

說白了主要是通過調 ButterKnifeProcessor 裡的process 進行回撥完成對註解的解析。

init(ProcessingEnvironment processingEnvironment) 

裡面提供了Filer等工具類。註解處理器可以用Filer類建立新檔案(原始檔、類檔案、輔助資原始檔)。由此方法建立的原始檔和類檔案將由管理它們的工具(javac)處理。 

getSupportedSourceVersion() 
支援JDK的版本

public Set getSupportedAnnotationTypes() 
註解處理器是註冊給哪些註解使用的。 

public boolean process(annotationsannotations, RoundEnvironment roundEnv)

核心方法,在這裡你可以掃描和處理註解,並生成java檔案。