大致流程:
调用
Handler
发送一个Message
到MessageQueue
中,Looper
调用loop()
方法到MessageQueue
中获取消息,并将消息交给Message
的target
对象 处理。流程完毕
具体流程:
首先看一下Android消息机制中所用到的几个类(Handler、Message、MessageQueue、Looper、ThreadLocal)
Message
定义包含描述和任意数据对象的消息
message
中主要的属性
//用来标识当前的消息,Handler接收到该消息的时候可以通过这个标识来知道需要做什么操作
public int what;
//如果你需要传递的数据是整型的话,可以采用这个两个参数,可以避免创建Bundle对象
public int arg1;
public int arg2;
//可以给接收者传递一个任意类型的对象,如果需要跨进程进行通信的话,那么可以使用它来进行传递数据
public Object obj;
//负责回复消息的 Messenger,有的场景下(比如接受者、发送者模型)需要使用它
public Messenger replyTo;
//当前消息的标志,只在被 Messenger 传递消息时使用,其他情况下都是 -1
public int sendingUid = -1;
//标识当前的消息是否在使用中
//当消息入队的时候这个状态就会被改变,在被回收的时候,状态重置
//当一个消息在使用中的时候,二次入队或者是回收的时候就会报错
/*package*/ static final int FLAG_IN_USE = 1 << 0;
/** If set message is asynchronous */
//标识当前的消息是否为异步
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
/** Flags to clear in the copyFrom method */
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
/*package*/ long when;
//数据的存储
/*package*/ Bundle data;
//发送和处理消息的handler
/*package*/ Handler target;
//消息回调
/*package*/ Runnable callback;
//在某些情况下,还会以链表的形式关联下一个消息
// sometimes we store linked lists of these things
/*package*/ Message next;
//消息池
private static final Object sPoolSync = new Object();
//回收消息链表
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
如何去获取一个消息(Message),推荐做法:
Message.obtain()
Handler.obtainMessage()
原因是:采用这两种方法会从消息池中获取 Message
对象,在一定程度上减少了对象的创建和销毁,节省内存。
obtain
源码看一下:
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
就是在原有的 Obtain
的方法上加了一些属性
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
对象,如果这个对象为空的话,那就 new Message()
,如果 sPool
不为空的话:假设当前链表的结构如下图所示:
执行
Message m = sPool;
就变成下图
继续执行
sPool = m.next;
最后是
m.next = null;
消息的回收再利用
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
可以看出消息回收的时候会将当前消息的标识重置为 FLAG_IN_USE
,如果这个消息再被添加到队列中的时候就会报错了。另外它还清除了其他的数据,并判断了当前消息池中的数据量是否大于最大的消息量,如果没有超过的话就会将当前消息加到回收消息的链表中,并将 sPoolSize+1
。
假设当前的链表形式是这样的:
执行
next = sPool;
以后
最后执行了
sPool = this;
,链表的结构就成了
当是这个方法具体在什么地方被调用了,我们暂且先看一下其他几个类的源码,来找找这个方法的踪迹。
MessageQueue
包含着一个
Message
的列表,消息不会直接添加进来,而是通过Handler
进行添加,通过Looper
进行读取
关于这个队列先说明一点,该队列的实现既非
Collection
的子类,亦非Map
的子类,而是Message
本身。因为Message
本身就是链表节点。队列中的Message mMessages;
成员即为队列,同时该字段直接指向队列中下一个需要处理的消息。
属性:
//队列是否可以退出
// True if the message queue can be quit.
private final boolean mQuitAllowed;
//底层调用的代码
@SuppressWarnings("unused")
private long mPtr; // used by native code
//消息链表的开头
Message mMessages;
private final ArrayList mIdleHandlers = new ArrayList();
private SparseArray mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
什么时候 MessageQueue
会被初始化呢?一般来说 MessageQueue
不是直接访问的,而是在 Looper.myQueue()
方法进行获取:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
入队列的方法:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
//如果当前message所绑定的Headlder就会报错,也就是消息一定要跟Handler进行关联
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
//如果消息已经在使用中,还入队的话就报错
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
//如果消息队列已经退出,还入队的话就报错
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//正常入队列以后,将消息的状态设置为正在使用
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//消息队列为空||当前消息需要立马执行||当前消息执行的时间比消息队列中的头部消息还要早
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//将消息加入到消息队列的头部中
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
//插入消息到队列时,只有在队列头部有个屏障并且当前消息是异步的时才需要唤醒队列
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
//第一次循环的时候,先将队列头部的消息赋值给prev
prev = p;
//第一次循环的时候,将头部的消息的下一个消息重新赋值给p
p = p.next;
//如果p为空,也就是没有下一个消息,或者是当前的消息比下一个消息p要更早执行就break
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//将消息插入到队列中
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
插入队列的方法主要是先判断 Handler
是否绑定,再判断是否被使用中,如果都OK的话,就将当前的 Message
设置为正在使用中,并且将 Message
执行的时间戳存入到 Message
中,接下来的就是要判断当前的消息要插入到哪个位置。
if (p == null || when == 0 || when < p.when)
这个时候表示的是需要插入到队列的头部。
否则的话:(假设当前的链表结构如下)
开始遍历的时候,定义一个变量
prev
,将 p
的值赋值给 prev
,然后将当前消息的下一个指向赋值给 p
:
假设此时
p
所指消息的 when
比新消息晚,则新消息位置在 prev
与 p
中间
如果当前的消息队列里面没有消息,或者是当前消息的执行状态时立马执行,或是当前消息的执行时间要比在队列中的所有消息都要早的话,那就将当前的消息插入队列中的头部。else的话,就进行判断,找出当前消息需要插入的位置。
出队列的方法
第一段
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
如果mPtr
为0则返回 null
。那么 mPtr
是什么?值为0又意味着什么?在 MessageQueue
构造方法中调用了 native
方法并返回了 mPtrmPtr = nativeInit();
;在 dispose()
方法中将其值置 mPtr = 0;
, 并且调用了 nativeDestroy()
。而 dispose()
方法又在 finalize()
中被调用。另外每次 mPtr
的使用都调用了 native
的方法,其本身又是 long
类型,因此推断它对应的是C/C++的指针。因此可以确定,mPtr
为一个内存地址,当其为0说明消息队列被释放了。这样就很容易理解为什么 mPtr==0
的时候返回 null
了。
第二段
这部分涉及到的代码基本上就是这个 next()
方法本身了,但可以肯定的是这里的返回语句是 return msg
。同时从 enqueueMessage()
方法可以看出来,在这个队列中取到的 message
对象不可能为空,因此这里的返回绝对不为空。
如果 next()
方法返回为空的时候,则说明这个消息队列正在退出或者是被释放回收了。
首先看一下 pendingIdleHandlerCount
,这是一个局部变量,初始化为-1,然后被赋值为 mIdleHandlers.size()
,mIdleHandlers
是一个 ArrayList
,在方法 addIdleHandler
中添加元素,在方法 removeIdleHandler
中移除元素。而我们所有的 Handler
并未实现 IdleHandler
接口,所以 mPendingIdleHandlers
的值应该是为0,因此可以看出与该变量相关的部分代码运行情况是确定的,好的,把不影响循环控制的代码减掉。
接下来要减的是:
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
看源码说明:
Flush any Binder commands pending in the current thread to the kernel driver. This can be useful to call before performing an operation that may block for a long time, to ensure that any pending object references have been released in order to prevent the process from holding on to objects longer than it needs to.
Binder.flushPendingCommands()
方法被调用说明后面的代码可能会引起线程阻塞。然后把这段减掉。
最后再把代码精简一下的样子就成为了:
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//当前时间要比消息队头的Message执行时间要早,
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
//让队列一直阻塞下去,直到下次被唤醒
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
}
nextPollTimeoutMillis = 0;
}
}
虽然还是很长,但也不能再减了。大致思路如下:如果有异步的消息,先执行异步消息。如果没有的话先获取第一个同步的 message
。如果它的 when
不晚与当前时间,就返回这个 message
;否则计算当前时间到它的 when
还有多久并保存到 nextPollTimeMills
中,然后调用 nativePollOnce()
来延时唤醒,唤醒之后再照上面那样取 message
。
移除消息
当我们需要移除某个 Message
的时候,一般都会调用 Handler
的 removeMessages
方法,其实 Handler
都是调用的 MessageQueue
中的方法:
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
public final void removeMessages(int what, Object object) {
mQueue.removeMessages(this, what, object);
}
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
再来看一下 MessageQueue
中的 void removeMessages(Handler h, int what, Object object)
方法:
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
首先判断 Handler
是否为空,这个是必不可少的。因为需要删除的是用当前 Handler
所发送的消息,如果没有 Handler
这个参数就无法得知要删除哪些消息了。接下来是一个同步代码块,代码块中有两个while
循环,为什么需要两个呢?让我们拭目以待:
第一个While
假设当前链表的状态是这样的:
mMessages = n;
p.recycleUnchecked();
这个时候
message
就被回收了。然后执行 p = n;
就这样轻松的移除了一个数据,然后
while
循环继续执行,继续删除 message
。原本是很美好的遍历着链表中的数据,忽然碰到一个过不了条件的 message
,就会跳出循环,可是万一我们后面的部分数据是符合条件的,这可怎么办呢?都还没有删除完呢,接下来就是我们第二个循环出场了:
第二个循环
经过第一个循环以后,链表的结构除了长度上可能会有变化,其他的都是和原来一样的,我们在贴一次当前结构的图吧:
Message n = p.next;
如果不为空,但是不满足移除条件,则将复制给P,然后继续将后移。假设如果我们当前的n是满足移除条件的,执行代码
Message nn = n.next;
n.recycleUnchecked();
消息n就会被回收了,然后执行
p.next = nn;
一轮循环结束,开启下一轮循环!!!
欲知后事,请听下回分解。
感谢
Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)
【Android自助餐】Handler消息机制完全解析(二)MessageQueue的队列管理