【Android自助餐】Handler消息机制完全解析(二)MessageQueue的队列管理

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

  • Android自助餐Handler消息机制完全解析二MessageQueue的队列管理

    • 添加到消息队列enqueueMessage
    • 从队列取出消息next
      • 第一段
      • 第三段
      • 第二段
    • 从队列移除消息removeMessages
      • 第一个while
      • 第二个while

关于这个队列先说明一点,该队列的实现既非Collection的子类,亦非Map的子类,而是Message本身。因为Message本身就是链表节点(见Message中obtain()与recycle()的来龙去脉)。

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

添加到消息队列enqueueMessage()

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

如果当前队列为空则直接mMessage=message即可。否则就需要逐个对比队列中每个message的when和新消息的when来确定新消息在队列中的位置。

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

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指向新消息即可完成插入操作。

除了上述三种情况就需要遍历队列来确定新消息位置了,下面结合示意图来说明。

假设当前消息队列如下

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

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

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

从队列取出消息next()

这部分内容有点高能,请根据个人BPU(BrainProcessUnit)酌情理解。

首先这个方法需要返回Message,那么我们现在来看看哪里有return。(共三段,我们最后看第二段。)

第一段

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了。

第三段

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

if (mQuitting) {
    dispose();
    return null;
}

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

第二段

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

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

继续来看这个next(),这个代码有点长,所以先做个减法。

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

第二个要减的是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()方法被调用说明后面的代码可能会引起线程阻塞。然后把这段减掉。

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

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

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

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

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

为了让第一个while可以执行,我们假设前3个元素符合移除条件,即前三个Message的targewhatobj分别与指定的handlerwhatobject相同。首先第一个元素满足条件进行如下操作:

执行n=p.next;

后移mMessage;

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

让p指向新的队头。

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

第二个while

此时消息队列状态如下:

执行n=p.next;

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

执行nn=n.next;

回收n指向的元素

执行p.next=nn;

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

时间: 2024-10-25 09:36:45

【Android自助餐】Handler消息机制完全解析(二)MessageQueue的队列管理的相关文章

【Android自助餐】Handler消息机制完全解析(一)Message中obtain()与recycle()的来龙去脉

[Android自助餐]Handler消息机制完全解析(一)Message中obtain()与recycle()的来龙去脉 Android自助餐Handler消息机制完全解析一Message中obtain与recycle的来龙去脉 提供obtain 回收recycle 提供obtain() 在obtain的所有重载方法中,第一行都是Message m = obtain();,即调用空参的方法. 先来看一下这个空参方法 public static Message obtain() { synchr

深入解析Android中Handler消息机制

Android提供了Handler 和 Looper 来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange).Handler消息机制可以说是Android系统中最重要部分之一,所以,本篇博客我们就来深入解析Android中Handler消息机制. Handler的简单使用 为什么系统不允许子线程更新UI 因为的UI控件不是线程安全的. 如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那为什么不对UI控件的访

Android消息传递之Handler消息机制(一)

前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不忘初心吧,这也是今天来学习总结Handler消息机制的一个原因. Handler机制产生背景 一个Android应用程序被创建的时候都会创建一个UI主线程,但是有时我们会有一些比较耗时的操作,为了防止阻塞UI主线程,我们会将耗时的操作放到子线程中进行处理,处理完之后操作UI,但是Android不允许

[转]Handler消息机制详解

能简单说得我们尽量不复杂: 为了避免ANR,我们会通常把 耗时操作放在子线程里面去执行,因为子线程不能更新UI,所以当子线程需要更新的UI的时候就需要借助到安卓的消息机制,也就是Handler机制了. 注意:在安卓的世界里面,当 子线程 在执行耗时操作的时候,不是说你的主线程就阻塞在那里等待子线程的完成--也不是调用 Thread.wait()或是Thread.sleep().安卓采取的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程.以这种方式设计你的应用程序,将能

Android Handler消息机制源码解析

好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个类合作完成,分别是Handler,MessageQueue,Looper,Message Handler : 获取消息,发送消息,以及处理消息的类 MessageQueue:消息队列,先进先出 Looper : 消息的循环和分发 Message : 消息实体类,分发消息和处理消息的就是这个类 主要工

Android开发:Handler异步通信机制全面解析(包含Looper、Message Queue

前言 最近刚好在做关于异步通信的需求,那么,今天我们来讲解下Android开发中的Handler异步通信传递机制(包括Looper.Message Queue) 目录 定义 Android提供的一套消息传递机制 作用 用于实现子线程对UI线程的更新,实现异步消息的处理: - 在新启动的线程中发送消息 - 在主线程中获取并处理信息 为什么要用Handler 在安卓开发中: - 为了保证Android的UI操作是线程安全的,Android规定了只允许UI线程修改Activity里的UI组件: - 但

Handler消息机制与Binder IPC机制完全解析

1.Handler消息机制 序列 文章 0 Android消息机制-Handler(framework篇) 1 Android消息机制-Handler(native篇) 2 Android消息机制-Handler(实战篇) 2.Binder IPC机制 序列 文章 0 Binder开篇 1 Binder Driver初探 2 Binder Driver再探 3 启动Service Manager 4 获取Service Manager 5 注册服务(addService) 6 获取服务(getS

Android Handler消息机制深入浅出

作为Android开发人员,Handler这个类应该是再熟悉不过了,因为几乎任何App的开发,都会使用到Handler这个类,有些同学可能就要说了,我完全可以使用AsyncTask代替它,这个确实是可以的,但是其实AsyncTask也是通过Handler实现的,具体的大家可以去看看源码就行了,Handler的主要功能就是实现子线程和主线程的通信,例如在子线程中执行一些耗时操作,操作完成之后通知主线程跟新UI(因为Android是不允许在子线程中跟新UI的). 下面就使用一个简单的例子开始这篇文章

Android中的Handler消息机制

转自:http://blog.csdn.net/liuhe688/article/details/6407225 在分析Android消息机制之前,我们先来看一段代码: public class MainActivity extends Activity implements View.OnClickListener { private TextView stateText; private Button btn; @Override public void onCreate(Bundle