当前位置: 首页 > news >正文

android表示机制,Android消息机制详解

前言

消息机制这种思想可以说是操作系统的基本机制之一,通过消息来驱动事件,满足交互的需求。

常见的操作系统如Windows、Android都有自己的消息机制的实现。

从Android的Handler谈起

通常我们做消息分发时,都是通过Handler帮我们实现,

//直接发送一个消息

mHandler.sendEmptyMessage(YOUR_MSG);

...

//发送一个待执行的Runnable

mHandler.post(new Runnable() {

public void run(){

//待执行的逻辑

}

});

复制代码

上述的这些方法都是一层封装,通过构造Message对象,最终都会走到,

...

public final boolean sendMessageDelayed(Message msg, long delayMillis){

if (delayMillis < 0) {

delayMillis = 0;

}

//将延迟的相对时间变成一个绝对的时间点

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

...

public boolean sendMessageAtTime(Message msg, long uptimeMillis){

//这里出现了我们经常提起的消息队列类MessageQueue

MessageQueue queue = mQueue;

if (queue == null) {

RuntimeException e = new RuntimeException(

this + " sendMessageAtTime() called with no mQueue");

Log.w("Looper", e.getMessage(), e);

return false;

}

//将构造好的Message对象入队

return enqueueMessage(queue, msg, uptimeMillis);

}

...

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis){

//Message的target成员变量是一个Handler(消息分发是通过判断它,进而知道要交给哪个handler去处理消息的)

//这里将当前handler赋值给它

msg.target = this;

//mAsynchronous在Handler构造函数中赋值,交给调用方去选择是否异步

if (mAsynchronous) {

msg.setAsynchronous(true);

}

//调用MessageQueue中的enqueueMessage入队,这个是核心的实现

return queue.enqueueMessage(msg, uptimeMillis);

}

复制代码

以上分析我们知道,Handler最终消息的入队是交由MessageQueue来实现的,我们来看一下具体源码,

//其实MessageQueue虽然是一个队列,但是是通过链表来实现的

boolean enqueueMessage(Message msg, long when){

...

synchronized (this) {

...

msg.markInUse();

msg.when = when;

Message p = mMessages;

boolean needWake;

//如果是新的队列头,直接插入队列

if (p == null || when == 0 || when < p.when) {

msg.next = p;

mMessages = msg;

//这个布尔值标志着是否需要唤醒事件队列,稍后将会讲到

needWake = mBlocked;

} else {

//按照未来执行的时间点when,插入链表中(这也是为什么用链表实现的原因)

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) {

//唤醒消息队列是native层的逻辑

nativeWake(mPtr);

}

}

return true;

}

复制代码

以上讲解了消息队列的入队的逻辑,我们来看看消息队列是怎么分发消息的。在Android中,是靠着Looper来实现从消息队列中取消息进而分发给对应的Handler的。Looper有两个重要的方法prepare()和loop(),

//通常我们直接调用prepare()方法时,quitAllowed默认是为true的

//quitAllowed最终会传递给MessageQueue的构造函数,通过这个布尔值来控制是否允许退出消息队列

//查看源码发现,如果prepareMainLooper(),即初始化主线程上的Looper,默认quitAllowed是为false的。

//这也符合常理,主线程不允许退出消息队列。如果强行执行MessageQueue.quit()将会抛出异常。

private static void prepare(boolean quitAllowed){

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

//初始化Looper并设置给ThreadLocal

sThreadLocal.set(new Looper(quitAllowed));

}

复制代码

在Java中,ThreadLocal为变量在每个线程都创建一个副本,每个线程都可以访问自己的内部副本变量。ThreadLocal 不是用来解决共享对象的多线程访问问题的,而是使用ThreadLocal使得各线程能够保持各自独立的一个对象。

public static void loop(){

...

for (;;) {

//可能会阻塞

Message msg = queue.next();

...

//调用Handler.dispatchMessage方法分发Message消息

msg.target.dispatchMessage(msg);

}

...

}

