热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

事件驱动模型Handler与Flutterfuture

业界相关资讯4月11日华为在P30的发布会上,华为消费者终端业务CEO余承东公布了方舟编译器,并宣布开源,称可提升app性能。表示开发者将开发好的APK用该编译器编译一下,即可大大

业界相关资讯

4月11日华为在P30的发布会上,华为消费者终端业务CEO余承东公布了方舟编译器,并宣布开源,称可提升app性能。表示开发者将开发好的APK用该编译器编译一下,即可大大提升App性能。从图中可以看出原理和Android系统的Ahead of Time与Just in Time类似。有网友猜想apk通过编译器会编译成机器码。让我们拭目以待吧。

《事件驱动模型 Handler 与 Flutter future》
《事件驱动模型 Handler 与 Flutter future》

事件驱动

《事件驱动模型 Handler 与 Flutter future》
《事件驱动模型 Handler 与 Flutter future》

以操作系统为例,我们每次的鼠标点击,键盘按下都会发出一个事件,然后加入操作系统的消息队列中,处理线程提取任务然后分发给对应的处理句柄去处理消息事件。上述的流程即可理解为事件驱动。下面是百度百科的解释

早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足,这是很浪费cpu时间的。而事件驱动的程序,则有机会释放cpu从而进入睡眠态(注意是有机会,当然程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu。

一个典型的事件驱动的程序,就是一个死循环,并以一个线程的形式存在,这个死循环包括两个部分,第一个部分是按照一定的条件接收并选择一个要处理的事件,第二个部分就是事件的处理过程。程序的执行过程就是选择事件和处理事件,而当没有任何事件触发时,程序会因查询事件队列失败而进入睡眠状态,从而释放cpu。

事件驱动的程序,必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件

事件驱动的程序,还有一个最大的好处,就是可以按照一定的顺序处理队列中的事件,而这个顺序则是由事件的触发顺序决定的,这一特性往往被用于保证某些过程的原子化。

目前windows,linux等都是事件驱动的,只有一些单片机可能是非事件驱动的。

事件驱动模型 Handler

《事件驱动模型 Handler 与 Flutter future》
《事件驱动模型 Handler 与 Flutter future》

无论在Android开发还是Android面试中经常使用和被问到的就是handler,根据上述事件驱动的描述来看handler本质就是事件驱动模型。在Android系统中每次点击事件、activity与service的启动,生命周期的执行、view的布局事件等,Android系统均会把上述事件转化成一个消息msg,放在消息队列MessageQueen中。由每个App进程的主线程去不断的获取消息并分发给句柄Handler去处理。下面我们分析一下handler中的一些事件驱动的策略。

Androd中的几个使用场景

为了证实我们上面提出的观点,我们看看在源码里的体现,我们只看消息的接收处理的逻辑,因为Android是基于C/S架构,我们的事件产生都是经过server端产生然后通过binder通信传递给Client(App进程)。

1.activity与service的启动,生命周期的执行的消息处理(ActivityThread中的H类)

class H extends Handler {
.... 省略
public void handleMessage(Message msg) {
....省略
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;

2.Activity点击事件分发
当系统点击手机屏幕时,Linux内核会将硬件产生的触摸事件包装为Event存到/dev/input/event目录下,loop会通过epoll机制监听该事件,然后最终回调ViewRootImpl的WindowInputEventReceiver的方法,传递给对应的Activity去处理改点击事件

epoll机制

Linux本身的一个设计思想也是一切皆文件,epoll机制可以理解对文件亦或是流的监听,当该文件/流不可读(缓冲区取完),epoll机制会使线程进入休眠状态(epoll_wait),不浪费cpu资源。当文件/流可读(缓存区有数据),epoll机制会唤醒线程然后读取数据。

休眠阻塞

当Looper.loop()开始调用时,内部就开始死循环获取MessageQueue中的消息。如果当前时间段没有要执行的消息,如果还在不断的死循环进行消息的遍历,无疑是对CPU的浪费。所以在没有消息处理时,会使用epoll机制使当前线程进入休眠状态。

Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
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();
}

//关键代码
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 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 // 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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

nativePollOnce(ptr, nextPollTimeoutMillis) 即为关键所在,该方法是个native方法,从其名字PollOnce表示轮询一次并看不出他有阻塞的含义,还有就是native内部有什么需要轮询呢?接下来我们看看native代码的实现

int Looper::pollInner(int timeoutMillis) {
...
int result = POLL_WAKE;
mResponses.clear();
mRespOnseIndex= 0;
mPolling = true; //即将处于idle状态
struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //fd最大个数为16
//等待事件发生或者超时,在nativeWake()方法,向管道写端写入字符,则该方法会返回;
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis) 可以看出会进入休眠状态,timeoutMillis值解释如下:
1.如果timeoutMillis =-1,一直阻塞不会超时。
2.如果timeoutMillis =0,不会阻塞,立即返回。
3.如果timeoutMillis>0,最长阻塞timeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。

唤醒

上面小节我们了解了Handler的休眠逻辑,那如何唤醒呢?无非两种情况
1.指定的timeoutMillis时间已到,可以理解为自己睡醒了
2.别人叫醒
我们可以猜测一下唤醒线程的执行时机实际就是新加入的事件消息是否需要马上执行。
我们来分析一下别人叫醒的地方。代码如下:

boolean enqueueMessage(Message msg, long when) {
····
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || 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 break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}

根据上述代码我们根据msg插入不同,将msg分为三类

《事件驱动模型 Handler 与 Flutter future》
《事件驱动模型 Handler 与 Flutter future》

1.如果异步线程向主线程新加入的消息是插入消息队列对头则需要唤醒队列

if (p == null || when == 0 || when // New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
}

2.如果异步线程向主线程新加入的消息是异步消息,并且在队列的第二个位置,并且开启了同步屏障,则唤醒队列

3.其他情况的msg,均不会唤醒消息队列

大家可以看出上面强调了异步线程,因为主线程处于休眠状态。所以上面的方法只能异步线程调用。这个异步线程会是谁呢?留给大家一起思考。

接下来看唤醒的逻辑,nativeWake方法内部关键代码如下:

void Looper::wake() {
uint64_t inc = 1;
// 向管道mWakeEventFd写入字符1
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, errno=%d", errno);
}
}
}

