欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~

作者:汪毅雄

导语: 本文讲述的是Android的消息机制原理,从Java到Native代码进行了梳理,并结合其中使用到的Epoll模型予以介绍。

Android的消息传递,是系统的核心功能,对于如何使用相信大家都已经相当熟悉了,这里简单提一句。我们可以粗糙的认为消息机制中关键的几个类的功能如下:

Handler:消息处理者

Looper:消息调度者

MessageQueue:存放消息的地方

使用过程:

Looper.prepare > #$%^^& > Looper.loop(死循环) --- loop到一个消息 > Handler处理

好了,我们直接看源码吧。

Java层

消息机制是伴随线程的,也就是说上面的几个类在可以在任何一个线程中都有实例的。

先看Looper吧。以主线程为例,Android进程在初始化,会调用prepareMainLooper

public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {...sMainLooper = myLooper();}}复制代码

 private static void prepare(boolean quitAllowed) {...sThreadLocal.set(new Looper(quitAllowed));}复制代码

 private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}复制代码

以上几个方法就是Looper初始化,如果是主线程Looper会创建一个不可退出的MessageQueue,并把looper实例放入线程独立(ThreadLocal)变量中。

Looper#loop

public static void loop() {final Looper me = myLooper();final MessageQueue queue = me.mQueue;for (;;) {Message msg = queue.next(); if (msg == null) {return;}...try {msg.target.dispatchMessage(msg);} ...msg.recycleUnchecked();}}复制代码

Looper prepare后就可以loop了,loop非常简单,一直去queue中拿消息就好了,拿到了交给target也就是Handler处理。大家有可能会奇怪这种死循环,执行起来不会太sb粗暴了吗?其实这个解决方式在queue.next!!!后面再讲。

Handler#dispatchMessage

public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}复制代码

handler收到后,如果发现message的callback不为空,则只处理callback。(提一句,我们用的很多的handler.post(Runnable),其实这个Runnable就是这里的callback,也就是说post的Runnable实质上是一个优先级很高的Message),如果没有则尝试交给handler本身的callback处理(handler初始化的时候可以用callback方式构造),再没有才到我们常用的handleMessage方法,这里就是我们经常重写的方法。

再说说消息的发送,一般handler会调用sendMessage方法,但是最终这个方法还是会跑到这里

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

再交给MessageQueue

boolean enqueueMessage(Message msg, long when) {。。。synchronized (this) {。。。Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}。。。}msg.next = p; // invariant: p == prev.nextprev.next = msg;if (needWake){nativeWake(mPtr);}}return true;}复制代码

MessageQueue会把消息插入队列,并依次改变队列中各个消息的指针。

咦,好像只用Java层貌似就能把整个消息机制说通了,native代码在哪儿?有何用呢?

但是,刚才提到了Looper初始化的时候也会新建一个MessageQueue

MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();}复制代码

好了,我们第一个native方法出来了。这时候我们可以猜得到,MessageQueue才是整个消息机制的核心!

Native层

接上面Java层的代码,MessageQueue构造的时候会调一个nativeInit。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();。。。
}复制代码

NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {mLooper = Looper::getForThread();if (mLooper == NULL) {mLooper = new Looper(false);Looper::setForThread(mLooper);}
}复制代码

native层调用init方法后,会在native层构建一个native Looper!来看看native Looper的初始化

Looper::Looper(bool allowNonCallbacks) :mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);...rebuildEpollLocked();
}复制代码

这里创建了一个eventfd,代码来自最新的8.0,这部分和5.0 pipe管道的mWakeReadPipeFd和mWakeWritePipeFd稍微有点不一样,前者是等待/响应,后者是读取/写入。只是android选取方式的不同而已,这块就不细说。

void Looper::rebuildEpollLocked() {。。。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)); eventItem.events = EPOLLIN;eventItem.data.fd = mWakeEventFd;int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);。。。
}复制代码

再到rebuildEpollLocked这个方法中,可以看到通过epoll_create创建了一个epoll专用的文件描述符,EPOLL_SIZE_HINT表示mEpollFd上能监控的最大文件描述符数。最后调用epoll_ctl监控mWakeEventFd文件描述符的Epoll事件,即当mWakeEventFd中有内容可读时,就唤醒当前正在等待的线程.。

这里不了解的人可能听着晕,上面这么一大段一句话概括就是:Android native层用了Epoll模型。什么是Epoll模型呢?我先简单介绍一下。

