文章目录
一、编译时技术简介
二、ButterKnife 原理分析
二、ButterKnife 生成 Activity_ViewBinding 代码分析
一、编译时技术简介
APT ( Annotation Processing Tool ) 注解处理工具 ;
编译时技术 , 广泛应用在当前主流框架中 , 如 JetPack 中的 DataBinding , Room , Navigatoion , 第三方 ButterKnife , ARouter 等框架 ;
编译时技术 最重要的作用就是在编译时可以 生成模板代码 ;
由于生成代码操作是在编译时进行的 , 不会对运行时的性能产生影响 ;
程序的周期 :
源码期 : 开发时 , 刚编写完 " .java " 代码 , 还未编译之前 , 就处于源码期 ;
编译期 : 程序由 java 源码编译成 class 字节码文件 ;
运行期 : 将字节码文件加载到 Java 虚拟机中运行 ;
编译时技术 APT 作用于 编译期 , 在这个过程中使用该技术 , 生成代码 ;
编译时技术 2 22 大核心要素 : 在编译时 , 执行生成代码的逻辑 , 涉及到两个重要概念 ;
① 编译时注解 ;
② 注解处理器 ;
举例说明 : 使用 ButterKnife 时会依赖两个库 ,
dependencies { implementation 'com.jakewharton:butterknife:10.2.3' annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' }
其中 com.jakewharton:butterknife:10.2.3 是 编译时注解 , com.jakewharton:butterknife-compiler:10.2.3 是 注解处理器 ;
二、ButterKnife 原理分析
使用 ButterKnife :
① 添加依赖 :
dependencies { implementation 'com.jakewharton:butterknife:10.2.3' annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' }
② Activity 中使用 ButterKnife :
package kim.hsl.apt; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import butterknife.BindView; import butterknife.ButterKnife; public class MainActivity extends AppCompatActivity { @BindView(R.id.hello) TextView hello; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); hello.setText("ButterKnife"); } }
BindView 注解分析 : 在 TextView hello 成员变量处添加了 @BindView(R.id.hello) 注解 ;
@Target(FIELD) 元注解 : 表示其作用与类的成员字段 ;
@Retention(RUNTIME) 元注解 : 表示该注解保留到运行时阶段 ;
int value() 注解属性 : 只有一个注解属性 , 并且属性名是 value , 则使用注解时 “value =” 可省略 ;
@Retention(RUNTIME) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); }
TextView hello 需要使用 findViewById 进行赋值 , 在上述代码中没有写 findViewById 相关的代码 ; 肯定是在某个地方执行了 findViewById 的方法 ;
ButterKnife.bind(this) 代码就是执行了 findViewById 方法 ;
ButterKnife 用到了编译时技术会 , 在项目编译时 , 会生成 MainActivity_ViewBinding 类 , 在该类中 , 会查找添加了 @BindView 直接的成员变量 , 再获取 注解属性 value 的值 , 然后调用 findViewById 方法获取组件并为成员变量赋值 ;
// Generated code from Butter Knife. Do not modify! package kim.hsl.apt; import android.view.View; import android.widget.TextView; import androidx.annotation.CallSuper; import androidx.annotation.UiThread; import butterknife.Unbinder; import butterknife.internal.Utils; import java.lang.IllegalStateException; import java.lang.Override; public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public MainActivity_ViewBinding(MainActivity target, View source) { this.target = target; target.hello = Utils.findRequiredViewAsType(source, R.id.hello, "field 'hello'", TextView.class); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.hello = null; } }
二、ButterKnife 生成 Activity_ViewBinding 代码分析
调用 ButterKnife 静态方法 Unbinder bind(@NonNull Activity target) , 传入 Activity 对象 , 在方法中调用了 ButterKnife 的 bind 方法 ;
在 bind 方法中 , 先获取了 Activity 的类对象 ,
Class<?> targetClass = target.getClass();
然后将类对象传入了 findBindingConstructorForClass 方法 ,
在 findBindingConstructorForClass 方法中 , 获取了某个构造方法 ,
获取 Activity 类对象名称 , 即 " kim.hsl.apt.MainActivity " ,
String clsName = cls.getName();
得到名称后 , 判断该类对象是否是系统的 API , 如果是则退出 ; 如果不是 , 则继续向下执行 ,
if (clsName.startsWith("android.") || clsName.startsWith("java.") || clsName.startsWith("androidx.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; }
拼装要生成的类名称 , “kim.hsl.apt.MainActivity_ViewBinding” , 并自动生成该类 ;
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
ButterKnife 涉及到的源码 :
public final class ButterKnife { /** * BindView annotated fields and methods in the specified {@link Activity}. The current content * view is used as the view root. * * @param target Target activity for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); } /** * BindView annotated fields and methods in the specified {@code target} using the {@code source} * {@link View} as the view root. * * @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 { 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) { 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; } }