我们向管道中写入了数据,由于我们使用epoll监听了该管道,所以epoll_wait会被唤醒。

同步屏障机制(sync barrier)

有这样一个场景:某个消息加入消息队列后,我们希望他立即被处理掉。但是我们的消息都是按照系统运行时间排序的。我们如果达到该目的呢。如果了解同步屏障机制的话改问题就不在是一个问题。

还是从代码入手

Message next() {
....

nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 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());
}

从关键位置代码的逻辑可以看出只要队列头部的msg的target为null就会查找队列中的异步消息

我们如何发送target为null的msg到队列头部呢?可以使用该方法

int token = mHandler.getLooper().getQueue().postSyncBarrier();

然后我们发送消息时设置当前消息为异步消息就可以了。
当然我们还需要移除target为null的消息,不然同步消息就永远不执行了。

mHandler.getLooper().getQueue().removeSyncBarrier(token)

Android系统中的触发View布局测量流程的msg就是一个异步消息,从而加速布局和绘制来减少卡顿。

空闲消息(idelHadler)

Message next() {
...省略代码
// No more messages.
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.
if (pendingIdleHandlerCount <0
&& (mMessages == null || now 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 = mIdleHandle

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.

线程轮询消息队列是发现没有要处理的消息时,发现注册了idelHadler。线程表示我就不休息了处理你吧。

以上我们了解了idelHadler的执行时机。idelHadler有哪些应用场景呢?

《事件驱动模型 Handler 与 Flutter future》
《事件驱动模型 Handler 与 Flutter future》

1.在开发过程中,在Activiy业务得到某个view的高度然后进行相关操作,在resume方法中必然是不好使的,因为还没有触发计算的msg并且还没有执行。 所以我们要等到消息执行完成才可以获取大小。怎么才能知道测量的消息执行完成了,idelHadler就派上了用场。我们可以在resume方法中想获取大小的逻辑,加到idelHadler回调中。

2.resume方法中数据填充太耗时,我们同样可以加到idelHadler回调中加快展示速度。

事件驱动模型 Flutter future

相信一些小伙伴已经接触了Flutter开发,Flutter也是事件驱动的,也有自己的Event Loop。

《事件驱动模型 Handler 与 Flutter future》
《事件驱动模型 Handler 与 Flutter future》

可以看出其有两个消息队列 微任务队列(MicroTask queue)和事件队列(Event queue)

  1. 事件队列包含外部事件,例如I/O, Timer,绘制事件等等
  2. 微任务队列则包含有Dart内部的微任务,主要是通过scheduleMicrotask来调度

同样我们不应该在Future执行耗时操作不然会卡。

大家计算一下输出的结果是啥?

import 'dart:async';
main() {
print('1');
scheduleMicrotask(() => print('3'));
new Future.delayed(new Duration(seconds:1),
() => print('7'));
new Future(() => print('5'));
new Future(() => print('6'));
scheduleMicrotask(() => print('4'));
print('2');
}

输出如下:

1
2
3
4
5
6
7

可以看出main方法内调用new Future() 或 scheduleMicrotask()实质上是向队列中发送消息,main方法结束然后开始轮询消息队列执行回调。

总结

本文通过事件驱动模型从而引出Handler和Future, 总结一下Handler中的几个关键名词 epoll机制,休眠/唤醒策略,同步屏障,异步/同步消息idelHadler。Flutter Future总结的就比较少了记住两个两个任务队列 微任务队列(MicroTask queue)事件队列(Event queue)。如有问题欢迎指正,共同学习共同进步。

Q&A

你以为你以为的就是你以为的吗?

实践是检验真理的唯一标准。

参考

事件驱动编程
Flutter学习之事件循环机制、数据库、网络请求
Flutter for Android Developers &#8211; Async UI
Flutter/Dart中的异步
你真的了解Handler吗?
异步消息
Handler之同步屏障机制
关于MessageQueue
你知道android的MessageQueue.IdleHandler吗
Android Event事件流分析
Android应用处理MotionEvent的过程
事件驱动
epoll原理是什么


推荐阅读
  • Python 数据可视化实战指南
    本文详细介绍如何使用 Python 进行数据可视化,涵盖从环境搭建到具体实例的全过程。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文总结了在SQL Server数据库中编写和优化存储过程的经验和技巧,旨在帮助数据库开发人员提升存储过程的性能和可维护性。 ... [详细]
  • 在CentOS 7环境中安装配置Redis及使用Redis Desktop Manager连接时的注意事项与技巧
    在 CentOS 7 环境中安装和配置 Redis 时,需要注意一些关键步骤和最佳实践。本文详细介绍了从安装 Redis 到配置其基本参数的全过程,并提供了使用 Redis Desktop Manager 连接 Redis 服务器的技巧和注意事项。此外,还探讨了如何优化性能和确保数据安全,帮助用户在生产环境中高效地管理和使用 Redis。 ... [详细]
  • XAMPP 遇到 404 错误:无法找到请求的对象
    在使用 XAMPP 时遇到 404 错误,表示请求的对象未找到。通过详细分析发现,该问题可能由以下原因引起:1. `httpd-vhosts.conf` 文件中的配置路径错误;2. `public` 目录下缺少 `.htaccess` 文件。建议检查并修正这些配置,以确保服务器能够正确识别和访问所需的文件路径。 ... [详细]
  • MySQL的查询执行流程涉及多个关键组件,包括连接器、查询缓存、分析器和优化器。在服务层,连接器负责建立与客户端的连接,查询缓存用于存储和检索常用查询结果,以提高性能。分析器则解析SQL语句,生成语法树,而优化器负责选择最优的查询执行计划。这一流程确保了MySQL能够高效地处理各种复杂的查询请求。 ... [详细]
  • PHP 各版本对比:标准版与最新顶级版的详细分析 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 独家解析:深度学习泛化理论的破解之道与应用前景
    本文深入探讨了深度学习泛化理论的关键问题,通过分析现有研究和实践经验,揭示了泛化性能背后的核心机制。文章详细解析了泛化能力的影响因素,并提出了改进模型泛化性能的有效策略。此外,还展望了这些理论在实际应用中的广阔前景,为未来的研究和开发提供了宝贵的参考。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 在2019中国国际智能产业博览会上,百度董事长兼CEO李彦宏强调,人工智能应务实推进其在各行业的应用。随后,在“ABC SUMMIT 2019百度云智峰会”上,百度展示了通过“云+AI”推动AI工业化和产业智能化的最新成果。 ... [详细]
  • 如何拆解联想C4030一体机并安装额外内存条?
    收到一台朋友赠送的联想C4030一体机,这是一款面向家庭用户的入门级设备。其配置包括Intel i3处理器、4GB内存和500GB硬盘,整体性能较为有限。尽管如此,该机配备了一块1920x1080分辨率的高清IPS屏幕,显示效果尚可。为了提升性能,计划拆解机器并加装额外的内存条。 ... [详细]
  • 搜索引擎技术概论(上篇):核心原理与应用分析
    搜索引擎技术概论(上篇)探讨了搜索的基本概念及其核心原理。搜索的本质在于信息检索,即用户通过输入关键词,利用特定的算法从海量数据中快速定位并提供所需信息。本文详细分析了搜索引擎的工作机制及其在实际应用中的表现。 ... [详细]
  • 本文对SQL Server系统进行了基本概述,并深入解析了其核心功能。SQL Server不仅提供了强大的数据存储和管理能力,还支持复杂的查询操作和事务处理。通过MyEclipse、SQL Server和Tomcat的集成开发环境,可以高效地构建银行转账系统。在实现过程中,需要确保表单参数与后台代码中的属性值一致,同时在Servlet中处理用户登录验证,以确保系统的安全性和可靠性。 ... [详细]
  • 如何在PHP中准确获取服务器IP地址?
    如何在PHP中准确获取服务器IP地址? ... [详细]
author-avatar
书友54330525
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有