• Isolate轻量级线程的使用场景
  • 发布于 2个月前
  • 302 热度
    0 评论
简介

在前面的篇章中, 我们已经知道了, 在 Dart 中,Isolate 是一种轻量级的线程,可以并发执行代码。并且它的主要应用场景之一是在后台 Isolate 中执行耗时的计算任务,不会阻塞应用程序的主线程。Dart 代码运行在一个独立的root isolate中,而 isolate 之间不共享内存, 多个isolate之间通过消息传递数据。


在 Flutter 3.7之前, 我们在root isolate中用shared_preferences缓存一个简单的String类型的变量。之后在子isolate里, 我们是获取不到刚缓存的String类型的值。这是因为, 切换了 isolate ,它就会变为 null。isolate之间不共享内存。现在有了后台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建立了通信连接,BackgroundIsolate和root 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 {
  /// Register the background isolate with the 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);
  // 堆代码 duidaima.com
  // 旋转图片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}');
}


用户评论