您的位置:首页 > 产品设计 > UI/UE

Android线程间通信(二):MessageQueue(上)

2015-11-15 14:40 597 查看
  MessageQueue(消息队列)是Message(消息)的管理者,它负责保存消息的集合,执行消息入队、出队等操作,同时提供SyncBarrier(同步障碍器)与IdleHandler(闲时任务)机制。SyncBarrier机制允许我们暂停部分Message的出队,而IdleHandler机制允许我们在没有消息需要出队处理时执行一些简单的任务。

1.MessageQueue的创建

消息队列只含一个构造方法,其代码如下:

MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}


  在Java中如果定义类的方法时不设置方法的访问权限,那么方法默认的访问权限不是public,不是private,也不是protected。方法默认的访问权限允许同一个包下的类访问该方法,但不允许子类继承与访问。翻看调用记录可发现,消息队列所在的android.os包下只有Looper类调用过消息队列的构造方法,而消息队列中并没有诸如缓存池之类的结构可以获取已经构造好的消息队列。我们可以断定消息队列是依附在Looper上的消息对象集合,想要获得消息队列必须通过Looper(Looper.myQueue())。

  quitAllowed指定消息队列是否允许退出,其值由Looper类指定,在Looper关联的线程是主线程时才为true(详见Android线程间通信(三):Looper)。nativeInit()方法是由C++编写而成的本地方法,计划在一段时间后写MessageQueue(下)介绍相关的本地方法。

2.SyncBarrier(同步障碍器)

  先上结论:SyncBarrier是特殊的消息对象,其特征是target字段为null且arg1字段保存token,其作用是阻碍消息队列使其在处理普通消息时直接跳过位于SyncBarrier后的所有 同步 消息。

  我们先来看看SyncBarrier是怎么定义的:

/**
* 将同步障碍器加入消息队列。如果此时消息队列处于阻塞状态也不需要唤醒,因为障碍器本身的目的就是
* 阻碍消息队列的循环处理。(可以假设一下为什么阻塞,各种阻塞场景下需不需要唤醒)
* @param when 同步障碍器从何时起效(这个时间是自系统启动开始算起,到指定时间的不包含深度睡
*             眠的毫秒数)。
* @return  新增的同步障碍器token,用于{@link #removeSyncBarrier(int) }移除障碍器时使用
* */
int enqueueSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//从消息池取出一个消息,并将其设置为同步障碍器(target为null,且arg1保存token的消息)
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;

//找到msg在消息队列中的位置(消息队列按照when从小到大排列),并把msg插入其中
…………(省略)
return token;
}
}


  看到这里想必大家有一个疑问:难道普通消息的terget不可能是null吗?我们目光移到MessageQueue.enqueueMessage(Message,long)方法上,这个方法是唯一能够从队列外部将一个普通消息加入队列的方法,方法体中的第一行便有这样一段代码:

if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}


  如果准备加入队列的Message.target为null,加入操作会抛出异常。因此我们可以断定,消息队列中target为null的消息一定是SyncBarrier。

   带着上面得到的结论,我们在消息队列类中找找看SyncBarrier身影,搜索”target == null”:

boolean enqueueMessage(Message msg, long when) {
…………(略)
needWake = mBlocked && p.target == null && msg.isAsynchronous();
…………(略)
}


   这段代码同时涉及到消息队列的阻塞与唤醒,我们稍后再介绍。继续搜索:

/**
*得到下一个等待处理的消息。如果当前消息队列为空或者下一个消息延时时间未到则阻塞线程。
*
* @return  <em>null</em> 消息队列已经退出或者被废弃
*/
Message next() {
…………(略)
for (;;) {
…………(略)
synchronized (this) {
// Try to retrieve the next message.  Return if found.
//now等于自系统启动以来到此时此刻,非深度睡眠的时间
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());
}
…………(略)
}//for(;;)结束
}


  如果队首是SyncBarrier,next()在寻找下一个待处理的普通消息时遇到同步消息会直接跳过,遇到异步消息才会继续执行。这段代码证实了我们之前的结论。继续搜索”target == null”,除了入队抛异常时又出现了一次外再也没出现过了。

