线程间消息传递机制

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/os/Handler.java

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/os/MessageQueue.java

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/os/Looper.java

http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/os/Message.java

1.消息怎么发送的?

我们都知道当调用Handler发送消息的时候,不管是调用

sendMessage,sendEmptyMessage,sendMessageDelayed还是其他发送一系列方法。最终都会调用sendMessageDelayed(Message msg, long delayMillis)方法。

  public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
该方法会调用sendMessageAtTime()方法。其中第二个参数是执行消息的时间,是通过从开机到现在的毫秒数(手机睡眠的时间不包括在内)+ 延迟执行的时间。这里不使用System.currentTimeMillis() ,是因为该时间是可以修改的。会导致延迟消息等待时间不准确。该方法内部会调用sendMessageAtTime()方法,我们接着往下走。
   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);
}
这里获取到线程中的MessageQueue对象mQueue(在构造函数通过Looper获得的),并调用enqueueMessage()方法,enqueueMessage()方法最终内部会调用MessageQueue的enqueueMessage()方法,那现在我们就直接看MessageQueue中把消息加入消息队列中的方法。

消息的加入

当通过handler调用一系列方法如sendMessage()、sendMessageDelayed()方法时,最终会调用MessageQueue的enqueueMessage()方法。现在我们就来看看,消息是怎么加入MessageQueue(消息队列)中去的。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
} synchronized (this) {
//如果当前消息循环已经结束,直接退出
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
} msg.markInUse();
msg.when = when;
Message p = mMessages;//头部消息
boolean needWake;
//如果队列中没有消息,或者当前进入的消息比消息队列中的消息等待时间短,那么就放在消息队列的头部
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 {
//判断唤醒条件,当前当前消息队列头部消息是屏障消息,且当前插入的消息为异步消息
//且当前消息队列处于无消息可处理的状态
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;
prev.next = msg;
} //调用nativeWake,以触发nativePollOnce函数结束等待
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

这里大家肯定注意到了nativeWake()方法,这里先不对该方法进行详细的描述,下文会对其解释及介绍。 其实通过代码大家就应该发现了,在将消息加入到消息队列中时,已经将消息按照等待时间进行了排序。排序分为两种情况(这里图中的message.when是与当前的系统的时间差):

  • 第一种:如果队列中没有消息,或者当前进入的消息比消息队列中头部的消息等待时间短,那么就放在消息队列的头部

第二种:反之,循环遍历消息队列,把当前进入的消息放入合适的位置(比较等待时间)

综上,我们了解了在我们使用Handler发送消息时,当消息进入到MessageQueue(消息队列)中时,已经按照等待时间进行了排序,且其头部对应的消息是Loop即将取出的消息

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

和之前一样,也是通过long类型的ptr获取NativeMessageQueue对象的指针,再调用wake方法

void NativeMessageQueue::wake() {
mLooper->wake();
}

调用的也是Looper的方法:

void Looper::wake() {
uint64_t inc = ;
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));
}
}
}

重点是write(mWakeEventFd, &inc, sizeof(uint64_t)),写入了一个1,这个时候epoll就能监听到事件,也就被唤醒了

Looper的wake方法其实是使用write函数通过mWakeEventFd往管道写入字符inc,其中TEMP_FAILURE_RETRY 是一个宏定义, 当执行write方法失败后,会不断重复执行,直到执行成功为止,在nativeinit中,我们已经通过epoll_create方法监听了mWakeEventFd的可读事件,当mWakeEventFd可读时,epoll文件描述符就会监听到,这时epoll_wait方法就会从管道中读取事件返回,返回后就执行消息处理逻辑,所以这里的往管道写入字符inc,其实起到一个通知的作用,告诉监听的线程有消息插入了消息队列了,快点醒过来(因为进入了休眠状态)处理一下。
 

2.怎么样进行消息循环

我们都知道消息的取出,是通过Loooper.loop()方法,其中loop()方法内部会调用MessageQueue中的next()方法。

  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;
}
//...
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
//...
} finally {
//...
}
//... msg.recycleUnchecked();
}
}

从MessageQueue获取下一条消息

1.如果是null,退出

2.如果不为null,dispatchMessage

怎么处理消费的分发过程?

public void dispatchMessage(Message msg){
//检查message的callback是否为null
if(msg.calllback!=null){
handleCallback(msg);
}
else{
if(mCallback!=null){
if(mCallback.handleMessage(mssg)){//主意如果这个返回值是true 将不会执行下面的handlerMessage
return;
}
}
handleMessage(msg);
}
}
Handlet处理消息的过程
首先,检查Message的callback是否为null,不为空就通过handleCallback来处理消息。message的callback是一个Runnbale对象,实际上就是Handler的post方法所传递的Runnable参数。
private static void handleCallback(Message message){
message.callback.run();
}

