Android 中 Message的应用
Message
在Android中主要是在 消息循环机制 中使用,即配合 Handler
,Looper
和MessageQueue
来进行线程切换,线程间传递数据;
以及配合Handler
在IPC中传递数据; 这里不对这些进行展开,它不是我们关注的重点.
我们在代码中,被建议(网上或者前辈或看注释)用以下的方式来使用 Message
,并且被告知,这样会提高性能.
Message msg = Message.obtain();
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain();
嗯! 根据官方的文档,这样确实能够提高性能,将避免在大多数情况下创建新的对象!
下面我们来看它是如何实现高效的.
Message对象复用的实现
Message
能提高效率的原因是,将使用完的Message
清除附带的数据后,添加到复用池中,当我们需要使用它时,直接在复用池中取出对象使用,而不需要重新new
创建对象. 而复用池本质上就是一个单向链表,为了更好的理解,Message
复用池的实现,我们先来看下,简单的单向链表实现.
public class Linked<T> {
// 链表的头节点
private Node<T> head;
// 链表的长度
private int length;
// 数据插入头部
public void insertHead(T data) {
Node<T> n = new Node<>();
n.data = data;
// 将所添加的节点的下一个节点指向头结点
n.next = head;
// 将头结点指向党员元素
head = n;
// 链表长度加1
length++;
}
// 移除头部,并取出数据
public T removeHead() {
// 拿到原头结点
Node<T> n = head;
// 将头结点设置为下一个节点
head = n.next;
// 原头节点的下一个节点置空
n.next = null;
// 链表长度减1
length--;
// 返回原头结点数据
return n.data;
}
public int getLength() {
return length;
}
// 节点类
static class Node<T> {
T data; // 节点存放的元素
Node<T> next; // 下一个节点的引用
}
}
上面实现了单向链表的插入头部,和移除头部功能.
相信大家,对这种单向链表的实现,都十分熟悉,这里不再详细讲解,如果看不懂,请自行复习数据结构相关知识点.
下面我们带着上面的知识,来看Message
中源码的实现.
// 只显示我们需要关注的代码
public final class Message implements Parcelable {
...
// 下一个节点的引用
Message next;
// 这里起到类锁的功能,相当于 Message.class
private static final Object sPoolSync = new Object();
// 可以类比为 头结点
private static Message sPool;
// 链表的长度
private static int sPoolSize = 0;
// 链表的最大长度
private static final int MAX_POOL_SIZE = 50;
// 获取Message对象,类比单向链表的removeHead操作
public static Message obtain() {
// 同步锁, 这里相当于锁住 Message.class, 起到类锁的作用, 每一个Message实例都是同一把锁
synchronized (sPoolSync) {
// 链表中有可复用的Message,直接拿到头结点,然后将头结点指向下一个元素
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
// 链表中,没有可复用的message,直接new
return new Message();
}
// 类比单向链表的insertHead操作
void recycleUnchecked() {
// 清空数据操作
obj = null;
...
// 同步锁
synchronized (sPoolSync) {
// 如果链表长度小于50,将当前结点,加入链表头部
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
...
}
从上面的分析,我们来梳理一下整体的流程思想.
Message
使用一个静态变量,来起到头结点的作用.
静态变量属于类变量,内存中只会存在一份引用,正好能起到 头结点的作用
Message
将自身作为Node节点,存储下一个节点的引用,和自身数据obtain()
方法相当于链表中移除首元素,并返回该元素的操作
从复用池中获取Message
避免了 new创建的消耗.
recycleUnchecked()
方法相当于链表中将节点加入到头部的操作
添加到复用池之前,会先将Message
中的数据清空.
Message
添加了线程安全的操作Message
复用池最多保存50个使用完待复用的Message
50个可能是考虑到内存开销和时间开销的平衡, 不是将数据无限制的添加到复用池.
Message
将自身作为节点, 使用一个静态变量作为头结点,让Message
自身形成一个链表,而链表中存的是 已经清空数据的Message
对象, 以这种方式起到复用的效果!
疑问
从代码中可以看出, Message
中,将 private static final Object sPoolSync = new Object();
作为锁标志,来起到类锁的作用.
它能起到类锁的作用是因为,static
修饰的变量在类加载的初始化阶段就将被创建,final
使得引用不可改变,从而达到 内存独一份的效果,进而起到和类锁同样的作用.
这里有个疑问, 为何不直接使用类锁来加锁呢? 使用上诉方式,反而需要new
一个Object
对象,不是增加开销吗?
// why not ???
synchronized (Message.class) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
有答案的同学, 欢迎在评论区中指出!