Android四个多线程分析:MessageQueue的实现

CC 许可,转载请注明出处

在前面两篇文章《Android多线程分析之二:Thread的实现》。《Android多线程分析之三:Handler。Looper的实现》中分别介绍了 Thread 的创建,执行,销毁的过程以及 Thread与 Handler,Looper 之间的关联:Thread 在其 run() 方法中创建和执行消息处理循环 Looper。而 Looper::loop() 方法不断地从 MessageQueue 中获取消息,并由 Handler 分发处理该消息。接下来就来介绍 MessageQueue 的运作机制,MessageQueue。

參考源代码:

android/framework/base/core/java/android/os/MessageQueue.java
android/framework/base/core/java/android/os/Message.java
android/frameworks/base/core/jni/android_os_MessageQueue.h
android/frameworks/base/core/jni/android_os_MessageQueue.cpp

先来看 MessageQueue 的构造函数以及重要的成员变量:

    // True if the message queue can be quit.
private final boolean mQuitAllowed;
private int mPtr; // used by native code
Message mMessages;
private boolean mQuiting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;

mQuitAllowed: 其含义与 Looper.prepare(boolean quitAllowed) 中參数含义一直。是否同意中止。
mPtr:Android MessageQueue 是通过调用 C++ native MessageQueue 实现的,这个 mPtr 就是指向 native MessageQueue;
mMessages:Message 是链表结构的,因此这个变量就代表 Message 链表。
mQuiting:是否终止了。
mBlocked:是否正在等待被激活以获取消息。

MessageQueue 的构造函数非常easy:

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

它通过转调 native 方法 nativeInit() 实现的,后者是定义在 android_os_MessageQueue.cpp 中:

static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return;
} nativeMessageQueue->incStrong(env);
android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}
static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
NativeMessageQueue* nativeMessageQueue) {
env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
reinterpret_cast<jint>(nativeMessageQueue));
}

nativeInit() 方法创建 NativeMessageQueue 对象。并将这个对象的指针复制给 Android MessageQueue 的 mPtr。NativeMessageQueue 的定义例如以下:

class MessageQueue : public RefBase {
public:
/* Gets the message queue's looper. */
inline sp<Looper> getLooper() const {
return mLooper;
} bool raiseAndClearException(JNIEnv* env, const char* msg);
virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) = 0;
protected:
MessageQueue();
virtual ~MessageQueue();
protected:
sp<Looper> mLooper;
}; class NativeMessageQueue : public MessageQueue {
public:
NativeMessageQueue();
virtual ~NativeMessageQueue();
virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj);
void pollOnce(JNIEnv* env, int timeoutMillis);
void wake(); private:
bool mInCallback;
jthrowable mExceptionObj;
};

当中值得关注的是 NativeMessageQueue 的构造以及pollOnce。wake 两个方法,它们是Java MessageQueue 中 nativePollOnce 和 nativeWake 的 native 方法:

NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
} void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
mInCallback = true;
mLooper->pollOnce(timeoutMillis);
mInCallback = false;
} void NativeMessageQueue::wake() {
mLooper->wake();
}

在 NativeMessageQueue 的构造函数中。会获取当前线程的 Looper(注意这是 C++ Looper,定义在frameworks/native/libs/utils/Looper.h 中)。假设当前线程还没有 Looper,就创建一个。并保存在线程的 TLS 中。pollOnce 和 wake 终于都是通过 Linux 的 epoll 模型来实现的。pollOnce() 通过等待被激活,然后从消息队列中获取消息;wake() 则是激活处于等待状态的消息队列。通知它有消息到达了。这是典型的生产者-消费者模型。

对于Android MessageQueue 来说。其基本的工作就是:接收投递进来的消息,获取下一个须要处理的消息。

这两个功能是通过 enqueueMessage() 和 next() 方法实现的。

next() 在前一篇文章介绍 Looper.loop() 时提到过。

