Android开发—Flutter 中的事件驱动【附:Flutter开发环境搭建和测试全套PDF资料】

1. 何谓驱动

Android开发—Flutter 中的事件驱动【附:Flutter开发环境搭建和测试全套PDF资料】

百度百科告诉我们,驱动即用推动,事件驱动就是用一个个的事件,推动某个操作的执行。

2. Loop

在我们的Java编程的程序系列中,Android就是一个典型的事件驱动应用,熟悉Android开发的同学一定知道,其底层不断地有一个Looper在循环,遇到消息则处理,否则阻塞在MessageQueue.next()方法处。我们非常常用的一个方法:runOnUiThread的作用是在主线程执行一个操作,因为在子线程安卓系统不允许我们操作UI。但是你想过他是如何实现的吗?

其实很简单:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

判断一下当前线程是否主线程,是的话直接执行,否则的话,通过Handler机制,post到主线程,然后由子线程的Looper取出执行。

比如我们在请求网络后,想让系统加载一个数据,数据返回后,更新视图,那么“数据返回”实际上就是一个事件,既然是事件我们肯定要消费,所以就调用runOnUiThread交给主线程消费。

其实,我们在任何一个主线程的调用栈中,调用栈底的方法一定是loop方法,因为所有发生在主线程的操作都是先给主线程的MessageQueue加入一个Message,然后looper取出执行操作,Message甚至可以附带一个Runnable来执行代码。也可以附带一些信息,在回调处统一处理。

3. OnClickListener 事件

我们通常会调用View下的setOnClickListener设定点击事件,比较常见的写法除了简单的设置View.OnClickListenerXML指定以外,通常是使用当前的Activity去实现View.OnClickListener接口,重写onClick方法,根据ID来判断点击事件产生的对象,进而确定具体的点击事件。

比如设置一个按钮,我们点击按钮就要请求网络,那么点击事件就会作为请求网络的驱动,这就是一个非常简单的事件驱动的流程。

4. 事件驱动模型

事件驱动模型有三个要素:事件源监听器事件。熟悉设计模式的同学应该很快就能反应过来,这三要素和Observer模式中的三个构成非常相似。实际上事件驱动模型就是基于观察者而定制的。

事件驱动模型的工作步骤:

  1. 定义监听器,为每一个事件编写处理方法。
  2. 监听器对象注册给事件源
  3. 事件源发生某个事件时,调用监听器中对应的方法完成事件的处理。

以上的3步,在OnClick事件中,分别对应:

    //1\. 监听器,处理响应事件
    val events = View.OnClickListener {
        Toast.makeText(this@MainActivity,"HelloWorld!",Toast.LENGTH_SHORT).show()
    }

    //2\. 注册到事件源
    findViewById<TextView>(R.id.tv).setOnClickListener (events)

	//3\. 当我们手指点击屏幕,产生事件时(即事件源产生事件),就向着目标分发事件,最终被触摸到的View所消费。

5. Dart和Flutter

(这部分的内容主要来自泪已无痕:[译] Flutter 异步编程:Future、Isolate 和事件循环,原文更详细,这里是完全参考着他写的,详细了解建议看原文。)

Dart是单线程模型,而Flutter则依赖于Dart,单线程模型有一个问题:同一时间执行一个操作,而其他的操作只能再其之后执行。

我们知道在Android中不能写死循环,否则会导致ANR,而Flutter也是一样,在Dart中如果我们写一个很大的for循环,那么在循环中执行操作同样会导致线程的阻塞,界面也被阻塞,例如在重写的setState()中执行循环,则必须等到结束后才能加载出界面。

但是在如今纷繁复杂的业务逻辑要求中,我们单线程模型实际上很难满足各种各样的需求了。所以,Dart采用了了一个代码序列器(事件循环)。

当我们启动一个DartApp时,将会构建一个新的线程进程,在Dart中为Isolate,该线程将是你在整个应用中唯一需要关注的。

所以,此线程创建后,Dart将会自动地:

  1. 初始化2个FIFO队列(MicroTask和Event)
  2. 并且当该方法执行完成后,执行Main方法
  3. 启动事件循环

事件循环是一种无限循环,在每个时钟周期内,如果没有其他的Dart代码执行,则:

void eventLoop(){
    while (microTaskQueue.isNotEmpty){
        fetchFirstMicroTaskFromQueue();
        executeThisMicroTask();
        return;
    }

    if (eventQueue.isNotEmpty){
        fetchFirstEventFromQueue();
        executeThisEventRelatedCode();
    }
}

从先后顺序我们可以看出,MicoTask队列优先于Event队列

5.1 MicroTask队列

MicroTask队列用于非常简短且需要异步执行的的内部动作,这些动作需要在其他事件完成后 并 在将执行权交给Event队列之前运行。

5.2 Event队列

Event 队列适用于以下参考模型:

  • IO
  • 手势
  • 绘图
  • 计时器
  • futures

事实上,每次外部事件被触发时,需要执行的代码都会被Event队列所引用。一旦没有MicroTask运行,事件循环将考虑Event队列中的第一项并执行它,而Future操作也将由Event队列执行。

5.3 Future

