ButterKnife 原理解析
一、使用方法
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 类。
ButterKnife 原理解析的更多相关文章
- [原][Docker]特性与原理解析
Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...
- 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现
本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...
- Web APi之过滤器执行过程原理解析【二】(十一)
前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...
- Web APi之过滤器创建过程原理解析【一】(十)
前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...
- GeoHash原理解析
GeoHash 核心原理解析 引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...
- alibaba-dexposed 原理解析
alibaba-dexposed 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49821413 原理参考地址: htt ...
- 支付宝Andfix 原理解析
支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...
- JavaScript 模板引擎实现原理解析
1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...
- Request 接收参数乱码原理解析三:实例分析
通过前面两篇<Request 接收参数乱码原理解析一:服务器端解码原理>和<Request 接收参数乱码原理解析二:浏览器端编码原理>,了解了服务器和浏览器编码解码的原理,接下 ...
随机推荐
- css 背景 平移 动画
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8&quo ...
- Maven 缺省内置变量
1.${project.build.directory} 构建目录,缺省为target 2.${project.build.outputDirectory} 构建过程输出目录,缺省为target/cl ...
- 使用php在服务器端生成图文验证码
图文验证码的实现原理: 1):准备些许图片将其存储在数据库,每一张图片对应一个标识字段. 2):在服务器端使用数组的形式将图片与标识字段组合起来. 3):随机给客户端返回图片,并接受用户输入的字段. ...
- Devops成功的八大炫酷工具
原文链接:http://www.infoworld.com/article/3031009/devops/8-more-cool-tools-for-devops-success.html 为自动化和 ...
- 两个页面相同js方法兼容
1. a.js页面 //Js获取Url参数 function request(paras) { var url = location.href; var paraString = url.substr ...
- IBM Security AppScan Glass Box:一种全新的漏洞扫描思想
IBM Security AppScan Glass Box:一种全新的漏洞扫描思想 Glass Box 是 IBM Security AppScan Standard Edition(以下简称 Ap ...
- Qemu事件处理机制简介
Qmeu 采用了基于事件驱动的架构,所有的事件都在一个事件循环(event loop)中被处理,系统中默认的事件循环是在main-loop.c 中的主循环(main loop).我们也可以使用 –ob ...
- JQuery小结(转)
一.页面加载 JQ的页面加载比JS要快,当整个dom树结构生成完毕后就会加载 JQ页面加载不存在覆盖问题,加载的时候是顺序执行 JQ的页面加载最简写的方式为: $(function(){ alert( ...
- Eureka 源码编译安装部署---Eureka运行eureka-server服务
---恢复内容开始--- 折腾了几天,终于运行好了,两个字:佩服 首先感谢这个大佬的博客支持:https://www.cnblogs.com/lifuping/p/5663127.html 1.首先在 ...
- 【JMeter4.0学习(五)】JMeter对服务器监控测试脚本开发
目录: 下载相关JMeter插件 服务器监控测试脚本开发 附:参考相关文档 本文主要来说一下如何通过JMeter插件来监控服务器CPU.内存.磁盘.网络等相关资源. 一.首先,需要下载相关JMeter ...