Dart中的background isolate探究

1,423 阅读2分钟

简介

在前面的篇章中, 我们已经知道了, 在 Dart 中,Isolate 是一种轻量级的线程,可以并发执行代码。并且它的主要应用场景之一是在后台 Isolate 中执行耗时的计算任务,不会阻塞应用程序的主线程。

Dart 代码运行在一个独立的root isolate中,而 isolate 之间不共享内存, 多个isolate之间通过消息传递数据。

Flutter 3.7之前, 我们在root isolate中用shared_preferences缓存一个简单的String类型的变量。之后在子isolate里, 我们是获取不到刚缓存的String类型的值。这是因为, 切换了 isolate ,它就会变为 nullisolate之间不共享内存。

现在有了后台isolate, 我们就可以获取到了。

后台任务依赖于root isolate提供token

RootIsolateToken

/// A token that represents a root isolate.
class RootIsolateToken {
  RootIsolateToken._(this._token);

  /// An enumeration representing the root isolate (0 if not a root isolate).
  final int _token;

  /// The token for the root isolate that is executing this Dart code.  If this
  /// Dart code is not executing on a root isolate [instance] will be null.
  static final RootIsolateToken? instance = () {
    final int token = __getRootIsolateToken();
    return token == 0 ? null : RootIsolateToken._(token);
  }();

  @Native<Int64 Function()>(symbol: 'PlatformConfigurationNativeApi::GetRootIsolateToken')
  external static int __getRootIsolateToken();
}

表示root isolate的令牌token

BackgroundIsolateBinaryMessenger

信使的作用, 用于后台(非根)隔离。用消息传递实现。

/// A [BinaryMessenger] for use on background (non-root) isolates.
class BackgroundIsolateBinaryMessenger extends BinaryMessenger {
  BackgroundIsolateBinaryMessenger._();

  final ReceivePort _receivePort = ReceivePort();
  final Map<int, Completer<ByteData?>> _completers =
      <int, Completer<ByteData?>>{};
  int _messageCount = 0;
 }
  • _receivePort: 一个 ReceivePort 实例,用于接收从其他隔离体发送过来的消息。
  • _completers:: 一个 Map,用于存储消息的 Completer 对象。Completer 用于异步地等待消息的返回结果。在这个 Map 中,消息的标识符(通常是一个唯一的整数)与对应的 Completer 关联。

ensureInitialized

static void ensureInitialized(ui.RootIsolateToken token) {
  if (_instance == null) {
    ui.PlatformDispatcher.instance.registerBackgroundIsolate(token);
    final BackgroundIsolateBinaryMessenger portBinaryMessenger =
        BackgroundIsolateBinaryMessenger._();
    _instance = portBinaryMessenger;
    portBinaryMessenger._receivePort.listen((dynamic message) {
      try {
        final List<dynamic> args = message as List<dynamic>;
        final int identifier = args[0] as int;
        final Uint8List bytes = args[1] as Uint8List;
        final ByteData byteData = ByteData.sublistView(bytes);
        portBinaryMessenger._completers
            .remove(identifier)!
            .complete(byteData);
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'services library',
          context:
              ErrorDescription('during a platform message response callback'),
        ));
      }
    });
  }
}

实现后台隔离的核心代码。

将上面获取的root isolate的令牌token传进来,跟BackgroundIsolate关联起来,也就是注册后台隔离体。这样,BackgroundIsolate就跟root isolate建立了通信连接,BackgroundIsolateroot isolate之间就可以传递消息了。

示例

获取缓存的值

floatingActionButton: FloatingActionButton(
  onPressed: () {
    final RootIsolateToken _rootIsolateToken = RootIsolateToken.instance!;
    Isolate.spawn(_isolateMain, _rootIsolateToken);
  },
  tooltip: 'Tap',
  child: const Icon(Icons.add),
),

点击按钮, 获取之前用shared_preferences缓存的String类型的值。

/// 顶层函数
_isolateMain(RootIsolateToken rootIsolateToken) async {
  /// 注册background isolate, 使用 root isolate提供的令牌
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);

  /// 使用shared_preferences插件
  final SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  final bool? _isDebug = sharedPreferences.getBool('isDebug');
  print('---😆😆mark1, _isDebug:$_isDebug😆😆---');

  /// 继续设置为false
  sharedPreferences.setBool('isDebug', false);
}

_isolateMain任务是个顶层函数, 内部可以获取到缓存的变量值了。并且, 在子isolate里面更新缓存的变量值后, 在root isolate也可以同步更新。

图像处理

图像处理通常需要大量的计算,例如缩放、滤镜应用或复杂的图像编辑。将图像处理操作放在后台隔离中可以减轻主线程的负担。

下面一个示例, 将图像旋转 90 度, 然后保存到文件系统中。

FloatingActionButton(
  onPressed: () async {
    final ByteData data = await rootBundle.load('assets/sample.png');
    final List<int> bytes = data.buffer.asUint8List();
    img.Image image = img.decodeImage(Uint8List.fromList(bytes))!;

    /// token
    final RootIsolateToken _rootIsolateToken = RootIsolateToken.instance!;

    // 新建ReceivePort接收消息
    final receivePort = ReceivePort();
    await Isolate.spawn(
      _processImage,
      {'image': image, 'sendPort': receivePort.sendPort, 'rootIsolateToken': _rootIsolateToken},
    );

    // 关闭receivePort
    final processedImage = await receivePort.first;
    receivePort.close();

    /// 获取存储路径
    _saveImg(processedImage);
  },
  tooltip: '编辑图片',
  child: const Text('编辑图片'),
)

旋转、保存图片

/// 顶层函数
_processImage(Map<String, dynamic> data) async {
  final img.Image image = data['image'];
  final SendPort sendPort = data['sendPort'];
  final RootIsolateToken rootIsolateToken = data['rootIsolateToken'];

  /// 注册 root isolaote
  BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);

  // 旋转图片90度
  final img.Image rotatedImage = img.copyRotate(image, 90);

  // 1、发送旋转后的图片到main isolate
  sendPort.send(rotatedImage);

  // 2、或者直接保存到本地
  /// _saveImg(rotatedImage);
}

/// 保存图片
_saveImg(img.Image rotatedImage) async {
  /// 获取存储路径
  final Directory _directory = (await getTemporaryDirectory());
// 保存编辑后的图片到本地
  final File outputFile = File(_directory.path + '/path_to_output_image.png');
  await outputFile.writeAsBytes(img.encodeJpg(rotatedImage));
  print('Image processing complete. Saved to ${outputFile.path}');
}

参考资料

BackgroundIsolateBinaryMessenger class

RootIsolateToken class

Flutter 3.7 新特性:介绍后台isolate通道

Flutter 小技巧之 3.7 性能优化 background isolate

Flutter 后台任务

Flutter Background Tasks

isolate_agents: Easy Isolates for Flutter