大致流程:
调用
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<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> 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<IdleHandler>,在方法 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;
Message nn = n.next;
n.recycleUnchecked();
消息n就会被回收了,然后执行 p.next = nn;
欲知后事,请听下回分解。
感谢
Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)