一. 简介
接上一篇文章:Android Handler机制(三) Looper源码分析 ,我们来继续分析一下Message源码
这一系列文章都是为了深入理解Handler机制. Message 作为消息传递的载体,源码主要分为以下
几个部分:
1. 操作数据相关,类似 getter()和 setter()这种方法还有之前提到过的 what 和 obj 这类属性。
2. 创建与回收对象实例相关,除了用关键字 new 外,其他得到对象实例的方法。
3. 其他工具类性质的扩展方法。
整个类代码大概600多行, 接下来就开始一起分析下
二. Message数据属性与方法
我们先看看 Message 源码有哪些可供我们使用的属性
public int what :开发者可自定义的消息标识代码,用于区分不同的消息。
public int arg1: 如果要传递的消息只有少量的 integer 型数据,可以使用 这个属性。
public int arg2 :同上面 arg1。
public Object obj :开发者可自定义类型的传输数据。
上面四个属性作为常用的消息传递的数据载体可直接赋值,例如 msg.arg1 = 100;。基本可以满足我们日常开发中简单消息传递。
如果上面几个数据属性不能满足我们的需求,可以使用扩展数据:Bundle 来传递
源码中见Bundle的方法:
public Bundle getData() {
if (data == null) {
data = new Bundle();
}
return data;
}
public void setData(Bundle data) {
this.data = data;
}
这段代码也没什么逻辑好分析的,值得一提就是 Bundle data 不是 public 的,
所以我们不能直接操作这个属性,需要通过上面二个方法操作数据, 举个例子:
Bundle bundle = new Bundle();
bundle.putString("String","value");
bundle.putFloat("float",0.1f);
Message msg = Message.obtain();
msg.setData(bundle);
关于Bundle的API使用
请查看参考此链接:Bundle API
三. 创建Message 对象的基本方法
首先来看下Message类的构造方法
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}
从注释来看, 官方推荐我们使用Message.obtain()来获取Message对象
那这个 obtain()方法干了什么呢?其实就是内部维持了一个链表形式的 Meesage 对象 缓存池,这样会节省重复实例化对象产生的开销成本。
数据结构中的链表一个单元有两个值,当前单元的值 (head)和下一个单元的地址指针(next),如果下一个单元不存在那么 next 就是 null
所以,想要实现 Message 对象链表式缓存池就需要额外的两个 Message 类型的 引用 head 和 next,都说了叫缓存池,所以把 head 叫 pool 更合适一点。
有了链表的基础结构我们再想实例化对象的时候就可以先去链表缓存池中看看 有没有,有的话直接从缓存池中拿出来用,没有再 new 一个.
我们主要来分析一下 Message.obtain()方法
// 用于标识当前对象是否存在于缓存池,0 代表不在缓存池中
int flags;
/*
这个常量是供上面的 flags 使用的,它表示 in use(正在使用)状态
如果 Message 对象被存入了 MessageQueue 消息队列排队等待 Looper
处理或者被回收到缓存池中等待重复利用时,那么它就是 in use(正在使 用)状态
只有在 new Message()和 Message.obtain()时候才可以清除掉 flags 上 的 in use 状态
*/
static final int FLAG_IN_USE = 1 << 0;
/*
静态常量对象,通过 synchronized (sPoolSync)让它作为线程并发操作时的锁确保同一时刻只有一个线程可以访问当前对象的引用
*/
private static final Object sPoolSync = new Object();
//当前链表缓存池的入口,装载着缓存池中第一个可用的对象
private static Message sPool;
// 链表缓存池中指向下一个对象引用的 next 指针
Message next;
// 当前链表缓存池中对象的数量
private static int sPoolSize = 0;
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
从缓存池中拿出来一个 Message 对象给你, 可以让我们在许多情况下避免分配新对象。
*/
public static Message obtain() {
// 上锁,这期间只有一个线程可以执行这段代码
synchronized (sPoolSync) {
// pool 不等于空就说明缓存池中还有可用的对象,直接取出来
if (sPool != null) {
// 声明一个 Message 引用指向缓存池中的 pool 对象
Message m = sPool;
// 让缓存池中 pool 引用指向它的 next 引用的对象
sPool = m.next;
// 因为该对象已经从缓存池中被取出,所以将 next 指针置空
m.next = null;
// 将从缓存池中取出的对象的 flags 的 in use 标识清除掉
m.flags = 0; // clear in-use flag
// 缓存池中 Message 对象数量减去一个
sPoolSize--;
return m;
}
}
// 如果缓存池中没有可用的对象就 new一个
return new Message();
}
理论上我们希望 sPool 引用指向了链表缓存池中的第一个对象,让它作为整个缓存池的出入口。所以我们把它设置成 static 的,这样它就与实例化出来的对象 无关,也就是说无论我们在哪个 Message 对象中进行操作,sPool 还是 sPool。
再来梳理一下:
静态方法 obtain()的代码逻辑流程:
先判断缓存池是不是空的:if(sPool != null),如果是空的就直接:return new Message();,
不是空的就声明一个引用让它指向缓存池第一个对象:Message m = sPool;,
而缓存池的链表头部 sPool 引用就指向了链表中下一个对象:sPool = m.next;,因为这个时候缓存池中第一个对象已经取出交给了引用 Message m, 所以需要清除掉这个对象身上的特殊标识,
包括缓存池中的 next 引用和用来标 记对象状态的 flags值:m.next = null; m.flags = 0;,
最后将缓存池中的对象 数量减一:sPoolSize--;
用图表示更简洁一点:
阶段一: 初始状态, sPool 指向链表头部
阶段二: 声明一个Message 引用 m 也指向链表头部
阶段三: 让sPool 指向链表下一个对象
阶段四: 让m引用指向对象的next=null, flag 清零.
到这里,就把消息缓存池中的头部消息取出来了
分析到这里我们知道了为什么官方推荐我们使用 Message.obtain()得到对象 了,因为它是在缓存池中取出来重复利用的,但是通过上面代码也看可以看到,只有缓存池里有东西时也就是 sPool != null 的时候才可以取,Message 是怎么 把对象回收到缓存池中的呢?
四. 回收 Message 对象到缓存池的方法
阅读源码后发现有一个 public void recycle()方法用于回收 Message 对象,但 是它也牵扯出了一堆其他方法与属性:
// 缓存池最大存储值
private static final int MAX_POOL_SIZE = 50;
// 区分当前 Android 版本是否大于或者等于 LOLLIPOP 版本的全局静态变量,默认初始值为 true
private static boolean gCheckRecycle = true;
/*** 用于区分当前 Android 版本是否大于或者等于 LOLLIPOP 版本 * 内部隐藏方法,在 APP启动时就会执行该方法,开发者是不可见的 * @hide */
public static void updateCheckRecycle(int targetSdkVersion) {
if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
gCheckRecycle = false;
}
}
//* 判断当前对象的 flags 是否为 in-use 状态
/*package*/ boolean isInUse() {
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
}
/*
调用这个方法后,当前对象就会被回收入缓存池中。
你不能回收一个在 MessageQueue 排队等待处理或者正在交付给 Handler 处理的 Message 对象
说白了就是 in-use 状态的不可回收
*/
public void recycle() {
// 判断当前对象是否为 in-use 状态
if (isInUse()) {
// 如果当前版本大于或者等于 LOLLIPOP 则抛出异常
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
// 如果当前版本小于 LOLLIPOP 什么也不干直接结束方法
return;
}
// 回收 Message 对象
recycleUnchecked();
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
@UnsupportedAppUsage
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
//回收缓存池中消息,标记flag为FLAG_IN_USE
// Clear out all other details.
//其他属性全部清除
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
// 上锁
synchronized (sPoolSync) {
// 如果当前缓存池对象中的数量小于缓存池最大存储值(50)就存入缓存池中
if (sPoolSize < MAX_POOL_SIZE) {
// 存入缓存池
next = sPool;
sPool = this;
// 缓存池数量加 1
sPoolSize++;
}
}
}
上面代码的逻辑很清晰,执行 recycle()方法后先判断当前对象是否为 in-use 状 态:if (isInUse()),如果是 in-use 状态的话当前 Android 版本是 LOLLIPOP(5.0) 版本之前直接结束程序,LOLLIPOP及之后版本抛出异常。
如果当前对象不是in-use 状态,那么就执行 recycleUnchecked()方法先将它切换到 in-use 状态:flags = FLAG_IN_USE;
再把所有的数据属性全部清除,最后把对象存入缓存池链表中。
回收 Message对象 流程图:
五. 我们需要手动回收消息嘛?
现在我们知道了通过执行 recycle()方法回收 Message 对象,但是如果要为每个 Message 对象都进行手动回收岂不是很麻烦?
庆幸的是开发人员也想到了这一点,从源码中可以看到其实最终真正执行回收操 作的调用 recycleUnchecked()方法,且注释中告诉我们 MessageQueue 和 Looper 内部也会调用该方法执行回收。
这里先说一个结论,MessageQueue 和 Looper 内部分发处理消息时,当它们得知 当前这个 Message 对象已经使用完毕后就会直接调用 recycleUnchecked()方法 将它回收掉.
MessageQueue中的相关代码:
//MessageQueue.java中 removeMessagexxx 都会调用recycleUnchecked()方法
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
//消息回收
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
Looper 中的相关代码:
public static void loop() {
.....
for (;;) {
//从MessageQueue中取出消息
Message msg = queue.next(); // might block
try {
//分发消息
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
.....
//消息分发完之后,回收消息
msg.recycleUnchecked();
}
}
所以,如果我们用实例化 Message 对象是放入 Handler 中去传消息的,那么我们 就不需要手动回收,他们内部自己就回收了。
六. 包含 Handler 参数的 obtain()方法
给 Message 内部装了一个 Handler 起了什么作用呢?首先,我们可以通过上面讲 解我们可以得出以下已知的结论:
1. 表面上看我们使用 Handler 发送消息后,消息直接传回到了 Handler 内部的 handleMessage(Message msg)方法中。
2. 实际上是先把消息传入了 MessageQueue 中,Looper 再从 MessageQueue 依次取出 消息分发给 Handler。
3. Looper 是线程独立的, Looper 和 MessageQueue 是一对一的。 但是,你有没有想过 Looper 和 Handler 是不是一对一的?答案:不是, MessageQueue 只负责队列消息,Looper 只负责取出消息分发。他们的功能很明 确而且通用。 所以,无论当前线程有多少个 Handler,同样都只有一个 Lopper 和一个 MessageQueue。
解释图:
提个问题:
Question: 既然每个线程只有一个 Looper 和 MessageQueue 的话那么 Looper 分发消息的时 候要如何判断当前这个 Message 是哪个 Handler 的呢?
Answer: 所以开发人员就给 Message内部配置了一个Handler属性,这样Looper分发消息时直接调用Messgae 内部的 Handler 属性就能找到它对应的 handleMessage(Message msg)接收消息的 方法了.
源码很简单,就是在空参方法 obtain()基础上加了个 Handler 属性,还有它的 getter()和 setter():
//handler对象
Handler target;
/**
* Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
* @param h Handler to assign to the returned Message object's <em>target</em> member.
* @return A Message object from the global pool.
*/
public static Message obtain(Handler h) {
Message m = obtain();
//把handler与Message关联上
m.target = h;
return m;
}
public void setTarget(Handler target) {
this.target = target;
}
/**
* Retrieve the {@link android.os.Handler Handler} implementation that
* will receive this message. The object must implement
* {@link android.os.Handler#handleMessage(android.os.Message)
* Handler.handleMessage()}. Each Handler has its own name-space for
* message codes, so you do not need to
* worry about yours conflicting with other handlers.
*/
public Handler getTarget() {
return target;
}
还有一个地方也可以找到他们 Message 与 Handler 关联的代码, 就是Handler发送消息的时候就已经关联上了,在 Handler.java文件中:
//1.发送消息
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
//2. 接着调用
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
// 3.消息关联上handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//这句话是两者关联
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这样子就知道 这个消息的源头是与哪个Handler相关联的, 当然处理消息时,自然就回到了对应线程的Hanlder的 handleMessage方法中了. 这样就保证消息发送和处理的准确性了.