android 队列管理,【Android自助餐】Handler消息机制彻底解析(二)MessageQueue的队列管理...

Android自助餐Handler消息机制彻底解析(二)MessageQueue的队列管理

关于这个队列先说明一点,该队列的实现既非Collection的子类,亦非Map的子类,而是Message自己。由于Message自己就是链表节点(见Message中obtain()与recycle()的前因后果)。

队列中的Message mMessages;成员即为队列,同时该字段直接指向队列中下一个须要处理的消息。java

添加到消息队列enqueueMessage()

要将message添加到队列除了提供message以外,还需提供消息触发时间when。

若是当前队列为空则直接mMessage=message便可。不然就须要逐个对比队列中每一个message的when和新消息的when来肯定新消息在队列中的位置。

先给出核心源码(有删减)android

Message p = mMessages;

if (p == null || when == 0 || when < p.when) {

msg.next = p;

mMessages = msg;

} else {

Message prev;

for (;;) {

prev = p;

p = p.next;

if (p == null || when < p.when) {

break;

}

}

msg.next = p;

prev.next = msg;

}

if (needWake) {

nativeWake(mPtr);

}

先看下新消息须要放到队头的状况:p == null || when == 0 || when < p.when。即队列为空,或者新消息须要当即处理,或者新消息处理的事件比队头消息更早被处理。这时只要让新消息的next指向当前队头,让mMessages指向新消息便可完成插入操做。

除了上述三种状况就须要遍历队列来肯定新消息位置了,下面结合示意图来讲明。

假设当前消息队列以下

2641194040f8d5f73405b09fba16b5df.png

开始遍历:p向队尾移,引入prev指向p上一个元素

e0883d76a46f1cffb0b6512993010708.png

假设此时p所指消息的when比新消息晚,则新消息位置在prev与p中间

858f43139a84b7b4dc7438e0cacbf4e3.png

最后即是调用native方法来唤醒(Linux的epoll,有兴趣的自行百度)。web

从队列取出消息next()

这部份内容有点高能,请根据我的BPU(BrainProcessUnit)酌情理解。

首先这个方法须要返回Message,那么咱们如今来看看哪里有return。(共三段,咱们最后看第二段。)ruby

第一段

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

若是mPtr为0则返回null。那么mPtr是什么?值为0又意味着什么?在MessageQueue构造方法中调用了native方法并返回了mPtrmPtr = nativeInit();;在dispose()方法中将其值置0mPtr = 0;而且调用了nativeDestroy()。而dispose()方法又在finalize()中被调用。另外每次mPtr的使用都调用了native的方法,其自己又是long类型,所以推断它对应的是C/C++的指针。所以能够肯定,mPtr为一个内存地址,当其为0说明消息队列被释放了。这样就很容易理解为何mPtr==0的时候返回null了。数据结构

第三段

你没有看错,第二段在后面svg

if (mQuitting) {

dispose();

return null;

}

这里的意思也很明显,当这个消息队列退出的时候,返回空。并且在返回前调用了dispose()方法,显然这意味着该消息队列将被释放。ui

第二段

这部分涉及到的代码基本上就是这个next()方法自己了,但能够确定的是这里的返回语句是return msg;。同时从enqueueMessage()方法能够看出来,在这个队列中取到的message对象不可能为空,所以这里的返回绝对不为空。

如此一来就能够得出一个结论:若是next()方法为空说明这个消息队列正在退出或将被释放回收。

继续来看这个next(),这个代码有点长,因此先作个减法。

第一个要减的就是pendingIdleHandlerCount,这个局部变量初始为-1,后面被赋值mIdleHandlers.size();。这里的mIdleHandlers初始为new ArrayList(),在addIdleHander()方法中增长元素,在removeIdleHander()方法中移除元素。而咱们所用的Handeler并未实现IdleHandler接口,所以在next()方法中pendingIdleHandlerCount的值要么为0,要么为-1,所以能够看出与该变量相关的部分代码运行状况是肯定的,好的,把不影响循环控制的代码减掉。