Epoll(必看!!!)

为什么要引入呢?

在Looper.loop的时候提到了,android不会简单粗暴地真的执行啥都没干的死循环。刚才说了,问题出在queue.next。Epoll干的事就是: 如果你的queue中没有消息可执行了,好了你可以歇着了,等有消息的我再告诉你。这个queue.next就是“阻塞”(休眠)在这里。

Epoll简单介绍

1、传统的阻塞型I/O(一边写,一边读),一个线程只能处理一个一个IO流。

2、如果一个线程想要处理多个流,可以采用了非阻塞、轮询I/O方式,但是传统的非阻塞处理多个流的时候,会遍历所有流,但是如果所有流都没数据,就会白白浪费CPU。
。。。
于是出现了select和epoll两种常见的代理方式。

3、select就是那种无差别轮询的代理方式。epoll可以理解为Event poll,也就是说代理者会代理流的时候也伴随着事件,因此有了对应事件,就可以避免无差别轮询了。

4、其通常的操作有:epoll_create(创建一个epoll)、epoll_ctl(往epoll中增加/删除某一个流的某一个事件)、epoll_wait(在一定时间内等待事件的发生)

eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);复制代码

好了,我们结合Looper初始化的代码来读一下epoll在这里干了什么吧。

我直接翻译了:往mEpollFd代理中、注册、一个叫mWakeEventFd流、的数据流入事件(EPOLLIN)

这样大家应该懂了吧。。。

接上MessageQueue在初始化后,在native创建了一个Looper。
我们继续消息的发送和提取在native层的表现。其实native层主要负责的是消息的调度,比如说何时阻塞、何时唤醒线程,避免CPU浪费。

native发送

发送在native比较简单,handler发送消息后,会到MessageQueue的enqueueMessage,此时在线程阻塞的情况下,会调用nativeWake来唤起线程。

void NativeMessageQueue::wake() {mLooper->wake();
}复制代码

 void Looper::wake() {。。。uint64_t inc = 1;ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));if (nWrite != sizeof(uint64_t)) {。。。。}
}复制代码

这里TEMP_FAILURE_RETRY是一个宏定义,顾名思义,就是不断地尝试往mWakeEventFd流里面写一个无用数据直到成功,以此来唤醒queue.next。这部分就不多说了。

native消息提取

也就是queue.next

Message next() {。。。for (;;) {。。。nativePollOnce(ptr, nextPollTimeoutMillis);。。。}}复制代码

可以看到,又是一个死循环(阻塞)。继续往下看

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {。。。mLooper->pollOnce(timeoutMillis);。。。
}复制代码

mLooper->pollOnce

mLooper->pollInner

int Looper::pollInner(int timeoutMillis) {。。。int result = POLL_WAKE;mPolling = true;struct epoll_event eventItems[EPOLL_MAX_EVENTS];int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);mPolling = false;mLock.lock();for (int i = 0; i < eventCount; i++) {int fd = eventItems[i].data.fd;uint32_t epollEvents = eventItems[i].events;if (fd == mWakeEventFd) {if (epollEvents & EPOLLIN) {awoken();} else {ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);}} 。。。}。。。mLock.unlock();。。。return result;
}复制代码

在这里,我们注意到epoll_wait方法,这里会得到一段时间内(结合消息计算得来的)收到的事件个数,这里对于queue来说就是空闲(阻塞)状态。过了这个时间后,看看事件数,如果为0,则意味着超时。否则,遍历所有的事件,看看有没有mWakeEventFd,且是EPOLLIN事件的,有的话就真正唤醒线程、解除空闲状态。

消息机制在native层的主要表现就是这些。

最后,画了一个粗糙、且不太准确图仅供参考学习

相关阅读

Android View和 Window 的关系

Android 7.0 中 ContentProvider 实现原理

云服务器20元/月起,更享千元续费大礼包

此文已由作者授权腾讯云技术社区发布,转载请注明原文出处