其次,检查mCallback是否为空,不为null及调用mCallback的handleMessage方法来处理消息

public interface Callback{
public boolean handleMessage(Message msg);
}

MessageQueue的next方法

Message next() {
//...
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
} //执行native层消息机制层,
//timeOutMillis参数为超时等待时间。如果为-1,则表示无限等待,直到有事件发生为止。
//如果值为0,则无需等待立即返回。该方法可能会阻塞
nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) {
//获取系统开机到现在的时间,如果使用System.currentMillis()会有误差,
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());
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
//...
}
}
Message next() {

        //如果退出消息消息循环,那么就直接退出
final long ptr = mPtr;
if (ptr == 0) {
return null;
} int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
} //执行native层消息机制层,
//timeOutMillis参数为超时等待时间。如果为-1,则表示无限等待,直到有事件发生为止。
//如果值为0,则无需等待立即返回。该方法可能会阻塞
nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) {
//获取系统开机到现在的时间,如果使用System.currentMillis()会有误差,
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) {//判断当前消息时间,是不是比当前时间大,计算时间差
// 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 {
// 不需要等待时间或者等待时间已经到了,那么直接返回该消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
//没有更多的消息了
nextPollTimeoutMillis = -1;
} // Process the quit message now that all pending messages have been handled.
//判断是否已经退出了
if (mQuitting) {
dispose();
return null;
} // 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.
//获取空闲时处理任务的handler 用于发现线程何时阻塞等待更多消息的回调接口。
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//如果空闲时处理任务的handler个数为0,继续让线程阻塞
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
//判断当前空闲时处理任务的handler是否是为空
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} //只有第一次迭代的时候,才会执行下面代码
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(TAG, "IdleHandler threw exception", t);
}
//如果不保存空闲任务,执行完成后直接删除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
} // 重置空闲的handler个数,因为不需要重复执行
pendingIdleHandlerCount = 0; //当执行完空闲的handler的时候,新的native消息可能会进入,所以唤醒Native消息机制层
nextPollTimeoutMillis = 0;
}
}
MessageQueue的next()法肯定会很懵逼。妈的,这个nativePollOnce()方法是什么鬼,为毛它会阻塞呢?这个msg.isAsynchronous()判断又是怎么回事?妈的这个逻辑有点乱理解不了啊。大家不要慌,让我们带着这几个问题来慢慢分析

Native消息机制

其实在Android 消息处理机制中,不仅包括了Java层的消息机制处理,还包括了Native消息处理机制(与我们知道的Handler机制一样,也拥有Handler、Looper、MessageQueue)。这里我们不讲Native消息机制的具体代码细节,如果有兴趣的小伙伴,请查看----->深入理解Java Binder和MessageQueue

这里我们用一张图来表示Native消息与Jave层消息的关系(这里为大家提供了Android源码,大家可以按需下载),具体细节如下图所示:

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);
}

在nativePollOnce()方法中调用nativeMessageQueue的pollOnce()方法,我们接着走。

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;
}
}
这里我们发现NativeMessageQueue的pollOnce(timeoutMillis)内部
调用的是Native looper中的 pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)方法。继续看
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
// 先处理没有Callback方法的 Response事件
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) { //ident大于0,则表示没有callback, 因为POLL_CALLBACK = -2,
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
}
if (result != 0) {
if (outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = 0;
if (outData != NULL) *outData = NULL;
return result;
}
// 再处理内部轮询
result = pollInner(timeoutMillis);
}
}
这里就简单介绍一下pollOnce()方法。该方法会一直等待Native消息,其中 timeOutMillis参数为超时等待时间。
如果为-1,则表示无限等待,直到有事件发生为止。
如果值为0,则无需等待立即返回。
那么既然nativePollOnce()方法有可能阻塞,那么根据上文我们讨论的MessageQueue中的enqueueMessage中的nativeWake()方法。大家就应该了然了。nativeWake()方法就是唤醒Native消息机制不再等待消息而直接返回。

nativePollOnce()一直循环为毛不造成主线程的卡死?

到了这里,其实大家都会有个疑问,如果当前主线程的MessageQueue没有消息时,程序就会便阻塞在loop的queue.next()中的nativePollOnce()方法里,一直循环那么主线程为什么不卡死呢?这里就涉及到Linux pipe/epoll机制,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