第二个要减的是Binder.flushPendingCommands()这个代码看源码说明:this

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.spa

这段话啥意不懂也不要紧,这里只须要知道:Binder.flushPendingCommands()方法被调用说明后面的代码可能会引发线程阻塞。而后把这段减掉。

第三个要减的是一个log语句if (DEBUG) Log.v(TAG, "Returning message: " + msg);

第四个要减的是上面提到的“第一段”返回null的语句,可是“第三段”得留着。

最后再把注释干掉给上代码:.net

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) {

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;

}

if (pendingIdleHandlerCount <= 0) {//上面分析过该变量要么为0要么为-1

mBlocked = true;

continue;

}

}

nextPollTimeoutMillis = 0;

}

}

虽然仍是很长,但也不能再减了。大体思路以下:先获取第一个同步的message。若是它的when不晚与当前时间,就返回这个message;不然计算当前时间到它的when还有多久并保存到nextPollTimeMills中,而后调用nativePollOnce()来延时唤醒(Linux的epoll,有兴趣的自行百度),唤醒以后再照上面那样取message,如此循环。代码中对链表的指针操做占了必定篇幅,其余的逻辑很清楚,就不一句句分析了。

从队列移除消息removeMessages()

该方法有2个重载,除此以外还有removeCallbacksAndMessages()等方法也能够移除消息。但代码段都基本同样,这里以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;

// Remove all messages at front.

while (p != null && p.target == h && p.what == what

&& (object == null || p.obj == object)) {

Message n = p.next;

mMessages = n;

p.recycleUnchecked();

p = n;

}

// Remove all messages after front.

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是否为空没必要多说,而后即是同步代码段,只里面有两个while循环。为何有两个呢?学过数据结构链表的都知道,链表分两种:带头结点和不带头结点。而这两种链表的遍历方式有所不一样:不带头结点的链表中,第一个元素须要单独处理,而后才能将后续部分当作带头结点的链表来使用while循环遍历。能够看出MessageQueue是不带头结点的链表,并且遍历过程当中有须要删除节点,所以要特殊处理的不仅是第一个元素,而是第一组符合删除条件的元素。有点晕了是吧,没关系,咱们开始斗图。

第一个while

假设须要遍历的消息队列如图所示。

9f1aff8d9dc205209213cd3e3da1e796.png

为了让第一个while能够执行,咱们假设前3个元素符合移除条件,即前三个Message的targe、what、obj分别与指定的handler、what、object相同。首先第一个元素知足条件进行以下操做:

执行n=p.next;

1243fb6c2b29f0d9606720c0daebc5f8.png

后移mMessage;

a05c1450d631626ea92a40db8b0f0206.png

回收p指向的元素,即第一个元素。

6b197c29a3e237e34f9a68b3bbcb97c5.png

让p指向新的队头。

664358c0d29557fcbe566434177a7219.png

此时又与初始队列状态同样了。先前咱们假设队头有三个元素符合移除条件,所以再循环执行上面4图2边后又获得初始状态的队列,此时队头元素不知足移除条件所以while终止,同时新的队列变成了“带头结点的链表”,所以mMessage指向的元素永远不用被判断是否知足移除条件。

第二个while

此时消息队列状态以下:

7f07ea1840a8823b70754c448cdc20ed.png

执行n=p.next;

b659f78778b875c22f92a03ff1e479c1.png

假设n指向的元素不知足移除条件,则只须要将p和n后移,如此也说明,p指向的元素老是已经被判断过不知足移除条件的。这部分逻辑很简单到给图就是看不起读者的智商,如今咱们假设n指向的元素知足移除条件,即当前队列以下:

7a2bc62ace28e12f482f2708b996d689.png

执行nn=n.next;

9a2c1ae979c4322fec2343f2d6d5375a.png

回收n指向的元素

78696e558f69f4272570c46222b1dc60.png

执行p.next=nn;

d9f2806e06da9210f30ba2f44bf9f88b.png

这时p以后的队列又是一个带头结点的链表。能够继续while了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值