66.源码解析:ButterKnife(7.0.1)

1.使用

2.预备知识:

(1)注解

元注解是指注解的注解。包括  @Retention @Target @Document @Inherited四种。

1.1、@Retention: 定义注解的保留策略

@Retention(RetentionPolicy.SOURCE)   //注解仅存在于源码中,在class字节码文件中不包含

@Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,

@Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

1.2、@Target:定义注解的作用目标

@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中

1.4、@Inherited:说明子类可以继承父类中的该注解


(2)反射

1、java的反射机制的定义:

在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

2、获取Class对象的3种方式:

(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;



3、Class类中的6个重要的方法:





1.getName()





一个Class对象描述了一个特定类的特定属性,而这个方法就是返回String形式的该类的简要描述。由于历史原因,对数组的Class对象

调用该方法会产生奇怪的结果。

2.newInstance()





该方法可以根据某个Class对象产生其对应类的实例。需要强调的是,它调用的是此类的默认构造方法。例如:

MyObject x = new MyObject();

MyObject y = x.getClass().newInstance();

3.getClassLoader()



返回该Class对象对应的类的类加载器。

4.getComponentType()



该方法针对数组对象的Class对象,可以得到该数组的组成元素所对应对象的Class对象。例如:

int[] ints = new int[]{1,2,3};

Class class1 = ints.getClass();

Class class2 = class1.getComponentType();

而这里得到的class2对象所对应的就应该是int这个基本类型的Class对象。

5.getSuperClass()



返回某子类所对应的直接父类所对应的Class对象。

6.isArray()



判定此Class对象所对应的是否是一个数组对象。







此例子中用到了2个很牛逼的方法:



4、关于Field的2个牛逼方法:









//获取类的属性x,无视权限







1.getClass().getDeclaredField("x");



















//设置属性可编辑





2.setAccessible(true);

5、关于Method的1个牛逼方法:invoke

Method 方法=类.getMethod(方法名,参数类型)

返回值=方法.invoke(对象,参数)

等价于不用反射的:

返回值=对象.方法(参数)


测试Reflect:

/*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;}

}}

运行结果如下:


测试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();   //1Method addMethod = classType.getMethod("add", new Class[]{  //2int.class, int.class});Object result = addMethod.invoke(invokertester, new Object[]{        //3new 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();   //1test.setName("小明");                      //2Method[] 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) {  //4LogUtil.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]");}}

运行结果如下:


3.源码解析:

(1)绑定视图id

(2)绑定点击事件

OnClick类第28行,调用了另一个自定义注解

OnClick类第32行,也调用了另一个自定义注解

(3)在activity.onCreate中初始化时

首先,bind方法的第317行,会调用findViewBinderForClass,获取ViewBinder对象

然后,bind方法的第319行,会调用ViewBinder对象的bind方法

坑爹啊,居然是接口,那么,它的实现类在哪呢?暂且不表,下回细说。

先深入findViewBinderForClass方法

第339、340行,会用反射创建一个ViewBinder对象,类名为:原类名+

这个玩意应该就是ViewBinder的实现类了,但是我找遍ButterKnife的源码,都找不到该实现类的源码。

于是,源码解析卡在这里了。后来,看了AbstractProcessor的相关知识,才知道,注解可以分为:运行时注解、编译时注解,

运行时注解就是就是运行时运用反射,动态获取对象、属性、方法等,一般的IOC框架就是这样,可能会牺牲一点效率。

然而,大名鼎鼎的ButterKnife运用的是编译时注解。

编译时注解就是在程序编译时根据注解进行一些额外的操作,ButterKnife在我们编译时,就根据注解,自动生成了一些辅助类。

入口为AbstractProcessor的process方法。好,终于又有方向,继续深入。

(4)编译时注解,ButterKnifeProcessor会自动根据注解生成辅助类

在源码中找到了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

—————————————【未完待续,我是分割线】———————————————————————

参考:

http://blog.csdn.net/lmj623565791/article/details/43452969

http://www.cnblogs.com/avenwu/p/4173899.html

来自为知笔记(Wiz)

时间: 2024-11-06 15:01:52

66.源码解析:ButterKnife(7.0.1)的相关文章

Android EventBus3.0使用及源码解析

叨了个叨 最近因为换工作的一些琐事搞的我一个头两个大,也没怎么去学新东西,实在是有些愧疚.新项目用到了EventBus3.0,原来只是听说EventBus的鼎鼎大名,一直没仔细研究过.趁着周末有些时间,研究下代码,也算没有虚度光阴. EventBus GitHub : https://github.com/greenrobot/EventBus EventBus3.0简介 EventBus是greenrobot出品的一个用于Android中事件发布/订阅的库.以前传递对象可能通过接口.广播.文件

EventBus3.0源码解析

本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充分解耦,从而使代码更简洁. 本文主要从以下几个模块来介绍 1.EventBus使用 2.EventBus注册源码解析 3.EventBus事件分发解析 4.EventBus取消注册解析 一.EventBus使用 1.首先是注册 EventBus.getDefault().register(this)

Android事件总线(二)EventBus3.0源码解析

相关文章 Android事件总线(一)EventBus3.0用法全解析 前言 上一篇我们讲到了EventBus3.0的用法,这一篇我们来讲一下EventBus3.0的源码以及它的利与弊. 1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus getDefault() { if (defaultInstance == null) { synchroniz

netty源码解析(4.0)-29 Future模式的实现

Future模式是一个重要的异步并发模式,在JDK有实现.但JDK实现的Future模式功能比较简单,使用起来比较复杂.Netty在JDK Future基础上,加强了Future的能力,具体体现在: 更加简单的结果返回方式.在JDK中,需要用户自己实现Future对象的执行及返回结果.而在Netty中可以使用Promise简单地调用方法返回结果. 更加灵活的结果处理方式.JDK中只提供了主动得到结果的get方法,要么阻塞,要么轮询.Netty除了支持主动get方法外,还可以使用Listener被

String源码解析(一)

本篇文章内的方法介绍,在方法的上面的注释讲解的很清楚,这里只阐述一些要点. Java中的String类的定义如下: 1 public final class String 2 implements java.io.Serializable, Comparable<String>, CharSequence { ...} 可以看到,String是final的,而且继承了Serializable.Comparable和CharSequence接口. 正是因为这个特性,字符串对象可以被共享,例如下面

Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数据结构第3部分 LinkedList源码解析(基于JDK1.6.0_45)第4部分 LinkedList遍历方式第5部分 LinkedL

Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要  和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码:最后再通过实例来学会使用LinkedList.内容包括:第1部分 LinkedList介绍第2部分 LinkedList数

Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayLis

消息中间件 RocketMQ源码解析:事务消息

关注微信公众号:[芋艿的后端小屋]有福利: RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表 RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址 您对于源码的疑问每条留言都将得到认真回复.甚至不知道如何读源码也可以请教噢. 新的源码解析文章实时收到通知.每周更新一篇左右. 1. 概述 2. 事务消息发送 2.1 Producer 发送事务消息 2.2 Broker 处理结束事务请求 2.3 Broker 生成