66.源码解析:ButterKnife(7.0.1)
2015-12-23 13:10
330 查看
1.使用
2.预备知识:[/b](1)注解[/b]
元注解是指注解的注解。包括 @Retention @Target @Document @Inherited四种。
1.1、@Retention: 定义注解的保留策略[/b]
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到1.2、@Target:定义注解的作用目标[/b]@Target(ElementType.TYPE) //接口、类、枚举、注解@Target(ElementType.FIELD) //字段、枚举的常量@Target(ElementType.METHOD) //方法@Target(ElementType.PARAMETER) //方法参数@Target(ElementType.CONSTRUCTOR) //构造函数@Target(ElementType.LOCAL_VARIABLE)//局部变量@Target(ElementType.ANNOTATION_TYPE)//注解@Target(ElementType.PACKAGE) //包 它的elementType 可以有多个,一个注解可以为类的,方法的,字段的等等1.3、@Document:说明该注解将被包含在javadoc中[/b]1.4、@Inherited:说明子类可以继承父类中的该注解[/b]
[/b][/b](2)反射[/b]
[/b]1、java的反射机制的定义:[/b]在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。[/b]2、获取Class对象的3种方式:[/b](1)通过Object类的getClass()方法。例如:Class c1 = new String("").getClass();(2)通过Class类的静态方法——forName()来实现:Class c2 = Class.forName("MyObject");(3)如果T是一个已定义的类型的话,在java中,它的.class文件名:T.class就代表了与其匹配的Class对象,例如:Class c3 = Manager.class;Class c4 = int.class;Class c5 = Double[].class;[/b]3、Class类中的6个重要的方法:[/b]1.getName()[/b][/b]一个Class对象描述了一个特定类的特定属性,而这个方法就是返回String形式的该类的简要描述。由于历史原因,对数组的Class对象调用该方法会产生奇怪的结果。[/b][/b]2.newInstance()[/b]该方法可以根据某个Class对象产生其对应类的实例。需要强调的是,它调用的是此类的默认构造方法。例如:MyObject x = new MyObject();MyObject y = x.getClass().newInstance();[/b][/b]3.getClassLoader()[/b]返回该Class对象对应的类的类加载器。[/b][/b]4.getComponentType()[/b]该方法针对数组对象的Class对象,可以得到该数组的组成元素所对应对象的Class对象。例如:int[] ints = new int[]{1,2,3};Class class1 = ints.getClass();Class class2 = class1.getComponentType();而这里得到的class2对象所对应的就应该是int这个基本类型的Class对象。[/b][/b]5.getSuperClass()[/b]返回某子类所对应的直接父类所对应的Class对象。[/b][/b]6.isArray()[/b]判定此Class对象所对应的是否是一个数组对象。[/b][b]此例子中用到了2个很牛逼的方法:[/b][/b][/b]4、关于Field的2个牛逼方法://获取类的属性x,无视权限
[/b][/b][/b][/b][/b]1.getClass().getDeclaredField("x");[b][/b][/b][/b][/b][b][b][b]//设置属性可编辑
[/b][/b][/b][/b][/b][/b][/b][/b][/b]2.setAccessible(true);5、关于Method的1个牛逼方法:invoke[/b][/b]
Method 方法=类.getMethod(方法名,参数类型)返回值=方法.invoke(对象,参数)等价于不用反射的:返回值=对象.方法(参数)测试Reflect:
[/b]
[/b]
[/b]测试Invoke:
[/b]3.源码解析:[/b]
(1)绑定视图id[/b]
(2)绑定点击事件[/b]
OnClick类第28行,调用了另一个自定义注解
OnClick类第32行,也调用了另一个自定义注解
(3)在activity.onCreate中初始化时[/b]
首先,bind方法的第317行,会调用findViewBinderForClass,获取ViewBinder对象然后,bind方法的第319行,会调用ViewBinder对象的bind方法
坑爹啊,居然是接口,那么,它的实现类在哪呢?暂且不表,下回细说。先深入findViewBinderForClass方法
第339、340行,会用反射创建一个ViewBinder对象,类名为:原类名+
这个玩意应该就是ViewBinder的实现类了,但是我找遍ButterKnife的源码,都找不到该实现类的源码。于是,源码解析卡在这里了。后来,看了AbstractProcessor的相关知识,才知道,注解可以分为:运行时注解、编译时注解,运行时注解就是就是运行时运用反射,动态获取对象、属性、方法等,一般的IOC框架就是这样,可能会牺牲一点效率。然而,大名鼎鼎的ButterKnife运用的是编译时注解。编译时注解就是在程序编译时根据注解进行一些额外的操作,ButterKnife在我们编译时,就根据注解,自动生成了一些辅助类。入口为AbstractProcessor的process方法。好,终于又有方向,继续深入。
(4)编译时注解[/b],ButterKnifeProcessor会自动根据注解生成辅助类[/b]在源码中找到了AbstractProcessor的子类:ButterKnifeProcessor
有点坑爹,源码中AbstractProcessor等类会标红,好奇怪,我明明程序可以好好的运行呀,怎么会找不到这些类呢?javax不是jdk自带的api吗?算了,先不管这了,继续看。
其中,process方法的129行,调用了brewJava方法,哈哈,就是这个方法,会自动创建java文件并写入代码
我将程序编译后,在
目录下找到了自动创建的java文件,如下:看这个类的名称,“$$ViewBinder”,是不是有印象。嘿嘿,ViewBinder的实现类,终于找到了。
第11行,调用findRequiredView方法
首先,调用findView,找到View类型的目标对象坑爹啊,抽象方法,那么实现方法在哪呢?再次短路!
好吧,暂时略过,先去看看转换View类型的方法,发现其实就是强转而已
继续寻找findView的实现类,全局搜索“findView(”,然后发现:
上面的抽象方法findView其实也在枚举类Finder中,好吧,原来枚举类中可以定义抽象方法,而且,枚举值,还可以实现它的抽象方法,表示又学到了一招。我们传入的为Activity,于是,见第100行,哈哈,熟悉的findViewById终于看到了。好!ButterKnife的@bind注解的流程已经走通了,下面再看@OnClick的流程:
返回去看那个自动生成的$$ViewBinder类,
见第17行,DebouncingOnClickListener即为点击事件的监听器,
第26行,在onClick方法中,调用了抽象方法doClick,实现方法在哪呢?在自动生成的辅助类中,见$$ViewBinder类的第18行。
实现方法中,调用了target.doMyClick(po),嘿嘿,回去看我们的使用类LoginActivity,见第44行,好!ButterKnife的@bind注解的流程也走通了!
那么,到这就大功告成了吗?不!现在我们只知道,ButterKnife在编译时,根据注解,自动生成了一个辅助类,这个辅助类,帮我们搞定了findViewById和OnClick!但是,这个辅助类的生成细节,我们还不是很清楚。
(5)分析ButterKnifeProcessor自动生成代码的细节
通过前面的分析,我们知道,自动生成主要涉及到两个方法:入口方法process写码方法brewJava
再次回顾下最终生成的辅助类:
先看与这个类联系最紧密的brewJava方法吧:
包名、导入类、类名的生成,都可以一目了然,问题是:bind和unbind方法里面的细节,这些都是和我们自己写的代码紧密联系的,它是怎么知道我们的字段名和方法名的?不得不说,反射和注解真是太牛逼了,也许有一天,真的就可以完全用机器写代码了。好了,先不感慨了,继续看代码:第104行,调用emitBindMethod方法第106行,调用emitUnbindMethod方法
第127行,遍历id,调用emitViewBindings方法
第198行,看到了辅助类的11行的东东,然后调用了emitHumanDescription方法
其实这个方法没什么用,看看就过去吧。
—————————————【未完待续,我是分割线】———————————————————————
再看process:
第120行,调用findAndParseTargets方法,查找并解析目标。不得不说,大牛的代码真的是不用太多的注释的,代码本身就是注释了,实乃吾辈学习楷模!
其实就是根据不同的注解,分别遍历,这里我们只分析@Bind和@OnClick,所以就只看第148行的parseBind和第156行的findandParseListener了,
先看parseBind,因为我们的使用类中@Bind的参数只是一个id,这里就只看parseBindOne
—————————————【未完待续,我是分割线】———————————————————————
参考:[/b]/article/1580270.html/content/3880371.html
来自为知笔记(Wiz)
2.预备知识:[/b](1)注解[/b]
元注解是指注解的注解。包括 @Retention @Target @Document @Inherited四种。
1.1、@Retention: 定义注解的保留策略[/b]
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到1.2、@Target:定义注解的作用目标[/b]@Target(ElementType.TYPE) //接口、类、枚举、注解@Target(ElementType.FIELD) //字段、枚举的常量@Target(ElementType.METHOD) //方法@Target(ElementType.PARAMETER) //方法参数@Target(ElementType.CONSTRUCTOR) //构造函数@Target(ElementType.LOCAL_VARIABLE)//局部变量@Target(ElementType.ANNOTATION_TYPE)//注解@Target(ElementType.PACKAGE) //包 它的elementType 可以有多个,一个注解可以为类的,方法的,字段的等等1.3、@Document:说明该注解将被包含在javadoc中[/b]1.4、@Inherited:说明子类可以继承父类中的该注解[/b]
[/b][/b](2)反射[/b]
[/b]1、java的反射机制的定义:[/b]在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。[/b]2、获取Class对象的3种方式:[/b](1)通过Object类的getClass()方法。例如:Class c1 = new String("").getClass();(2)通过Class类的静态方法——forName()来实现:Class c2 = Class.forName("MyObject");(3)如果T是一个已定义的类型的话,在java中,它的.class文件名:T.class就代表了与其匹配的Class对象,例如:Class c3 = Manager.class;Class c4 = int.class;Class c5 = Double[].class;[/b]3、Class类中的6个重要的方法:[/b]1.getName()[/b][/b]一个Class对象描述了一个特定类的特定属性,而这个方法就是返回String形式的该类的简要描述。由于历史原因,对数组的Class对象调用该方法会产生奇怪的结果。[/b][/b]2.newInstance()[/b]该方法可以根据某个Class对象产生其对应类的实例。需要强调的是,它调用的是此类的默认构造方法。例如:MyObject x = new MyObject();MyObject y = x.getClass().newInstance();[/b][/b]3.getClassLoader()[/b]返回该Class对象对应的类的类加载器。[/b][/b]4.getComponentType()[/b]该方法针对数组对象的Class对象,可以得到该数组的组成元素所对应对象的Class对象。例如:int[] ints = new int[]{1,2,3};Class class1 = ints.getClass();Class class2 = class1.getComponentType();而这里得到的class2对象所对应的就应该是int这个基本类型的Class对象。[/b][/b]5.getSuperClass()[/b]返回某子类所对应的直接父类所对应的Class对象。[/b][/b]6.isArray()[/b]判定此Class对象所对应的是否是一个数组对象。[/b][b]此例子中用到了2个很牛逼的方法:[/b][/b][/b]4、关于Field的2个牛逼方法://获取类的属性x,无视权限
[/b][/b][/b][/b][/b]1.getClass().getDeclaredField("x");[b][/b][/b][/b][/b][b][b][b]//设置属性可编辑
[/b][/b][/b][/b][/b][/b][/b][/b][/b]2.setAccessible(true);5、关于Method的1个牛逼方法:invoke[/b][/b]
Method 方法=类.getMethod(方法名,参数类型)返回值=方法.invoke(对象,参数)等价于不用反射的:返回值=对象.方法(参数)测试Reflect:
[/b]
/*java反射用法*/ public class TestReflect extends InstrumentationTestCase { public void testMain() { LogUtil.e("测试反射:[START]"); try { main(); } catch (Exception e) { LogUtil.e("出错啦:e=" + e.getMessage()); } LogUtil.e("测试反射:[END]"); } //★这里说的Field都是 类 身上的,不是实例上的 public static void main() throws Exception { Point pt1 = new Point(3, 5); //得到一个字段 Field fieldY = pt1.getClass().getField("y"); //y 是变量名 //fieldY的值是5么?? 大错特错 //fieldY和pt1根本没有什么关系,你看,是pt1.getClass(),是 字节码 啊 //不是pt1对象身上的变量,而是类上的,要用它取某个对象上对应的值 //要这样 LogUtil.e(fieldY.get(pt1).toString()); //这才是5 //现在要x了 /* Field fieldX = pt1.getClass().getField("x"); //x 是变量名 LogUtil.e(fieldX.get(pt1)); */ //运行 报错 私有的,找不到 //NoSuchFieldException //说明getField 只可以得到 公有的 //怎么得到私有的呢?? /* Field fieldX = pt1.getClass().getDeclaredField("x"); //这个管你公的私的,都拿来 //然后轮到这里错了 // java.lang.IllegalAccessException: //Class com.ncs.ReflectTest can not access a member of class com.ncs.Point with modifiers "private" LogUtil.e(fieldX.get(pt1)); */ //三步曲 一是不让你知道我有钱 二是把钱晃一下,不给用 三是暴力抢了 //暴力反射 Field fieldX = pt1.getClass().getDeclaredField("x"); //这个管你公的私的,都拿来 fieldX.setAccessible(true);//上面的代码已经看见钱了,开始抢了 LogUtil.e(fieldX.get(pt1).toString()); //out 3 OK!! } public static class Point { private int x; public int y; public String s1 = "ball"; public String s2 = "hubin"; public String s3 = "zhangxiaoxiang"; //做实验而已,字段不可能是 public 的 public Point(int x, int y) { super(); this.x = x; this.y = y; } } }运行结果如下:
[/b]
[/b]测试Invoke:
/*java反射中Method类invoke方法的用法*/ public class TestReflect extends InstrumentationTestCase { private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } public int add(int param1, int param2) { return param1 + param2; } public String echo(String mesg) { return "echo" + mesg; } public void testMain() { LogUtil.e("测试反射:[START]"); Class classType = TestReflect.class; try { Object invokertester = classType.newInstance(); //1 Method addMethod = classType.getMethod("add", new Class[]{ //2 int.class, int.class }); Object result = addMethod.invoke(invokertester, new Object[]{ //3 new Integer(100), new Integer(200) }); LogUtil.e(result.toString()); Method echo = classType.getMethod("echo", new Class[]{String.class}); Object obj = echo.invoke(invokertester, new Object[]{new String("jy is very good!!!")}); LogUtil.e(obj.toString()); TestReflect test = new TestReflect(); //1 test.setName("小明"); //2 Method[] methods = test.getClass().getDeclaredMethods(); //3 //循环查找获取id方法,并执行查看是否有返回值 for (int i = 0; i < methods.length; i++) { //如果此方法有get和Id关键字则执行 if (methods[i].getName().indexOf("get") != -1 && methods[i].getName().indexOf("Name") != -1) { try { // 获取此get方法返回值,判断是否有值,如果没有值说明即将执行的操作新增 if (methods[i].invoke(test, null) == null) { //4 LogUtil.e("此对象没有值!!!"); } else { Object strName = methods[i].invoke(test, null); LogUtil.e(strName.toString()); } } catch (Exception e) { System.out.print(""); } } } } catch (Exception ex) { LogUtil.e("出错啦:e=" + ex.getMessage()); } LogUtil.e("测试反射:[END]"); } }运行结果如下:
[/b]3.源码解析:[/b]
(1)绑定视图id[/b]
(2)绑定点击事件[/b]
OnClick类第28行,调用了另一个自定义注解
OnClick类第32行,也调用了另一个自定义注解
(3)在activity.onCreate中初始化时[/b]
首先,bind方法的第317行,会调用findViewBinderForClass,获取ViewBinder对象然后,bind方法的第319行,会调用ViewBinder对象的bind方法
坑爹啊,居然是接口,那么,它的实现类在哪呢?暂且不表,下回细说。先深入findViewBinderForClass方法
第339、340行,会用反射创建一个ViewBinder对象,类名为:原类名+
这个玩意应该就是ViewBinder的实现类了,但是我找遍ButterKnife的源码,都找不到该实现类的源码。于是,源码解析卡在这里了。后来,看了AbstractProcessor的相关知识,才知道,注解可以分为:运行时注解、编译时注解,运行时注解就是就是运行时运用反射,动态获取对象、属性、方法等,一般的IOC框架就是这样,可能会牺牲一点效率。然而,大名鼎鼎的ButterKnife运用的是编译时注解。编译时注解就是在程序编译时根据注解进行一些额外的操作,ButterKnife在我们编译时,就根据注解,自动生成了一些辅助类。入口为AbstractProcessor的process方法。好,终于又有方向,继续深入。
(4)编译时注解[/b],ButterKnifeProcessor会自动根据注解生成辅助类[/b]在源码中找到了AbstractProcessor的子类:ButterKnifeProcessor
有点坑爹,源码中AbstractProcessor等类会标红,好奇怪,我明明程序可以好好的运行呀,怎么会找不到这些类呢?javax不是jdk自带的api吗?算了,先不管这了,继续看。
其中,process方法的129行,调用了brewJava方法,哈哈,就是这个方法,会自动创建java文件并写入代码
我将程序编译后,在
目录下找到了自动创建的java文件,如下:看这个类的名称,“$$ViewBinder”,是不是有印象。嘿嘿,ViewBinder的实现类,终于找到了。
第11行,调用findRequiredView方法
首先,调用findView,找到View类型的目标对象坑爹啊,抽象方法,那么实现方法在哪呢?再次短路!
好吧,暂时略过,先去看看转换View类型的方法,发现其实就是强转而已
继续寻找findView的实现类,全局搜索“findView(”,然后发现:
上面的抽象方法findView其实也在枚举类Finder中,好吧,原来枚举类中可以定义抽象方法,而且,枚举值,还可以实现它的抽象方法,表示又学到了一招。我们传入的为Activity,于是,见第100行,哈哈,熟悉的findViewById终于看到了。好!ButterKnife的@bind注解的流程已经走通了,下面再看@OnClick的流程:
返回去看那个自动生成的$$ViewBinder类,
见第17行,DebouncingOnClickListener即为点击事件的监听器,
第26行,在onClick方法中,调用了抽象方法doClick,实现方法在哪呢?在自动生成的辅助类中,见$$ViewBinder类的第18行。
实现方法中,调用了target.doMyClick(po),嘿嘿,回去看我们的使用类LoginActivity,见第44行,好!ButterKnife的@bind注解的流程也走通了!
那么,到这就大功告成了吗?不!现在我们只知道,ButterKnife在编译时,根据注解,自动生成了一个辅助类,这个辅助类,帮我们搞定了findViewById和OnClick!但是,这个辅助类的生成细节,我们还不是很清楚。
(5)分析ButterKnifeProcessor自动生成代码的细节
通过前面的分析,我们知道,自动生成主要涉及到两个方法:入口方法process写码方法brewJava
再次回顾下最终生成的辅助类:
先看与这个类联系最紧密的brewJava方法吧:
包名、导入类、类名的生成,都可以一目了然,问题是:bind和unbind方法里面的细节,这些都是和我们自己写的代码紧密联系的,它是怎么知道我们的字段名和方法名的?不得不说,反射和注解真是太牛逼了,也许有一天,真的就可以完全用机器写代码了。好了,先不感慨了,继续看代码:第104行,调用emitBindMethod方法第106行,调用emitUnbindMethod方法
第127行,遍历id,调用emitViewBindings方法
第198行,看到了辅助类的11行的东东,然后调用了emitHumanDescription方法
其实这个方法没什么用,看看就过去吧。
—————————————【未完待续,我是分割线】———————————————————————
再看process:
第120行,调用findAndParseTargets方法,查找并解析目标。不得不说,大牛的代码真的是不用太多的注释的,代码本身就是注释了,实乃吾辈学习楷模!
其实就是根据不同的注解,分别遍历,这里我们只分析@Bind和@OnClick,所以就只看第148行的parseBind和第156行的findandParseListener了,
先看parseBind,因为我们的使用类中@Bind的参数只是一个id,这里就只看parseBindOne
—————————————【未完待续,我是分割线】———————————————————————
参考:[/b]/article/1580270.html/content/3880371.html
来自为知笔记(Wiz)
相关文章推荐
- css的opacity属性
- Javascript图片无缝滚动
- Node.js回调黑洞全解:Async、Promise 和 Generator
- js全屏显示代码的三种方法
- 289. Game of Life-LeetCode(生命游戏)
- js控制页面的全屏展示和退出全屏显示的方法
- 关于HTML5标签不兼容(IE6~8)
- caffe make的时候出错:src/caffe/util/math_functions.cu(140): error: calling a host function("std::signbit
- jquery实现点击其他区域时隐藏下拉div和遮罩层的方法
- JSONArray使用
- 兼容所有浏览器的JQuery zClip插件实现复制到剪贴板功能
- CBIR: Texture Features 基于内容的图像检索:纹理特征
- CBIR: Colour Features 基于内容的图像检索:颜色特征
- 使用余弦定理制作磁盘形状h5音乐播放器
- JQuery中$.ajax()方法参数详解
- Javascript 返回上一页
- jQuery对象与dom对象的区别
- 介绍一个成功的 Git 分支模型(master - hotfix - develop - feature - release)
- Javascript 排序数组或对象
- Trouble install node.js with homebrew