复制代码

Handler.dispatchMessage, 由源码可知,Handler对Runnable、Handler自定义的Callback和继承Handler分别做了回调函数的适配,对开发者而言,使用Handler变得更为灵活和方便,

public void dispatchMessage(Message msg){

if (msg.callback != null) {

//这里就是你传进来的runnable

handleCallback(msg);

} else {

//mCallback可以在Handler的构造函数中定义

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

//自带的handleMessage可以在继承Handler时实现

handleMessage(msg);

}

}

复制代码

MessageQueue.next中实现细节,

Message next(){

...

for (;;) {

...

//稍后会讲到,Linux系统的IO多路复用机制

nativePollOnce(ptr, nextPollTimeoutMillis);

...

//如果消息队列中有异步消息,则优先执行异步消息,然后再顺序执行剩余消息

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) {

// 下一条消息未到执行时间,赋值一个超时的时间

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

//将获取到Message移出队列,并返回

return msg;

}

} else {

// 没有Message了,赋值-1表示无限等待,直到下一条消息到来

nextPollTimeoutMillis = -1;

}

}

}

复制代码

在Message为空的时候,会去处理之前添加过的IdleHandler,因此我们可以利用IdleHandler实现一些功能

for (int i = 0; i < pendingIdleHandlerCount; i++) {

...

boolean keep = false;

try {

keep = idler.queueIdle();

} catch (Throwable t) {

...

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

public static interface IdleHandler{

//返回true将你的idle handler保留,false则从list中移除

boolean queueIdle();

}

复制代码

总结一下,Android的消息机制就是,通过不断地使用Handler将消息发送给一个消息队列入队,并且有一个无限循环的Looper,不断地从消息队列里取出消息然后分发给对应的目标Handler执行。

待思考的问题

既然Android主线程上的Looper是无限循环,为什么主线程没有被阻塞?

由之前的分析可知Looper.loop()方法中是一个for(;;)的无限循环,并且Android主线程会初始化一个对应的Looper。那么既然是死循环,为什么主线程没有卡死,或者说CPU的占用没有飙升。

首先看一下MessageQueue的构造函数,

MessageQueue(boolean quitAllowed) {

mQuitAllowed = quitAllowed;

mPtr = nativeInit();

}

复制代码

其中nativeInit调用了native层的实现,

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz){

NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();

...

nativeMessageQueue->incStrong(env);

//将指针nativeMessageQueue再解释为jlong类型,并返回

return reinterpret_cast(nativeMessageQueue);

}

复制代码

NativeMessageQueue的构造函数,

NativeMessageQueue::NativeMessageQueue() : ...省略{

mLooper = Looper::getForThread();

if (mLooper == NULL) {

//可见初始化了一个native的Looper

mLooper = new Looper(false);

Looper::setForThread(mLooper);

}

}

复制代码

我们进入native的Looper看看它的构造函数,

Looper::Looper(bool allowNonCallbacks) : ...省略 {

...

rebuildEpollLocked();

}

void Looper::rebuildEpollLocked() {

...

// 创建了epoll

mEpollFd = epoll_create(EPOLL_SIZE_HINT);

...

struct epoll_event eventItem;

memset( & eventItem, 0, sizeof(epoll_event));

//设置感兴趣的event进行监听

eventItem.events = EPOLLIN;

eventItem.data.fd = mWakeEventFd;

int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

...

}

复制代码

这里的epoll是linux系统上多路IO复用的机制。

Linux 的I/O多路复用模式 select、poll、epoll

select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。当调用epoll_wait()获取就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,之后再去epoll中的一个数组中取得相应的fd即可。避免了频繁的内存拷贝。

之后我们看一下MessageQueue的nativePollOnce的实现,

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {

...

//还是调用了native的Looper中的方法

mLooper->pollOnce(timeoutMillis);

...

}

int Looper::pollOnce(int timeoutMillis, int*outFd, int*outEvents, void**outData) {

int result = 0;

for (; ; ) {

...

//调用内部方法

result = pollInner(timeoutMillis);

}

}

//精简了很多方法,主要看流程

int Looper::pollInner(int timeoutMillis) {

...

struct epoll_event eventItems[EPOLL_MAX_EVENTS];

// 等待事件发生或者超时

int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

...

//循环遍历,处理所有的事件

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 {

}

} else {

ssize_t requestIndex = mRequests.indexOfKey(fd);

if (requestIndex >= 0) {

pushResponse(events, mRequests.valueAt(requestIndex));

} else {

}

}

}