Android 消息传递机制的更多相关文章

  1. Handler Looper源码解析(Android消息传递机制)

    Android的Handler类应该是常用到的,多用于线程间的通信,以及子线程发送消息通知UI线程刷新View等等.这里我主要总结下我对整个消息传递机制,包括Handler,Looper,Messag ...

  2. Android消息传递之Handler消息机制

    前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...

  3. Android学习之Handler消息传递机制

    Android只允许UI线程修改Activity里的UI组件.当Android程序第一次启动时,Android会同时启动一条主线程(Main Thread),主线程主要负责处理与UI相关的事件,如用户 ...

  4. Android异步消息传递机制源码分析

    1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.p ...

  5. (Android数据传递)Intent消息传递机制 “Intent”“数据传递”

    Intent类的继承关系:   需要注意的是,该类实现了Parcelable(用于数据传递)和Cloneable接口. Intent是一种(系统级别的)消息传递机制,可以在应用程序内使用,也可以在应用 ...

  6. Android学习笔记-事件处理之Handler消息传递机制

    内容摘要:Android Handler消息传递机制的学习总结.问题记录 Handler消息传递机制的目的: 1.实现线程间通信(如:Android平台只允许主线程(UI线程)修改Activity里的 ...

  7. Android消息传递之基于RxJava实现一个EventBus - RxBus

    前言: 上篇文章学习了Android事件总线管理开源框架EventBus,EventBus的出现大大降低了开发成本以及开发难度,今天我们就利用目前大红大紫的RxJava来实现一下类似EventBus事 ...

  8. Android消息传递之EventBus 3.0使用详解

    前言: 前面两篇不仅学习了子线程与UI主线程之间的通信方式,也学习了如何实现组件之间通信,基于前面的知识我们今天来分析一下EventBus是如何管理事件总线的,EventBus到底是不是最佳方案?学习 ...

  9. Android消息传递之组件间传递消息

    前言: 上篇学习总结了Android通过Handler消息机制实现了工作线程与UI线程之间的通信,今天来学习一下如何实现组件之间的通信.本文依然是为学习EventBus做铺垫,有对比才能进步,今天主要 ...

随机推荐

  1. php禁止个别ip访问网站

    PHP禁止个别IP访问自己的网站,可以看看下面的方法. function get_ip_data(){ $ip=file_get_contents("http://ip.taobao.com ...

  2. vsftpd 添加用户

    方法/步骤     首先要添加一个新的ftp用户并添加访问路径 useradd -d /alidata/www/ace ceshi        -d是用户的访问目录   为新添加的ftp用户设置密码 ...

  3. 人人商城返回Json格式的数据

    人人商城返回Json格式的数据 1.找到该插件对应的 core/mobile 路径 2.新建一个 api.php 文件 <?php header('Content-Type:applicatio ...

  4. inode,软硬链接

    如何查看inode ll -di /boot / /app查看文件和文件夹的inode号 df -i查看挂载点文件夹的inode号 做inode增长实验 创建60万个文件的方法1(效率不高):for ...

  5. excel匹配相应条件 自动填充数据

    =VLOOKUP(A6&B6,IF({1,0},Sheet3!$A$3:$A$505&Sheet3!$B$3:$C$505,Sheet3!$Q$3:$Q$505),2,0) =VLOO ...

  6. 29.连续子数组的最大和(python)

    题目描述 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决.但是,如果向量 ...

  7. Linux 开启相关端口及查看已开启端口

    防火墙层面:   /sbin/iptables -I INPUT -p tcp --dport 8011 -j ACCEPT #开启8011端口  /etc/rc.d/init.d/iptables ...

  8. 在百度ueditor上粘贴从word中copy的图片和文字 图片无法显示的问题

    我这边从world 里面复制粘贴图片到编辑器中,它自动给我上传了,但是我是用的第三方的要设置一个token值,我找了很久,也没有找到应该在哪里设置这个上传的参数,如果是点击图片上传,我知道在dialo ...

  9. UVA 10900 So do you want to be a 2^n-aire?

    #include<bits/stdc++.h> #include<stdio.h> #include<iostream> #include<cmath> ...

  10. AtCoder AGC022C Remainder Game (图论)

    题目链接 https://atcoder.jp/contests/agc022/tasks/agc022_c 题解 大水题一道 就他给的这个代价,猜都能猜到每个数只能用一次 仔细想想,我们肯定是按顺序 ...