一、使用方法
1、添加依賴。
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
2、使用。
public class MainActivity extends AppCompatActivity { // 1、控件id綁定 @BindView(R.id.myBtn) Button myBtn; Unbinder unbinder = null; // 2、點擊事件綁定 @OnClick(R.id.myBtn) public void click() { Toast.makeText(this,"btn click",Toast.LENGTH_SHORT).show(); myBtn.setText("hello world"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //3、activity注冊 unbinder = ButterKnife.bind(this); } @Override protected void onDestroy() { //4、activity 取消注冊 unbinder.unbind(); super.onDestroy(); } }
3、編譯運行。
二、原理解析
很明顯的我們可以看出,ButterKnife.bind(this) 是 activity和ButterKnife建立關系的地方,我們從這里入手分析。
----->>點擊進入 bind
public static Unbinder bind(@NonNull Activity target) { //獲取decorView 就是頁面的跟布局View 本質 是FrameLayout View sourceView = target.getWindow().getDecorView(); // 獲取unbinder return createBinding(target, sourceView); }
---->> 點擊進入 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); } 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 先findBindingConstructorForClass
如果找不到就 返回 Unbinder.EMPTY,這里的Unbinder.EMPTY就是new Unbinder() 然后直接結束函數 ,不做處理,
如果得到constructor就 調用constructor.newInstance得到一個unbinder返回,我們稍微看一下newInstance 返回的是一個泛型,至於是在何時傳入的泛型,我們先保留下來。
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (serializationClass == null) { return newInstance0(initargs); } else { return (T) newInstanceFromSerialization(serializationCtor, serializationClass); } }
我們繼續探查他是如何得到construtor的
---->>點擊進入 findBindingConstructorForClass
@Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { // 1、先從BINDINGS 里邊獲取construtor 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 { // 2、如果沒有就,利用反射生成construtor 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); } // 3、將生成的construtor 放入BINDINGS做備份 BINDINGS.put(cls, bindingCtor); return bindingCtor; }
我們重點看,生成construtor這一段:
String clsName = cls.getName();
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
1、這里向Contrutor傳入Unbinder 泛型,這就能夠解釋newInstance的返回值是Unbinder。
2、這里泛型使用的類名(clsName + "_ViewBinding")中,竟然包含了我們傳進來的類名,這里完整的類名就是MainActivity_ViewBinding ,能夠反射獲取實例,說明這個類是確實存在的,二我們並沒有編寫相關的類,而在app運行過程更不可能產生類,那就只能是,app運行之前 由ButterKnife 生成的。
我們不妨看看這個類里有什么:
public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view2131165258; @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; view = Utils.findRequiredView(source, R.id.myBtn, "field 'myBtn' and method 'click'"); target.myBtn = Utils.castView(view, R.id.myBtn, "field 'myBtn'", Button.class); view2131165258 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.click(); } }); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.myBtn = null; view2131165258.setOnClickListener(null); view2131165258 = null; } }
從代碼中我們看到了 我們view的id以及activity中的變量名,可以聯想到,是我們添加Bind注解時傳進來的。
---->>findRequiredView我們可以猜測出是 尋找控件使用的。我們可以看一看,
public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); if (view != null) { return view; } String name = getResourceEntryName(source, id); throw new IllegalStateException("Required view '" ....... }
我們終於看到了 findViewById。
---->> castView 傳入 view 傳入 class ,很明顯是轉型使用的。
還有一個問題就是ButterKnife 如何能夠在運行前根據我們的代碼 ,生成相應的 _ViewBinding 文件的,請繼續看。
三、注解處理器
在添加依賴時我們還添加了一個,annotationProcessor,就是完成呢些文件生成的。
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
這其中涉及到了annotationProcessor技術,和 APT(Annotation Processing Tool)技術,他是一種注解處理器,
在項目編譯期可以對源代碼進行掃描,找出存活時間為RetentionPolicy.CLASS
的指定注解,然后對注解進行解析處理。
至於后邊java類的生成,涉及到了JavaPoet技術
這里我們先看用注解處理器收集類信息的過程,之前我們已經在app的 build.gradle引入了 ButterKnife 的注解處理器: butterknife-compiler,其中有一個ButterKnifeProcessor 類完成了注解處理器的核心邏輯。
@AutoService(Processor.class) public final class ButterKnifeProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); String sdk = env.getOptions().get(OPTION_SDK_INT); ...... debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE)); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); try { trees = Trees.instance(processingEnv); } catch (IllegalArgumentException ignored) { } } @Override public Set<String> getSupportedOptions() { return ImmutableSet.of(OPTION_SDK_INT, OPTION_DEBUGGABLE); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); for (Class<? extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } @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(); 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; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } }
注意,ButterKnifeProcessor
類上使用了@AutoService(Processor.class)
注解,來實現注解處理器的注冊,注冊到 javac 后,在項目編譯時就能執行注解處理器了。
ButterKnifeProcessor
繼承了AbstractProcessor
抽象類,並重寫以上五個方法,如果我們自定義解處理器也是類似的,看下這幾個方法:
1、init()
首先 init() 方法完成sdk版本的判斷以及相關幫助類的初始化,幫助類主要有以下幾個:
- Elements elementUtils,注解處理器運行掃描源文件時,以獲取元素(Element)相關的信息。Element 有以下幾個子類:
包(PackageElement
)、類(TypeElement
)、成員變量(VariableElement
)、方法(ExecutableElement
) - Types typeUtils,
- Filer filer,用來生成 java 類文件。
- Trees trees,
2、getSupportedAnnotationTypes()
該方法返回一個Set<String>
,代表ButterKnifeProcessor
要處理的注解類的名稱集合,即 ButterKnife 支持的注解:butterknife-annotations
3、getSupportedSourceVersion()
返回當前系統支持的 java 版本。
4、getSupportedOptions()
返回注解處理器可處理的注解操作。
5、process()
最后,process() 方法是我們要重點分析的,在這里完成了目標類信息的收集並生成對應 java 類。