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檔案。