Done:

int callbackResult = response.request.callback->handleEvent(fd, events, data);

if (callbackResult == 0) {

//移除fd

removeFd(fd, response.request.seq);

}

// Clear the callback reference in the response

response.request.callback.clear();

result = POLL_CALLBACK;

return result;

}

复制代码

这里基本上对native层消息模型有个大致的认识。native层通过system call的epoll机制,在无限循环情况下会进入阻塞休眠,等待时间唤醒,从而避免了阻塞主线程的情况发生。

总结一下,Android通过Java层与native层两层各自的消息机制实现了在主线程非阻塞的消息系统。

最后有一张图描述了两层各自的关系,

6828aa6d138d8e85802ab9501d263bb3.png

相关文章:

  • html中页面按百分比显示,HTML页面百分比自适应浏览器小结
  • android orientation框架,LinearLayout布局简单介绍
  • html页面c标签替换,HTML常用标签,什么是空标签和可替换标签
  • 响应式编程html,函数式响应式编程 - Functional Reactive Programming-Go语言中文社区
  • 纯html无限级目录树,利用Ajax实现无限级目录树(.NET)[转载]
  • 微型计算机自动化控制专业,你也能考国家电网之自动控制类专业!
  • 计算机与现代教育的英语作文,雅思写作大作文范文:电脑与现代教育
  • 五年级英语短文计算机,有关于计算机的英语短文章
  • h5怎么引入html,在html文件引入其它html文件的几种方法
  • html排行榜代码手机版,移动端前端代码优化丨排名VS体验
  • 计算机应用技术教程试题大一,大一计算机导论期末考试试题模拟试题及答案
  • 嵌入式计算机技术分类,嵌入式系统的分类及应用
  • 初中计算机课程图文混排教案,七年级信息技术教案:图文的混合编排
  • 天津城建大学计算机学院官网,天津城建大学计算机与信息工程学院研究生导师简介-杨振舰...
  • 计算机文化基础的改革,计算机学生论文,关于对计算机文化基础课程改革相关参考文献资料-免费论文范文...
  • 《Java编程思想》读书笔记-对象导论
  • Android 控件背景颜色处理
  • angular组件开发
  • ERLANG 网工修炼笔记 ---- UDP
  • es6--symbol
  • Logstash 参考指南(目录)
  • mysql_config not found
  • MySQL主从复制读写分离及奇怪的问题
  • Otto开发初探——微服务依赖管理新利器
  • Redis在Web项目中的应用与实践
  • Sass 快速入门教程
  • Vultr 教程目录
  • 不上全站https的网站你们就等着被恶心死吧
  • 回顾2016
  • 小程序测试方案初探
  •  一套莫尔斯电报听写、翻译系统
  • 整理一些计算机基础知识!
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (二)springcloud实战之config配置中心
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (论文阅读40-45)图像描述1
  • (万字长文)Spring的核心知识尽揽其中
  • (转)h264中avc和flv数据的解析
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .NET 反射的使用
  • .NET 中 GetProcess 相关方法的性能
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • /dev/sda2 is mounted; will not make a filesystem here!
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [ 数据结构 - C++] AVL树原理及实现
  • [2016.7 day.5] T2
  • [20190401]关于semtimedop函数调用.txt
  • [Android View] 可绘制形状 (Shape Xml)