该文章属于《Android Handler机制之》系列文章,若是想了解更多,请点击 《Android Handler机制之总目录》安全
在前面的文章中咱们讲解了Handler、Looper、MessageQueue的具体关系,了解了具体的消息循环的流程。下面将一块儿来探讨最为整个消息循环的消息载体Message。bash
Message中能够携带的数据比较丰富,下面对一些经常使用的数据进行了分析。oop
/**
* 用户定义的消息代码,以便当接受到消息是关于什么的。其中每一个Hanler都有本身的命名控件,不用担忧会冲突
*/
public int what;
/**
* 若是你只想存不多的整形数据,那么能够考虑使用arg1与arg2,
* 若是须要传输不少数据可使用Message中的setData(Bundle bundle)
*/
public int arg1;
/**
* 若是你只想存不多的整形数据,那么能够考虑使用arg1与arg2,
* 若是须要传输不少数据可使用Message中的setData(Bundle bundle)
*/
public int arg2;
/**
* 发送给接受方的任意对象,在使用跨进程的时候要注意obj不能为null
*/
public Object obj;
/**
* 在使用跨进程通讯Messenger时,能够肯定须要谁来接收
*/
public Messenger replyTo;
/**
* 在使用跨进程通讯Messenger时,能够肯定须要发消息的uid
*/
public int sendingUid = -1;
/**
* 若是数据比较多,能够直接使用Bundle进行数据的传递
*/
Bundle data;
复制代码
其中关于what的值为何不会冲突的缘由是,以前咱们讲过的handler是与线程进行绑定的。也就是说不一样消息循环消息的发送,处理的线程是不同的。固然是不会冲突的。对于Messenger,由于涉及到Binder机制,这里就不过多的描述了,有兴趣的小伙伴能够自行查询相关资料学习。post
官方建议使用Message.obtain()系列方法来获取Message实例,由于其Message实例是直接从Handler的消息池中获取的,能够循环利用,没必要另外开辟内存空间,效率比直接使用new Message()建立实例要高。其中具体建立消息的方式,我已经为你们分好类了。具体分类以下:学习
//无参数
public static Message obtain() {...}
//带Messag参数
public static Message obtain(Message orig) {}
//带Handler参数
public static Message obtain(Handler h) {}
public static Message obtain(Handler h, Runnable callback){}
public static Message obtain(Handler h, int what){}
public static Message obtain(Handler h, int what, Object obj){}
public static Message obtain(Handler h, int what, int arg1, int arg2){}
public static Message obtain(Handler h, int what,int arg1, int arg2, Object obj) {}
复制代码
其中在Message的obtain带参数的方法中,内部都会调用无参的obtain()方法来获取消息后。而后并根据其传入的参数,对Message进行赋值。(关于具体的obtain方法会在下方消息池实现原理中具体描述)ui
既然官方建议使用消息池来获取消息,那么在了解其内部机制以前,咱们来看看Message中的消息池的设计。具体代码以下:this
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的消息池设计,咱们大概能看出如下几点:spa
在上文中,咱们已经知道了在使用消息池得到消息时,都会调用无参的obtain()方法。具体代码以下:线程
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; //从新标识当前Message没有使用过
sPoolSize--;
return m;
}
}
return new Message();//若是为空直接返回
}
复制代码
从上述代码中,咱们能够了解,也就是当前 消息池不为空(sPool !=null)的状况下,那么咱们就能够从消息池中获取数据,相应的消息池中的消息数量会减小。消息池的内部实现是以链表的形式,其中spol指针指向当前链表的头结点,从消息池中获取消息是以移除链表中sPool所指向的节点的形式,具体原理以下图所示: 设计
在Meaage的消息回收中,消息的实际回收方法是recycleUnchecked()方法,具体以下图所示:
void recycleUnchecked() {
//用于表示当前Message消息已经被使用过了
flags = FLAG_IN_USE;
//状况以前Message的数据
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
//判断当前消息池中的数量是否是小于最大数量,其中 MAX_POOL_SIZE=50
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;//记录当前消息池中的数量
}
}
}
复制代码
在recycleUnchecked()方法中,大体分为三步,第一步将该条回收的消息状态设置为正在使用,第二步将Message全部的存储信息都变为初始值,第三步,若是当前消息池仍可以存储回收的消息,那么就将消息存储在消息池中。其中将回收消息加入消息池中是使用链表的形式,具体回收消息到消息池以下图所示:
这里为了方便你们梳理逻辑,我提早将几种会调用消息进行回收的状况都描述出来了,具体的状况以下所示:
void removeMessages(Handler h, int what, Object object)
void removeMessages(Handler h, Runnable r, Object object)
void removeCallbacksAndMessages(Handler h, Object object)
复制代码
当使用Handler删除某条消息的时候,会分别调用MessageQueue的
这三个方法逻辑比较相似。这里直接选取removeCallbacksAndMessages()方法来进行讲解。具体代码以下:
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// 第一次循环
while (p != null && p.target == h
&& (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 && (object == null || n.obj == object)) {
//下面操做会将知足回收条件的消息,从消息队列中移除
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
复制代码
在removeCallbacksAndMessages(Handler h, Object object)方法中,在该方法中分别进行了两次循环,确定有不少读者朋友会很好奇,为何这里会进行两次循环呢?下面我就具体来说解一下。
咱们都知道,在Handler机制中,多个handler对应同一个MessageQueue对应同一个Looper,Handler与MessageQueue与Looper之间的关系是N:1:1
。也就是说在MessageQueue中咱们能够有多个不一样Handler发送的Message。那么咱们再结合上面的代码,咱们来分析这两次循环。
根据上文对代码的理解,第一次循环会将MessageQueue中,当前Handler发送的全部消息移除,注意!!!!!!!!!这里并不会将整个MessageQueue中的当前Handler发送的消息所有移除
,而是在遍历过程当中,若是有其余Handler发送的消息,那么就会将mMessages指向头结点并跳出循环。以下图所示:
通过上文的分析,咱们已经知道了,在进行第一次循环后,已经将在removeCallbacksAndMessages方法执行时全部对应的Handler发送的消息移除掉了,可是MessageQueue中可能任然会残留没有移除掉的消息。那么第二次循环,根据代码来理解的话,咱们能够获得下图:
因此为了保证将整个MessageQueue中该Handler发送的消息所有被移除,在第一次循环移除以后,咱们必需要再执行一次循环移除操做
。
ps:很是感谢@承香墨影指出的错误的地方,以前没有考虑到同步的关系,误导了你们。心里感到无比的愧疚。
public static void loop() {
//省略部分代码
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//省略部分代码
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略部分代码
//回收消息
msg.recycleUnchecked();
}
}
复制代码
咱们都知道消息的取出是经过Looper类中的loop方法。从代码中咱们能够看出,当消息取出并执行相应操做后。最后会将消息回收。
public void quitSafely() { mQueue.quit(true);}
public void quit() { mQueue.quit(false); }
复制代码
当退出消息队列的时候,也就是调用Loooper的quitSafely()或quit()方法,从代码中咱们能够看出,会调用其内部的MessageQueue的quit(boolean safe)方法。咱们继续跟踪代码。
void quit(boolean safe) {
if (!mQuitAllowed) {//注意,主线程是不能退出消息循环的
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {//若是当前循环消息已经退出了,直接返回
return;
}
mQuitting = true;
if (safe) {//若是是安全退出
removeAllFutureMessagesLocked();
} else {//若是不是安全退出
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
复制代码
在MessageQueue的quit(boolean safe)方法中,会将mQuitting (用于判断当前消息队列是否已经退出)置为true,同时会根据当前是否安全退出的标志 (safe)来走不一样的逻辑,若是安全则走removeAllFutureMessagesLocked()方法,若是不是安全退出则走removeAllMessagesLocked()方法。下面分别对这两个方法进行讨论。
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
复制代码
非安全退出其实很简单,就是将全部消息队列中的消息所有回收。具体示意图以下所示:
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;//当前队列中的头消息
if (p != null) {
if (p.when > now) {//判断时间,若是Message的取出时间比当前时间要大直接移除
removeAllMessagesLocked();
} else {
Message n;
for (;;) {//继续判断,取队列中全部大于当前时间的消息
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {//将全部全部大于当前时间的消息的消息回收
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
复制代码
观察上诉代码,在该方法中,会判断当前消息队列中的头消息的时间是否大于当前时间,若是大于当前时间就会removeAllMessagesLocked()方法(也就是回收所有消息),反之,则回收部分消息,同时没有被回收的消息任然能够被取出执行。具体示意图以下所示:
在Looper调用quit()方法时,也就是Looper退出消息循环的时候,咱们已经知道了其内部会调用MessageQueue的quit(boolean safe)方法。当MessageQueue退出的时候,会将mQuitting置为true。那么当对应的Handler发送消息时,咱们都知道会调用MessageQueue的enqueueMessage(Message msg, long when)方法。那么如今咱们观察下列代码:
boolean enqueueMessage(Message msg, long when) {
...省略部分代码
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;
}
...省略部分代码
}
复制代码
观察该代码咱们得知,当循环消息退出的时候,若是这个时候Handler继续发送消息来。会将该消息回收。可是如今这里有个问题。既然咱们的消息队列已经结束循环了。那么咱们回收该消息又有什么用呢?咱们又不能从新的开启消息循环。不知道Google这里为何会这么设计。