在分析这两个函数之前,先来介绍一下 Message:前面说过 Message 是完备的。即它同一时候带有消息内容和处理消息的 Handler 或 callback。以下列出它的主要成员变量:

public int what;     // 消息 id
public int arg1; // 消息參数
public int arg2; // 消息參数
public Object obj; // 消息參数
long when; // 处理延迟时间,由 Handler 的 sendMessageDelayed/postDelayed 设置
Handler target; // 处理消息的 Handler
Runnable callback; // 处理消息的回调
Message next; // 链表结构。指向下一个消息

Message 有一些名为 obtain 的静态方法用于创建 Message,通常我们都是通过 Handler 的 obtain 静态方法转调 Message 的静态方法来创建新的 Message。

接下来分析 enqueueMessage:

    final boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
} boolean needWake;
synchronized (this) {
if (mQuiting) {
return false;
} msg.when = when;
Message p = mMessages;
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 = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
if (needWake) {
nativeWake(mPtr);
}
return true;
}

首先检測消息的合法性:是否已经在处理中和是否有处理它的Handler,然后推断 mQuiting 是否中止了,假设没有则依据消息处理时间排序将消息插入链表中的合适位置。

在这当中作了一些降低同步操作的优化,即使当前消息队列已经处于 Blocked 状态,且队首是一个消息屏障(和内存屏障的理念一样。这里是通过 p.target == null 来推断队首是否是消息屏障)。而且要插入的消息是全部异步消息中最早要处理的才会 needwake 激活消息队列去获取下一个消息。

Handler 的 post/sendMessage 系列方法最后都是通过转调 MessageQueue 的 enqueueMessage 来实现的,比方:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
} private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

事实上 Handler 中与Message 相关的静态方法都是通过 MessageQueue 的相应的静态方法实现的,比方 removeMessages, hasMessages, hasCallbacks 等等,这里就不一一详述了。至此。已经完整地分析了怎样通过 Handler 提交消息到 MessageQueue 中了。

以下来分析怎样从 MessageQueue 中获取合适的消息, 这是 next() 要做的最基本的事情,next() 方法还做了其他一些事情,这些其他事情是为了提高系统效果,利用消息队列在空暇时通过 idle handler 做一些事情,比方 gc 等等。

但它们和获取消息关系不大。所以这部分将从略介绍。

   final Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0; for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(mPtr, nextPollTimeoutMillis); synchronized (this) {
if (mQuiting) {
return null;
} // Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
} // If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
} if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} // Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler 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; // While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

队列被激活之后,首先推断队首是不是消息屏障,假设是则跳过全部的同步消息,查找最先要处理的异步消息。假设第一个待处理的消息还没有到要处理的时机则设置激活等待时间。否则这个消息就是须要处理的消息,将该消息设置为 inuse,并将队列设置为非 blocked 状态。然后返回该消息。

next() 方法是在 Looper.loop() 中被调用的,Looper 在获得要处理的消息之后就会调用和消息关联的 Handler 来分发消息。这里再回想一下:

  public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
} msg.target.dispatchMessage(msg); msg.recycle();
}
}

假设队列中没有消息或者第一个待处理的消息时机未到,且也没有其他利用队列空暇要处理的事务,则将队列设置为设置 blocked 状态,进入等待状态;否则就利用队列空暇处理其他事务。

至此,已经对 Android 多线程相关的主要概念 Thread, HandlerThread, Handler, Looper, Message, MessageQueue 作了一番介绍。下一篇就要讲讲 AsyncTask。这是为了简化 UI 多线程编程为提一个方便的工具类。

版权声明:本文博主原创文章,博客,未经同意不得转载。