深入理解 Android 消息机制原理相关推荐

  1. 理解 Android 消息机制

    本人只是Android小菜一个,写技术文章只是为了总结自己最近学习到的知识,从来不敢为人师,如果里面有不正确的地方请大家尽情指出,谢谢! 本文基于原生 Android 9.0 源码来解析 Androi ...

  2. 安卓php推送消息机制,深入剖析Android消息机制原理

    在Android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易.更好地架构系统,避免一些低级的错误.在学习Android中消息机制之前,我 ...

  3. 安卓开发必须会的技能!浅谈Android消息机制原理,威力加强版

    目录 想要成为一名优秀的Android开发,你需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样. PagerAdapter 介绍 ViwePager 缓存策略 ViewPager 布局处 ...

  4. 三级缓存和二级缓存的区别,浅谈Android消息机制原理,面试心得体会

    写在前面 为什么只看这一篇就够了? 现在CSDN.知乎.掘金上各路大佬层出不穷,他们身经百战.血洗杀场,总结出满满的求职干货.但同时也存在很多非良心的博主,要么活出了人类的本质,复读机一样到处转载:要 ...

  5. 理解Android Binder机制原理

    原文地址: http://blog.csdn.net/universus/article/details/6211589 Binder是Android系统进程间通信(IPC)方式之一.Linux已经拥 ...

  6. 【微信小程序】浅谈Android消息机制原理,大厂面经合集

    开头 最近很多网友反馈:自己从各处弄来的资料,过于杂乱.零散.碎片化,看得时候觉得挺有用的,但过个半天,啥都记不起来了.其实,这就是缺少系统化学习的后果. 为了提高大家的学习效率,帮大家能快速掌握An ...

  7. android handler的机制和原理_一文搞懂handler:彻底明白Android消息机制的原理及源码

    提起Android消息机制,想必都不陌生.其中包含三个部分:Handler,MessageQueue以及Looper,三者共同协作,完成消息机制的运行.本篇文章将由浅入深解析Android消息机制的运 ...

  8. Android消息机制基本原理和使用

    在Android开发过程中,我们常常遇到子线程更新UI的需求,例如在子线程进行耗时较长的下载,等下载完成之后,再去更新UI,提示用户下载完成,直接在子线程里更新UI,会得到报错提示:Only the ...

  9. android r.java 原理,深入理解Android消息处理系统原理

    Android应用程序也是消息驱动的,按道理来说也应该提供消息循环机制.实际上谷歌参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制. Android通过Looper.Ha ...

  10. Android:Android消息机制整理

    文章目录 前言 一.Android消息机制的构成 二.为什么只允许主线程对UI进行更新操作 三.消息机制具体分析 ThreadLocal原理分析 MessageQueue Looper Handler ...

最新文章

  1. linux 检测添加磁盘空间,Linux构造磁盘空间满的测试环境
  2. 机器学习-线性回归与梯度下降
  3. Qt 常量中有换行符 中文
  4. 如何修改 SAP Spartacus CMS API 默认的 endpoint
  5. Sublime Text3(mac)一些插件和快捷键
  6. 40款精简活力fcpx标题字幕 mTitle Kinetic for Mac
  7. 麦肯锡70页特辑报告论述《人工智能的未来之路》(完整版PPT)
  8. html商城加减号,商城购物车的加减号控制商品数量
  9. mongodb 插入一个数组 java_mongodb:推送到数组元素的子数组或添加到数组(如果不存在)...
  10. matlab机器学习库
  11. Unity Animator人物模型动画移动偏移
  12. cpolar——安全的内网穿透工具
  13. js制作动态图片时钟
  14. Leetcode刷题——题目8、9、10
  15. 语法糖 Syntactic sugar: 复杂代码的等价简洁替代
  16. 一切照旧... ...
  17. linux下非root用户如何修改root权限的文件
  18. Antimalware Service Executable占用内存过高怎么办
  19. 嘉兴经开区第四届创新创业大赛总决赛成功举办
  20. Docker查看容器的IP地址

热门文章

  1. 华为鸿蒙系统首发设备,鸿蒙首发设备包装曝光:安卓已成过去式,鸿蒙正式走上舞台...
  2. c语言数组指针题库,C语言 数组指针练习题.doc
  3. Windows版JMeter下载安装
  4. 如何写出优质干净的代码,这6个技巧你不能错过
  5. $.ajax()常用属性
  6. 编程基本功:正常运行的代码,你看明白能做什么?不如解决几个简单BUG
  7. 编程基本功:写一个JAVA版的对象回收利用机制
  8. 错误: 找不到或无法加载主类 org.apache.tools.ant.launch.Launcher
  9. 多重继承有时候确实有必要
  10. 想起一则急着争权的故事