内容简介:Version 10.1.0地址ButterKnife有两种实现方式
Version 10.1.0
ButterKnife有两种实现方式
- 定义注解,在运行时利用反射实现。
- 定义注解,在编译时利用APT生成固定格式的源文件。
源码下载编译遇到的一个问题
直接git clone的Project的名称是butterknife,和里面的一个Module重名,导致gradle sync失败,需要clone时修改下名称。
使用
Application Module和Library Module在使用上有点差别。
- Application Module 1.1 build.gradle 添加依赖
android { ... // Butterknife requires Java 8. compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'com.jakewharton:butterknife:10.1.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' } 复制代码
1.2 使用
不能给private或static添加,否则报错
class ExampleActivity extends Activity { @BindView(R.id.user) EditText username; @BindView(R.id.pass) EditText password; @BindString(R.string.login_error) String loginErrorMessage; @OnClick(R.id.submit) void submit() { // TODO call server... } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // TODO Use fields... } } 复制代码
- Library Module 2.1 添加依赖
project.gradle
buildscript { repositories { mavenCentral() google() } dependencies { classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0' } } 复制代码
build.gradle
android { ... // Butterknife requires Java 8. compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'com.jakewharton:butterknife:10.1.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' } 复制代码
添加plugin
apply plugin: 'com.jakewharton.butterknife' 复制代码
2.2 使用 需要用R2替换掉R,因为注解的值只支持常量,而Library Module中的R变量不再是常量,ButterKnife生成的R2变量都是常量。
class ExampleActivity extends Activity { @BindView(R2.id.user) EditText username; @BindView(R2.id.pass) EditText password; ... } 复制代码
源码分析 先看下源码包结构
可见 支持反射实现和APT实现
- 首先分析APT的实现方式
(1) 从入口ButterKnife.bind()开始
/** * 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 { ...... } } 复制代码
主要是通过绑定的类,获取继承Unbinder的类的构造方法,利用反射创建对象。
@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; } // 如果是系统类,则返回null 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; } // 获取类名+"_ViewBinding"的类的构造函数 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 () { ...... } BINDINGS.put(cls, bindingCtor); return bindingCtor; } 复制代码
返回类名+"_ViewBinding"的类的构造函数。该类是如何产生的呢?
先别急,这个就是APT在编译时生成的。我们先看下这个类的代码。在Project中build下工程。
public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); if (view != null) { return view; } ...... } 复制代码
可见,View的获取,还是通过Android提供的findViewById,和类型转换。
view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.show(p0); } }); 复制代码
点击事件,封装了一层DebouncingOnClickListener用来防快速点击。 这种防快速点击的方式我们也可以直接应用到我们的工程中。
Context context = source.getContext(); Resources res = context.getResources(); target.appname = res.getString(R.string.app_name); 复制代码
获取资源的方式,也是通过Resource。
(2) 下面分析下,如何在编译时生成类
关于APT技术和如何实现就不细说了,网上都有,很简单。
所以先从ButterKnifeProcessor proces方法开始讲起。
@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; } 复制代码
具体生成代码的逻辑就不细说了,主要就是通过APT技术,遍历文件,查找自定义注解,封装成固定格式的javapoet的类,再生成文件。
- 第一行,遍历所有自定义Bind注解,存到Map中
- 第二行,生成javapoet中的JavaFile对象
- 第三行,利用JavaFile对象生成具体文件
(3) 说下ButterKnife如何在Module中生成R2文件,将资源定义成常量,编译在注解中使用,这个我们开发中也可以借鉴。 juejin.im/post/5ce3aa…
以上所述就是小编给大家介绍的《ButterKnife源码拆轮子学习》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。