Android四个多线程分析:MessageQueue实现的更多相关文章

  1. Android多线程分析之四:MessageQueue的实现

    Android多线程分析之四:MessageQueue的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前面两篇文章<Androi ...

  2. Android多线程分析之中的一个:使用Thread异步下载图像

    Android多线程分析之中的一个:使用Thread异步下载图像 罗朝辉 (http://blog.csdn.net/kesalin) CC 许可.转载请注明出处 打算整理一下对 Android Fr ...

  3. Android多线程分析之五:使用AsyncTask异步下载图像

    Android多线程分析之五:使用AsyncTask异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处 在本系列文章的第一篇<An ...

  4. Android多线程分析之三:Handler,Looper的实现

    Android多线程分析之三:Handler,Looper的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多 ...

  5. Android多线程分析之一:使用Thread异步下载图像

    Android多线程分析之一:使用Thread异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处   打算整理一下对 Android F ...

  6. Android多线程分析之二:Thread的实现

    Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处   在前文<Android多线程分析之一 ...

  7. android 内存溢出问题分析

      最近的项目中,内存一直再增长,但是不知道是什么问题,导致内存溢出,在网上看到了这么一篇关于内存分析与管理的文章,解决了部分问题,感觉这篇文 章还不错,就转帖到我的blog上了,希望对大家有所帮助. ...

  8. Android Learning:多线程与异步消息处理机制

    在最近学习Android项目源码的过程中,遇到了很多多线程以及异步消息处理的机制.由于之前对这块的知识只是浅尝辄止,并没有系统的理解.但是工程中反复出现让我意识到这个知识的重要性.所以我整理出这篇博客 ...

  9. Android Choreographer 源码分析

    Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整, ...

随机推荐

  1. RobotFramework 数据分离

    RobotFramework 数据分离分为三种 1. Run 添加参数 2. 外部的Python文件 3. 读取Excel Run 添加参数 (适用于jenkins) 使用-v 参数名:参数值 使用外 ...

  2. Net MVC微信扫码支付

    微信扫码支付+Asp.Net MVC 这里的扫码支付指的是PC网站上面使用微信支付,也就是官方的模式二,网站是Asp.net MVC,整理如下. 一.准备工作 使用的微信API中的统一下单方法,关键的 ...

  3. JavaScript快速入门(四)——JavaScript函数

    函数声明 之前说的三种函数声明中(参见JavaScript快速入门(二)——JavaScript变量),使用Function构造函数的声明方法比较少见,我们暂时不提.function func() { ...

  4. 百度地图js版定位控件

    一 概述 百度地图在最新版已加入浏览器定位控件,个人认为应该是既高德地图更新了一个浏览器也能定位功能后,百度不甘落后自己简简单单,草草写了个这个功能的定位控件 GeolocationControl 这 ...

  5. MFC消息响应机制分析

    ---- 摘要: ---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析 ...

  6. 《深入理解mybatis原理》 MyBatis事务管理机制

    MyBatis作为Java语言的数据库框架,对数据库的事务管理是其很重要的一个方面.本文将讲述MyBatis的事务管理的实现机制. 首先介绍MyBatis的事务Transaction的接口设计以及其不 ...

  7. 设置 Linux 的 LD_LIBRARY_PATH 变量

    源地址:http://www.cnblogs.com/trying/archive/2013/06/07/3123577.html 我们在ubuntu图形界面下用eclipse写了一个动态库,到cen ...

  8. 文件比较,文件夹比较-- vimdiff,beyond compare, compare suite, WinMerge,Kdiff3

    文件比较,文件夹比较-- vimdiff,beyond compare, compare suite, WinMerge,Kdiff3  有一个项目的源码包需要比较,400M以上,这就要找个好的工具了 ...

  9. __NSAutoreleaseNoPool(): ... utoreleased with no pool in place - just leaking

    __NSAutoreleaseNoPool(): ... utoreleased with no pool in place - just leaking 我的平台 mac os 10.6 Xcode ...

  10. data URI scheme及其应用

    data URI scheme通俗的来讲就是将一张图片直接塞到HTML中而不是通过HTTP请求去获取.这样从表面上看会降低一次HTTP的请求,实现了对于网页的优化(只是看了其它一些文章data URI ...