相关方法:

MessageQueue.enqueueSyncBarrier(long):往消息队列中添加一个SyncBarrier,并返回这个SyncBarrier的token;

Looper.postSyncBarrie():同上;

MessageQueue.removeSyncBarrier(int):参数为SyncBarrier的token值,从消息队列中移除指定token的Syncbarrier;

Looper.removeSyncBarrier(int):同上。

3.同步消息与异步消息

  前文提到过,同异步消息的区别有两点。第一点,同步消息自始至终都会按照顺序执行(when相同的消息,哪个先入队就先执行哪个),异步消息的执行顺序则完全不确定。第二点,同步消息会同步障碍器拦截而异步消息不会受到影响。

  第一点出自官方文档(Message.isAsynchronous()注释),暂未找到可以佐证的代码块。第二点,我们可以从SyncBarrier的工作原理中得到佐证。

  通常我们会把中断消息、事件消息等较为重要的Message设置为异步消息,以保证系统能够尽早处理这些消息。

4.IdleHandler(闲时任务)

  IdleHandler允许我们在消息队列空闲时执行一些不耗时的简单任务。

/**
* 回调接口,当线程准备阻塞以等待更多的消息时调用。
* 开发者可以实现自己的IdleHandler类,然后通过{@link #addIdleHandler}方法将其添加到消息队列
* 中。一旦消息队列的循环空闲下来,就会执行这些IdleHandler的
* {@link IdleHandler#queueIdle IdleHandler.queueIdle()}方法。你可以在这个方法中添加一次操作。
*/
public static interface IdleHandler {
/**
* 方法在以下两种情况下会被调用:
* 1.当消息队列处理完消息开始等待消息时,此时队列为空;
* 2.当队列中依然有待处理的消息,但这些消息的交付(delivery)时刻要晚于当前时刻时;
*@return  <em>true</em> 下次执行{@link #next() next()}如果遇到空闲,依然执
* 行这个IdleHandler(闲时任务) ; <em>false</em> 这次IdleHandler执行完之后就把它从闲时任务列表中删除。
*/
boolean queueIdle();
}


  我们来看看消息队列是怎么使用IdleHandler的,首先目光转到成员变量上,我们可以发现有这样两个成员:

/**IdleHandler列表**/
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
/**保存等待处理的IdleHandler(闲时任务)**/
private IdleHandler[] mPendingIdleHandlers;


  mIdleHandlers可以理解成是一个IdleHandler的总列表,每次next()将要执行IdleHandler时都会从这个总列表中取出所有的IdleHandler。mPendingIdleHandlers指定哪些IdleHandler需要在本次执行中完成,每次next()将要执行IdleHandler时都会从mIdleHandlers拷贝IdleHandler的总列表到mPendingIdleHandlers中。

下面这一段代码是MessageQueue.next()中用于处理IdleHandler的代码段:

for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; //待处理任务即将被处理,将其从待处理数组中删去(置空引用)

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf("MessageQueue", "IdleHandler threw exception", t);
}

if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;


  执行IdleHandler其实就是调用其queueIdle()方法,queueIdle()如果返回false,next()方法会将该IdleHandler从mIdleHandlers中删除。这样的话,下一次next()方法再执行IdleHandler时就不会再重复执行它了。

  需要特别提醒的是,虽然next()方法是一个无限for循环,但是每次调用next()都只会执行一次mIdleHandlers中的闲时任务。因为在上面的代码段之前有这样一段:

if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}


相关方法:

MessageQueue.isIdling():判断当前MessageQueue是否空闲;

MessageQueue.addIdleHandler(IdleHandler) ;

MessageQueue.removeIdleHandler(IdleHandler)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 通信 线程