我们在写Android程序的时候,有经常用到Handler来与子线程通信,亦或者是用其来管理程序运行的状态时序。Handler其是由Android提供的一套完善的操作消息队列的API。它既可以运行在主线程中,也可以运行在子线程中,唯一的区别是其内部的Looper对象不同。在这里我将对Android中Handler消息队列的实现进行一个总结,以便深入了解其原理并且巩固其使用方式。

本系列的主要内容如下:

  • 1.整体架构与主要的数据结构
  • 2.Message的入列出列以及延迟处理的实现方式
  • 3.底层原理:epoll的基础概念以及在Handler中的应用

整体架构

实现Handler消息队列的源码如下:

framework/base/core/java/android/os/Handler.java
framework/base/core/java/android/os/Looper.java
framework/base/core/java/android/os/Message.java
framework/base/core/java/android/os/MessageQueue.java
framework/base/core/jni/android_os_MessageQueue.cpp
system/core/libutils/Looper.cpp
system/core/include/utils/Looper.h

Handler: 主要提供各类API用于将Message对象加入到队列中,并在Message从队列中被取出时,触发handleMessage回调或者执行Message中的Runable对象的run函数。

Looper: java层的Looper类是消息队列的工作引擎,提供一个死循环不断从MessageQueue中取出Message, 取出的Message会在Handler的handleMessage中处理。

Message: 消息队列中所传递的消息的数据类型,其父类是Parcelable。内部还包含了一个Message对象池的实现,用来复用Message对象;

MessageQueue:消息队列,主要实现Message的入列,出列的逻辑管理,维护Message单链表,队列的清空,以及与JNI部分的通信。

android_os_MessageQueue: 消息队列的JNI部分实现,内部的主要逻辑就是初始化native的Looper对象,以及在java部分调用JNI中的方法时,执行native Looper部分对应的方法。

native Looper: native层的Looper中实现了两个功能,一个是利用epoll实现了队列的阻塞与唤醒功能,另外一个是实现了一套native层中使用的队列机制。

其分层架构如下所示:Handler, Message,Looper三者围绕MessageQueue进行处理,MessageQueue通过jni与Looper.cpp进行通信实现空队列时阻塞,有消息入列时唤醒等功能。


整体架构如下:MessageQueue中维护着一个Message的单链表,Handler中enqueueMessage将消息添加到队列中,Looper.java从队列中取出Message并将其交由Handler的dispatchMessage分发。android_os_MessageQueue.cpp主要是jni的实现部分,完成MessageQueue跟Looper.cpp的交互。 Looper.cpp则使用epoll机制实现了队列的阻塞与唤醒。

数据结构

Android的队列所管理的数据类型为Message, 消息队列所用的数据结构则是Message类型的单链表。一般模式下其结构如下所示:

在Looper中,依次从Message 0 到 Message n中将数据取出丢给Handler处理。当添加新的数据时,如果Message对象的when属性为0,则将其添加到链头。这种情况一般是我们调用Handler的sendMessageAtFrontOfQueue时出现。

在其他情况下,每一次添加新的消息时,都需要从链头依次对比Message的when变量,当新消息的when变量小于链中元素的when变量,则将其插入链中。一般调用sendEmptyMessageAtTimesendEmptyMessageDelayed sendMessageDelayedsendMessageAtTime等函数时,会出现延迟处理的消息。如果队列中存在延迟消息,那么使用sendMessage等及时处理的消息时,会出现其插入在链表中的情况。

以上总结了队列的数据结构是一个单链表,因此我们上述所有疑问,都可以转换成如何操作单链表的问题。正常情况下的单链表如下所示:

那Message究竟是怎么加入到队列中,又是怎么从队列中取出最终到handlerMessage中为我们所得, 为什么队列可以将数据优先处理,为什么有的数据可以延迟处理,其具体实现队列管理的算法是怎么实现的。

首先看一下,Handler是如何从队列中取值的,具体实现在MessageQueue中的next()函数

//获取当前时间戳
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//mMessages是一个全局的Message类型变量,保存着链头的Message, 如果队列没有数据,则该变量指向空
Message msg = mMessages;
if (msg != null && msg.target == null) {//无主的Message,不处理,直接寻找下一个节点的Message.// 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) {//当前时间还没达到队列中第一个Message的消息处理时间,需要继续等待。// 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;//返回当前链头的Message,并将mMessages对象指向其内部名为next的Message类型的对象。//这里就相当于取出了队列的头部数据。if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);//标识当前Message正在使用中msg.markInUse();//返回Message对象。return msg;}
} else {// No more messages.nextPollTimeoutMillis = -1;
}

这部分仅仅列出取值的具体算法,其他包括空队列的IdleHandler的处理,以及阻塞的逻辑的部分这里我并没有贴出来。