Future是一个异步执行并且在未来某一个时刻完成或者失败的任务,当实例化一个Future时:

  • 该Future的实例被创建、并记录在由Dart管理的内部数组中。
  • 需要由此Future执行的代码直接推送到Event中。
  • 该Future实例返回一个状态(=incomplete)
  • 如果存在下一个同步代码,则执行它。

只要事件循环Event循环中获取它,被Future引用的代码将像其他任何Event一样执行。

当改代码将被执行完成(或者失败)时,then或者cacheError回调将被触发。

在如下例子中:

void main(){
    print('Before the Future');
    Future((){
        print('Running the Future');
    }).then((_){
        print('Future is complete');
    });
    print('After the Future');
}

输出的顺序:

Before the Future
After the Future
Running the Future
Future is complete

执行的流程:

  1. print(‘Before the Future’)
  2. 将 (){print(‘Running the Future’);} 添加到 Event 队列;
  3. print(‘After the Future’)
  4. 事件循环获取(在第二步引用的)代码并执行它
  5. 当代码执行时,它会查找 then() 语句并执行它

所以,Flutter/Dart是使用Event循环机制来模拟并发的请求的。

Aysnc方法在使用时,Dart会认为该方法的返回值是一个Future,它同步执行该方法,直到遇到第一个await关键字,然后它暂停该方法其他部分的执行,一旦await关键字引用的Future执行完成,下一行代码将立即执行。

例如这个例子:

void main() async {
  methodA();
  await methodB();
  await methodC('main');
  methodD();
}

methodA(){
  print('A');
}

methodB() async {
  print('B start');
  await methodC('B');
  print('B end');
}

methodC(String from) async {
  print('C start from $from');

  Future((){                // <== 该代码将在未来的某个时间段执行
    print('C running Future from $from');
  }).then((_){
    print('C end of Future from $from');
  });

  print('C end from $from');
}

methodD(){
  print('D');
}

从Main方法开始执行,遇到第一个await则暂停,按照以往的认知,打印应该是:

A
B start
C start from B
C running Future from B
C end of Future from B
C end from B
B end
C start from A
C running Future from A
C end of Future from A
C end from A
D

但是其实是:

A
B start
C start from B
C end from B
--- C中的Future代码会在之后执行,不会立即执行。
B end
C start from main
C end from main
D
---到此处代码执行完了,才开始执行Event中的代码
C running Future from B
C end of Future from B
C running Future from main
C end of Future from main

这和我们想象的多线程(异步)处理有点不一样,异步只是线程各自以未知的顺序向前推进,但是单线程的Dart依靠事件循环机制,模拟了异步操作,但是我们发现所有的Future都放到了代码的最后执行。预测程序执行变得非常困难。

我们又知道,await会阻塞在当前代码的位置,所以,await和Future配合使用,可以获得同步的效果,即该代码等到Future执行完了再向下执行:

methodC(String from) async {
  print('C start from $from');

  await Future((){                  // <== 在此处进行修改
    print('C running Future from $from');
  }).then((_){
    print('C end of Future from $from');
  });
  print('C end from $from');
}

这样一来,可能在C start之后,等到C执行完成再向下执行。

所以,Dart中的异步执行实际上只是按照Event的事件处理规则执行,并不是真正的异步执行:

Android开发—Flutter 中的事件驱动【附:Flutter开发环境搭建和测试全套PDF资料】

5.4 Isolate

那么Flutter/Dart有自己的线程吗?Isolate。

Isolate在Flutter中不共享内存,不同的Isolate之间通过消息进行通信。

而每个Isolate都有自己的事件循环及队列(MicroTask和Event),这就意味着一个Isolate中运行的代码和另外一个Isolate不存在任何的关联。

是不是有点像Loop。

5.4.1 如何启用Isolate呢?

我们需要在调用者和新的Isolate之间建立通信。

每个Isolate都暴露了一个将消息传递给Isolate的被称为SendPort的端口(这是一个监听端口,不是用来发送的)。

这意味着调用者new Isolate需要知道彼此的端口才能进行通信。
我们有了端口就能发送消息,最后记得在dispose中销毁即可。

这是一种单工的通信方式(单监听流),我们只能从一方听到另一方发送的消息,如果要实现双工的通信需要彼此之间建立两条监听流。

5.5 结论

Flutter的事件循环机制有点像Android线程的Looper,但是Dart是单线程的,而Future和Isolate则可以实现或者是模拟异步操作,在开发中有非常重要的作用。

最后

Flutter学习进阶——开发环境搭建和测试

每一个移动开发者都在为 Flutter 带来的“快速开发、富有表现力和灵活的 UI、原生性能”的特色和理念而痴狂,从超级 App 到独立应用,从纯 Flutter 到混合栈,开发者们在不同的场景下乐此不疲的探索和应用着 Flutter 技术,也在面临着各种各样不同的挑战。

本篇知识要点:

  • 1、Flutter跨平台开发概述
  • 2、Windows中Flutter开发环境搭建
  • 3、编写你的第一个Flutter APP
  • 4、Flutter Dart语言系统入门

Android开发—Flutter 中的事件驱动【附:Flutter开发环境搭建和测试全套PDF资料】

完整学习笔记pdf免费分享,需要的朋友只需要点赞支持一下后,【点击这里直达免费获取方式

上一篇:论文笔记| The Emergence, Advancement and Future of Textual Answer Triggering


下一篇:C++并发与多线程(八) async、future、packaged_task、promise