接上一章的 Looper, 本章开始分析消息机制中的 Message 与消息对象池.
什么是 Message
- 一个可以发送给 Handler 的描述和任意数据对象的消息对象, 这个对象包含两个额外的 int 字段和一个额外的对象字段. 这样就可以使用在很多情况下不用做分配工作.
- 尽管 Message 的构造函数是公开的, 但是获取 Message 的对象最好的方式是调用 Message.obtain() 或者 Handler.obtainMessage(), 因为这样是直接从一个可回收的消息对象池中获取 Message 对象.
成员变量 what
- 官方: 它是用户定义 Message 的标识符, 用以分辨消息的内容, Handler 拥有自己消息代码的命名空间, 因此你不用担心与其他的 Handler 冲突.
成员变量 arg1 与 arg2
- arg1 与 arg2 都是 Message 的可选变量, 可以用来存放两个整数值, 不用访问 obj 对象就能读取的变量. 如果只是几个整形的数值, 相对于使用 setData() 方法, 使用 arg1/arg2 是较低成本的替代方案.
成员变量 obj
- 官方: 将一个独立的对象发给接收者. 当使用 Messenger 去发送消息, 并且这个对象包含 Parcelable 类的时候, 它必须是非空的, 对于其他的数据传输, 建议使用 setData() 方法.
剩余成员变量
//回复跨进程的 Messenger
public Messenger replyTo;
//Messenger 发送的 UID
public int sendingUid = -1;
//正在使用的标志值,表示当前 Messgae 正处于使用状态,
//当 Message 处于消息队列中,处于消息池中或者 Handler 正在处理 Messgae 的时候,它就处于使用状态
/*package*/ static final int FLAG_IN_USE = 1 << 0;
//异步标志值 表示当前 Message 是异步的
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
//消息标志值 在调用 copyFrom()方法时,这个常量就会被设置,值其实和 FLAG_IN_USE 一样.
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
//消息标志, 上面 3 个常量 FLAG 用在这里.
/*package*/ int flags;
//用于存储发送消息的时间点, 以毫秒为单位
/*package*/ long when;
//用于存储比较复杂的数据
/*package*/ Bundle data;
//用于存储发送当前 Message 的 Hadnler 对象, 说明 Handler 其实和 Message 是相互持有引用的.
/*package*/ Handler target;
//用于存储将会执行的 Runnable 对象,
//除了 HandlerMessgae(Message msg) 方法,还可以使用 Runnable 执行操作.要注意的是这种方法并不会创建新的线程
/*package*/ Runnable callback;
//指向下一个 Messgae,
/*package*/ Message next;
//这个静态是为了给同步块提供一个锁
private static final Object sPoolSync = new Object();
//这个静态的 Message 是整个线程池链表的头部, 通过它才能逐个取出对象池的 Message
private static Message sPool;
//记录对象池中 Message 的数量(链表的长度)
private static int sPoolSize = 0;
//设置了对象池中 Message 的最大数量, 也就是链表的最大长度
private static final int MAX_POOL_SIZE = 50;
//该系统是否支持回收的标志位
private static boolean gCheckRecycle = true;
1. Message.obtain
在 Message.java 中, 通过 Message.obtain()
方式来获取 Message
对象的, 一共有 8 个方法.下面将会针对这些逐个分析一下.
1.1 public static Message obtain( )
代码位于 Message.java 126 行
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
有一个变量为 sPool
, 那么 sPool
到底有什么用呢? 这就涉及到了 Message
的设计原理了. 这里就引出了 Message
中的消息对象池了.
消息对象池
private static Message sPool;
乍一看, 好像没什么, 就是一个 Message
对象而已, sPool
对象默认是 null
.
这时候回头来看上面的无参 obtain
方法, 内部直接 new Message()
返回了. 但是官方又不推荐直接 new Message()
, 所以推断 sPool
在大部分情况下, 是不会为 null
的. 那么就找一下, 看在什么地方会对 sPool
进行赋值.
经过搜索后发现, 整个 Message
就有两次地方对 sPool
进行赋值.
- 上面的无参
obtain
方法中. -
void recycleUnchecked()
方法中.
首先第一点已经排除了. 那么就直接进入到第二点方法内查看.
Message.recycleUnchecked()
为了更好的理解 我把 recycleUnchecked() obtain()
放在一起., 并省略一些不重要的代码
void recycleUnchecked() {
...
if (sPoolSize < MAX_POOL_SIZE) {
// 第一步
next = sPool;
// 第二步
sPool = this;
// 第三步
sPoolSize++;
...
}
}
public static Message obtain() {
synchronized (sPoolSync) {
//第一步
if (sPool != null) {
// 第二步
Message m = sPool;
// 第三步
sPool = m.next;
// 第四步
m.next = null;
// 第五步
m.flags = 0;
// 第六步
sPoolSize--;
return m;
}
}
}
recycleUnchecked()
注意: 变量 next
与 sPool
都是 Message
.
- 第一步:
next = sPool
, 首先因为消息对象池一开始就为 null, 所以此时的next
应该也是为null
.- 第二步:
sPool = this
, 将当前Message
对象作为消息池中第一个可被复用的对象.- 第三步:
sPoolSize++
,sPoolSize
默认为 0, 这个时候就变成了 1, 将消息对象池内的数量+1, 这个数量是全局共享的.
假设现在又调用了一遍 recycleUnchecked
这个方法, 之前第一遍调用 recycleUnchecked
中那个第一个可被复用的对象为 Message1
, 依旧执行上面三步.
- 第一步:
next = sPool
, 因为消息对象池中有一个Message1
, 所以此时sPool
为Message1
, 同时赋值给next
.- 第二步:
sPool = this
, 将当前Message
对象作为消息池中第一个可被复用的对象. 假设当前这个为Message2
.- 第三步:
sPoolSize++
,sPoolSize
此时为 1, ++ 后, 变为 2.
那么现在,
sPool
消息对象池数量为 2,sPool
对应Message2
,sPool
内持有的另一个 Message 对象next
对应Message1
.
依次类推, 直到sPoolSize = MAX_POOL_SIZE (MAX_POOL_SIZE = 50)
.
接着看 obtain(), 假设消息对象池中已经有两个对象了. 就是 Message1, Message2
.
- 第一步: 判断
sPool
是否为null
, 为null
就直接创建一个Message
并返回.- 第二步:
Message m = sPool
, 将消息对象池的头部赋值给m
, 刚才sPool
对应的是Message2
.- 第三步:
sPool = m.next
, 将消息对象池的下一个可复用的Message
取出并赋值给消息对象池的头部, 那么此时,sPool
对应Message1
,m
对应Message2
.Message1.next
是为null
的.- 第四步:
m.next = null
, 因为之前已经把m.next
也就是Message1
赋值给sPool
了. 所以就把m
做为单独的一个Message
来对待.- 第五步
m.flags = 0
, 设置 m 的标记位, 标记正在被使用.- 第六步.
sPoolSize--
, 因为已经取出了一个,赋值给m
了, 这时候消息对象池的容量减一.
那么现在, m
就是单独的一个 Message
对象. sPool
消息对象池数量为 1, 对应的是 Message1
, 并且 sPool.next = null
, 因为从消息对象池中取出了 Message2
给了 m
.
那么剩下的是什么操作呢, Message2
取出来了, 就要开始分发了, 还记得昨天分析的 Looper.loop()
方法吗, 分发完 Message2
后最后一步就是调用了 Message2.recycleUnchecked()
方法, 又将 Message2
放入了消息对象池了, 这样就完成了一次 Message
的复用.
(一开始消息对象池就是 null
, 当我们第一次调用 Message.obtain()
的时候, 其实就是直接 new
了一个 Message
的. 然后在昨天学习的 Looper.loop()
方法的最后, 分发完消息后, 调用了 msg.recycleUnchecked()
将我们 new
的 Message
放入了消息对象池.)
- 可以将
sPool
看成一个指针, 通过next
来将对象组成一个链表. 因为每次只需要从池子中拿出一个对象, 所以不需要关心池子里具体有多少个对象, 而是拿出当前这个sPool
所指向的这个对象就可以了.sPool
从思路上理解就是通过左右移动来完成复用和回收.
image.png- 把
Message m = sPool
, 然后sPool = m.next
因此第一个m.next
就等于第二个message
, 从上图上看相当于指针向后移动了一位, 随后将第一个message.next()
的值设置为 null.如下图
image.png- 现在这个链表看上去就断了, 如果
in-use
这个message
使用完毕了, 怎么回到链表中? 这就是recycleUnchecked()
回收了.
这时候再看下recycleUnchecked()
中的代码,next = sPool
, 将当前sPool
所指向的message
对象赋值给in-use
的next
. 然后sPool=this
, 将sPool
指向第一个message
对象.从而达到回收 . 如下图
image.png
这样链表就恢复了,而且不管是复用还是回收都是保证线程同步的. 所以始终会形成一条链式结构.
1.2 public static Message obtain(Message orig)
public static Message obtain(Message orig) {
Message m = obtain();
m.what = orig.what;
m.arg1 = orig.arg1;
m.arg2 = orig.arg2;
m.obj = orig.obj;
m.replyTo = orig.replyTo;
m.sendingUid = orig.sendingUid;
if (orig.data != null) {
m.data = new Bundle(orig.data);
}
m.target = orig.target;
m.callback = orig.callback;
return m;
}
- 解析
和
obtain
差不多, 但是是将Message
的所有内容赋值一份到新的消息中.
代码中可以看到首先调用的是无参obtain
, 从消息对象池中获取一个Message
对象m
, 然后把origin
中所有的属性都赋值给m
.并返回m
.
1.2 public static Message obtain(Handler h)
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
- 解析
和无参
obtain
一样, 但是成员变量中target
的值可以用以指定的值(入参)来替换.
剩下几个 obtain
方法都基本类似, 都是先调用了obtain
无参函数, 在重置一些值. 这里就不再一一说明.
主要就是对无参 obtain
方法的理解, recycleUnchecked()
方法的理解以及消息对象池 sPool
的理解. 这三个方面, 还是要捋清楚的. 下一章一起学习分析 MessageQueue
.
网友评论