Looper调用MessageQueue对象的next()方法获取到从队列中返回的Message对象后,再将其交由Handler去处理,也就是走到我们经常用到的handleMessage(Message msg)回调中,这部分的逻辑如下:

Message msg = queue.next(); // might block
if (msg == null) {// No message indicates that the message queue is quitting.return;}......
......try {msg.target.dispatchMessage(msg);
} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}
}

每一个Message对象都有一个target变量,这个target变量类型就是Handler, 每当Message取出时,都提交到其自身归属的Handler去处理。

每次取消息的时序图如上所示,MessageQueue由Looper初始化,初始化之后,调用其loop方法开始让队列开始工作。loop方法中,开始循环调用MessageQueue的next()函数去拿Message数据,next()具有阻塞特性,当队列没有消息时,nativePollOnce()函数处会阻塞,原理是底层使用epoll实现了消息的阻塞/唤醒机制。当队列中有新加入的数据时,epoll_wait就会退出,从而MessageQueue的next()方法将Message返回给Looper,并提交到Handler中消化。

知道了Handler的消息取出的流程,接下来看一下将消息加入队列的逻辑。Handler一共为我们提供了如下公开的API用来将消息添加到队列中

//发送一个空消息public final boolean sendEmptyMessage(int what)//发送一个延迟处理的空消息public final boolean sendEmptyMessageDelayed(int what, long delayMillis)//发送一个指定时间戳执行的空消息public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)//发送一个延迟消息public final boolean sendMessageDelayed(Message msg, long delayMillis)//发送一个定时消息public boolean sendMessageAtTime(Message msg, long uptimeMillis)//将消息发送至队列的头public final boolean sendMessageAtFrontOfQueue(Message msg)//提交一个任务public final boolean post(Runnable r)//提交一个定时任务public final boolean postAtTime(Runnable r, long uptimeMillis)//提交一个带身份认证的定时任务public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)//提交一个延迟任务public final boolean postDelayed(Runnable r, long delayMillis)//提交一个任务到队列头public final boolean postAtFrontOfQueue(Runnable r)

以上方法,最终均会调用Handler的私有方法将Message提交到队列中,uptimeMillis就是该msg最终执行的时间戳,也用来进行链中元素的排序。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

接下来看一下消息插入的具体实现,MessageQueue中的enqueueMessage方法。

boolean enqueueMessage(Message msg, long when) {......//标记当前Message正在被使用msg.markInUse();//将Message的when变量赋值为执行时间戳msg.when = when;//mMessages为链头,如果此时队列无数据,则mMessages为空Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {//这里判断有三种情况,一种是链头为空表示当前队列无数据,一种是当前提交的Message执行时间戳为0,表示使用者//希望将其加入到队列头,还有一种情况是当前提交的Message执行时间戳小于链头Message的执行时间戳,因此也需    //要将其加入到队列头                // New head, wake up the event queue if blocked.//加入队列头只需要将自身的next变量引用到当前的链头对象,然后再将代表当前链头的mMessages变量引用到msgmsg.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的插入位置。Message的插入位置//是根据其内部的when变量来判断,当链表中的元素when属性值大于新元素的时间戳值,//则将元素加入到该元素的前一个节点。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.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.//队列为空时,此时epoll_wait处于等待状态,我们需要将其唤醒,然后loop可以继续从队列中取出数据,分发数据。if (needWake) {nativeWake(mPtr);}}return true;}

运行时序如下所示,该图描述了一个Message数据加入空队列,到数据被取出消化的流程。

总结:Android消息队列中的Message入列和出列,都是基于单链表来实现,其队列排序的核心变量就是Message内部的when变量,when变量是一个时间戳,由Handler给该变量赋值,延迟消息,定时消息,都是根据when变量来实现。 在这里分析队列的逻辑时,发现了跟jni部分的通信,主要是nativePollOnce,nativeWake方法,这两个方法实际是实现了空队列阻塞,以及唤醒的功能,底层使用epoll机制实现。

上述分析中讲了Handler的消息入列与出列的具体实现时,其中有碰到MessageQueue中使用了两个jni的函数,nativePollOnce和nativeWake,nativePollOnce的作用一个是保证了Looper循环在消息队列中没有数据或者链头的Message的when变量大于当前时间戳时能够阻塞,从而减少CPU的资源使用率,nativeWake的作用则是在Looper循环被阻塞的时候,当有新的消息加入到队列中执行时,能够及时唤醒阻塞的循环,保证消息能够及时处理。那这两个函数是如何实现阻塞/唤醒的呢? 首先跟踪两者的本地函数定义在MessageQueue中:

 private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/private native static void nativeWake(long ptr);

对应的JNI函数在android_os_MessageQueue.cpp中,如下:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,jlong ptr, jint timeoutMillis) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);nativeMessageQueue->wake();
}

