Flutter开发-- Isolate

isolate和线程类似,但是每个isolate有他们自己的内存,他们不分享状态,只通过message互通消息。每一个isolate里都有一个事件循环,这个概念和JavaScript的线程概念多少有些相似。也就是说,如果在主isolate之外开了另外的一个或者多个isolate,那么他们之间可以通过message(消息)互相通信。

所有的Flutter app都在isolate上面运行。一般是只有一个,叫做main isolate。而且运行的也足够快,不会产生不良的影响。但是,难免出现需要执行大量的任务,从而导致界面出现了卡顿。在ioslate里,任何任务的事件只有最多16ms,这样才能保证界面60帧的刷新率。一旦代码的执行事件超过了这个限制就会出现界面的卡顿。 如果你正经历这个问题,那么可以把这些任务放在另外一个isolate里面。

代码执行事件和帧率的关系:

要怎么运行起来一个isolate呢?

  • Isolate.run,一杆子买卖,一次性在另外的一个isolate上执行代码。也可以使用compute这个方法。
  • Isolate.spawn, 这样创建的isolate会在后台长期存在。

Isolate.run

多数情况下第一种方式就可以解决了,比如计算一个斐波那契数列:

dart 复制代码
int slowFib(int n) => n <= 1 ? 1 : slowFib(n - 1) + slowFib(n - 2);

void fib40() async {
 var result = await Isolate.run(() => slowFib(40));
 print('Fib(40) = $result');
}

使用起来也是非常的简单。只需要在Isolate.run前面放一个await就可以得到结果了。

compute

前文说到,Isolate.runcompute基本类似。compute是在Flutter的专属,相当于语法糖的存在。上例就可以写为:

dart 复制代码
// final result = await Isolate.run(() => slowFib(target));
final result = await compute(slowFib, target);
return result;

算是一个小小的改进。

有一点需要注意的是:Flutter web不支持Isolate

在web运行,卡了

Isolate.spawn

入口函数的定义

入口函数接收一个参数SendPort, 用来向其他的isolate发送消息,一般来说就是那个spawn了isolate的isolate。

dart 复制代码
Future<void> spawnFib(SendPort sendPort) async {
  final result = slowFib(target);

  sendPort.send(result); // 发送计算结果
}

现在你可能就有疑问了,要计算斐波那契数列,那不得知道target的值是多少么。函数接收的参数只能发送计算的结果,没办法接收发送过来的target的值。

这就需要先用SendPort参数先把本isolate可以接收数据的SendPort发到信息发送方就可以了。如:

dart 复制代码
Future<void> spawnFib(SendPort sendPort) async {
  final commandPort = ReceivePort();
  sendPort.send(commandPort.sendPort);
  
  // 在这里接收传入的信息,并验证是不是传入的target值
  await for (final message in commandPort) {
    if (message is int) {
      // 略 
    }
    // 略
  }
}

Spawn一个isolate

上文讲了如何定义一个isolate的入口函数,下面来研究一下如何spawn一个isolate。

在上例中看到,要发送消息就要首先初始化一个接收消息的ReceivePort。要开启一个isolate就是这样:

dart 复制代码
 // 先定义一个接收端
 final p = ReceivePort(); 
 
 // Spawn一个isolate,把接收端的发送端作为参数发送过去
 await Isolate.spawn(spawnFib, p.sendPort); 
  • 第一步:初始化一个ReceivePort
  • 第二步:spawn一个isolate,同时把定义好的入口函数和ReceivePortSendPort实例作为参数传入。

学会了以上的内容就可以定义一个isolate入口函数和spawn一个isolate了。并且可以在两个isolate直接实现双向通信了。下面还有几个细节需要注意,既然可以互相发送消息,这些消息里内容的不同需要做一些区分。比如上文已经提到的isolate的入口函数,现在已经知道要发送两种不同类型的数据:

  1. 发送的是斐波那契数列的target
  2. 发送的是供主isolate发送消息的SendPort

这需要在spawn了isolate的主isolate里作区分。比如:

dart 复制代码
// 略
await for (var response in p) {
  if (response == null) {
    break;
  }

  if (response is SendPort) {
    sendPort = response;
    sendPort.send(40);

    break;
  }

  // TODO: show calulation result
  debugPrint('received message $response');
}
// 略

同时,在主isolate里还有一件必须处理的事,那就是在任务完成之后需要终止子isolate的执行。这也要通过发送消息的方式通知子isolate。

在主isolate里发送通知:

dart 复制代码
if (sendPort != null) sendPort.send(null);

在子isolate里执行isolate的退出:

dart 复制代码
Future<void> spawnFib(SendPort sendPort) async {
  final commandPort = ReceivePort();
  sendPort.send(commandPort.sendPort);

  await for (final message in commandPort) {
    if (message is int) {
      // 略
    } else if (message == null) { // 收到主isolate发来的null消息
      break;
    }
  }

  debugPrint("Spawn isolate existing...");
  Isolate.exit();                 // 子isolate总之执行
}

主isolate里发送了null消息之后,在子isolate里:

  1. 检查消息是否是null的,如果是则退出await-for循环。
  2. 执行本isolate的退出操作。

完整的示例代码如下:

isolate入口函数

dart 复制代码
Future<void> spawnFib(SendPort sendPort) async {
  final commandPort = ReceivePort();
  sendPort.send(commandPort.sendPort);

  await for (final message in commandPort) {
    if (message is int) {
      // final target = int.parse(message);
      final target = message;

      debugPrint("received message $target");

      final result = slowFib(target);

      debugPrint("cal result $result");

      sendPort.send(result);
    } else if (message == null) {
      break;
    }
  }

  debugPrint("Spawn isolate existing...");
  Isolate.exit();
}

在按钮点击时间里spawn一个上面的isolate:

dart 复制代码
onPressed: () async {
    final p = ReceivePort();
    await Isolate.spawn(spawnFib, p.sendPort);

    SendPort? sendPort;

    await for (var response in p) {
      if (response is SendPort) {
        sendPort = response;
        sendPort.send(40);
      }

      if (response is int) {
        // TODO: show calulation result
        debugPrint('received message $response');
        break;
      }
    }

    if (sendPort != null) sendPort.send(null);
  },

最后

以上的例子只是实现了比较简单的功能。有兴趣的同学可以实现一个可以发送进度消息的,可以取消的isolate试试。

直接使用isolate来实现上面说的进度、取消的功能有点略微的繁琐。因此就可以考虑第三方库worker_manager来实现这些功能。它提供了更多的封装和更丰富的功能,使用冯家方便。

相关推荐
无极程序员1 小时前
PHP常量
android·ide·android studio
萌面小侠Plus2 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农2 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶2 小时前
Android——多线程、线程通信、handler机制
android
君蓦2 小时前
Flutter 本地存储与数据库的使用和优化
flutter
L72562 小时前
Android的Handler
android
清风徐来辽2 小时前
Android HandlerThread 基础
android
HerayChen3 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野3 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java