jni函数中并没有做处理,只是调用NativeMessageQueue的方法,继续跟踪两个函数在NativeMessageQueue的执行如下:

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {mPollEnv = env;mPollObj = pollObj;mLooper->pollOnce(timeoutMillis);mPollObj = NULL;mPollEnv = NULL;if (mExceptionObj) {env->Throw(mExceptionObj);env->DeleteLocalRef(mExceptionObj);mExceptionObj = NULL;}
}void NativeMessageQueue::wake() {mLooper->wake();
}

发现最终的实现都是在Looper.cpp中,在分析Looper.cpp的源码时,有必要了解一下epoll的基础概念,以及使用方式,参考这篇文章 。
native Looper对象的初始化,在android_os_Message.cpp文件的NativeMessageQueue构造函数中完成

NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {//从当前线程的私有域中查看是否有已经创建的LoopermLooper = Looper::getForThread();if (mLooper == NULL) {//如果没有已经存在的Looper,则创建新的mLooper = new Looper(false);Looper::setForThread(mLooper);}
}

继续跟踪Looper的构造函数

Looper::Looper(bool allowNonCallbacks) :mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {//eventfd 是 Linux 的一个系统调用,创建一个文件描述符用于事件通知//具体参数介绍以及使用方式,请看[这里](http://man7.org/linux/man-pages/man2/eventfd2.2.html)mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s", strerror(errno));AutoMutex _l(mLock);rebuildEpollLocked();
}void Looper::rebuildEpollLocked() {// Close old epoll instance if we have one.if (mEpollFd >= 0) {
#if DEBUG_CALLBACKSALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endifclose(mEpollFd);}// Allocate the new epoll instance and register the wake pipe.//mEpollFd是epoll创建的文件描述符mEpollFd = epoll_create(EPOLL_SIZE_HINT);LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));struct epoll_event eventItem;memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union//标记mWakeEventFd对read操作有效eventItem.events = EPOLLIN;eventItem.data.fd = mWakeEventFd;//epoll_ctl执行EPOLL_CTL_ADD参数的操作的意思是将mWakeEventFd加入到监听链表中,当有read操作时,唤醒mWakeEventFd的wait等待。int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",strerror(errno));............

Looper的构造函数中,通过epoll_create获得了一个epoll的文件描述符,再通过epoll_ctl将mWakeEventFd添加到epoll中监听。从上面个跟踪的流程得知,我们调用nativePollOnce()函数,最终执行的地方是在Looper的pollOnce中,代码如下:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0;for (;;) {............if (result != 0) {
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - returning result %d", this, result);
#endifif (outFd != NULL) *outFd = 0;if (outEvents != NULL) *outEvents = 0;if (outData != NULL) *outData = NULL;return result;}result = pollInner(timeoutMillis);}
}int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif// Adjust the timeout based on when the next message is due.if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);if (messageTimeoutMillis >= 0&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {timeoutMillis = messageTimeoutMillis;}
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d",this, mNextMessageUptime - now, timeoutMillis);
#endif}// Poll.int result = POLL_WAKE;mResponses.clear();mResponseIndex = 0;// We are about to idle.mPolling = true;//开始等待事件的触发, timeoutMillis是在该时间内,如果没有获取到事件,则自动返回,为-1则一直等待到有事件过来,为0则不管有没有事件,都    //立即返回。 Handler在设计的时候,首先会查询一次队列,如果没有数据,则立即返回,然后重新pollOnce走到这里等待新的数据过来, 往    // mWakeEventFd写数据,才会唤醒返回。struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);// No longer idling.mPolling = false;// Acquire lock.mLock.lock();// Rebuild epoll set if needed.if (mEpollRebuildRequired) {mEpollRebuildRequired = false;rebuildEpollLocked();goto Done;}//请求出错// Check for poll error.if (eventCount < 0) {if (errno == EINTR) {goto Done;}ALOGW("Poll failed with an unexpected error: %s", strerror(errno));result = POLL_ERROR;goto Done;}//请求超时// Check for poll timeout.if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - timeout", this);
#endifresult = POLL_TIMEOUT;goto Done;}// Handle all events.
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif
......
......

在队列消息为空的情况下,那么我们就会阻塞在poll_inner的epoll_wait处,如果有新消息加入队列,则上层会调用nativeWake,最终对应Looper中的wake函数将epoll_wait唤醒。


void Looper::wake() {
#if DEBUG_POLL_AND_WAKEALOGD("%p ~ wake", this);
#endifuint64_t inc = 1;//往mWakeEventFd中写数据, mWakeEventFd监听到有事件触发,则使epoll_wait返回。ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));if (nWrite != sizeof(uint64_t)) {if (errno != EAGAIN) {ALOGW("Could not write wake signal: %s", strerror(errno));}}
}

至此,Looper.cpp中实现队列的阻塞唤醒的功能已经完成,总结一下:

  1. Looper.cpp对象是在android_os_MessageQueue的nativeInit函数被调用时初始化
  2. Looper.cpp的构造函数中,使用eventfd函数创建了一个mWakeEventFd的文件描述符用于事件通知,并使用epoll_create函数创建了一个epoll的文件描述符。然后调用epoll_ctl将mWakeEventFd加入到了事件监听链中。
  3. 初始化的队列为空,请求数据的时候,首先会走到pollOnce中,传入的timeoutMillis值为0,则epoll_wait会立即返回,然后MessageQueueh会进行timeoutMills = -1的第二次pollOnce, 这时候就阻塞在epoll_wait函数处。
  4. 当有新数据加入队列时,调用wake函数往mWakeEventFd中写数据, 此时触发epoll_wait阻塞中断,pollOnce返回,上层MessageQueue中的next()函数也将执行完毕,并将Message丢给Handler处理

Android Handler消息队列的实现原理相关推荐

  1. android的消息队列机制

    android下的线程,Looper线程,MessageQueue,Handler,Message等之间的关系,以及Message的send/post及Message dispatch的过程. Loo ...

  2. 消息队列和ZeroMQ原理和应用

    一.定义 消息队列(message queue)本质就是个队列,先进先出FIFO. 利用FIFO先进先出的特性,可以保证消息的顺序性. 主要用途:不同服务server.进程process.线程thre ...

  3. 【Android 异步操作】手写 Handler ( 消息队列 MessageQueue | 消息保存到链表 | 从链表中获取消息 )

    文章目录 一.MessageQueue 消息队列存储消息 二.MessageQueue 消息队列取出消息 三.消息队列完整代码 一.MessageQueue 消息队列存储消息 Message 链表 : ...

  4. Android进阶知识树——Android Handler消息机制

    1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...

  5. Android Handler消息机制源码解析

    好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...

  6. 【安卓学习笔记】Android Handler 消息机制探究

    一.概述 1.android消息机制的含义: Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper ...

  7. [Android] android的消息队列机制

    2019独角兽企业重金招聘Python工程师标准>>> android下的线程,Looper线程,MessageQueue,Handler,Message等之间的关系,以及Messa ...

  8. Android Handler消息机制源码分析

    一,前言 众多周知, Android 只允许在主线程中更新UI,因此主线程也称为UI线程(ActivityThread). 如此设计原因有二: (1) 由于UI操作的方法都不是线程安全的,如果多个线程 ...

  9. Android Handler消息机制不完全解析

    1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...

最新文章

  1. 任务调度器leetcode621
  2. android_通过高级应用程序开发策略在Android中进行用户参与
  3. 无聊,写写工作日记吧.
  4. 标书中如何正确描述所用的统计学方法
  5. 五十、简单的斗鱼分析案例
  6. c51单片机有几个终端语言,吃过大亏,才知道要从51单片机入手
  7. 1984. 学生分数的最小差值
  8. Guitar Por如何演奏刮弦
  9. Attachments to close incidents
  10. 如何做优化,UITabelView才能更加顺滑
  11. 使用thinkPhp,修改线上数据库的配置,请删除 Runtime 中的所有内容后重试
  12. spark访问不存在文件,或者空文件
  13. 开启关闭Centos的自动更新
  14. Office Scan(OSCE)10.0客户端手动卸载
  15. autocad2014点击保存闪退_autocad2014启动闪退 AutoCAD启动时闪退怎么办
  16. 修复win7本地服务器,win7开启本地服务器配置
  17. 利用Python实现FGO自动战斗脚本,再也不用爆肝啦~
  18. 如何实现电脑远程操控西门子触摸屏画面
  19. ipad pro 文章
  20. 苹果退款_苹果充值退款什么意思

热门文章

  1. mac u盘文件过大 拷贝不进去_告诉你Mac 为什么不能拷贝文件到U盘
  2. matlab 画海面图,大海怎么画?波光粼粼的的海面画法是什么?
  3. transformer做文本分类的keras实现完整版
  4. php 表添加字段sql语句,sql动态添加字段实例解析
  5. VBA之满足指定区域的指定条件后提取数据
  6. 优秀架构师必须掌握的架构思维 1
  7. 腐烂国度计算机配置要求,《腐烂国度2》PC配置公布 目前只支持Win10无中文!
  8. Omnibox之Chrome关闭地址栏 输入提示
  9. uWISG/uwisg/wisg
  10. Win10任务栏不显示蓝牙图标 - 解决方案