Flutter中的 Zone 和 它的衍生物

253 阅读3分钟

什么是Zone

最好的解释就是官方说明

先来看看最核心的定义

A zone represents an environment that remains stable across asynchronous calls.
在异步调用中执行的一个稳定的环境

所以它定下了一个基调,后续我们直接或间接使用它的时候,基本都是出于异步的原因

All code is executed in the context of a zone, available to the code as Zone.current The initial `main` function runs in the context of the default zone ([Zone.root]). Code can be run in a different zone using either [runZoned] or [runZonedGuarded] to create a new zone and run code in it, or [Zone.run] to run code in the context of an existing zone which may have been created earlier using [Zone.fork].

所有的代码都执行在一个叫Zone的上下文中, flutter的程序在启动main方法的时候就默认执行在了一个叫zone.root的 区域 中了 ,同时我们可以通过 runZoned 和 runZoneGuarded的方式创建一个我们自己的zone ,我们可以用Zone.current来查看当前的zone的类型

在程序入口的main()中默认运行在一个root Zone 中 ,你也可以创建自己的Zone, Zone和Zone之间是隔离的, 他们之间无法通信,你能创建出来的Zone 都是从root Zone fork来的

Zone有什么用

对于Zone而言,它有两个构造函数:

  1. ZoneSpecification
  2. ZoneValues

ZoneSpecification:其实是Zone内部代码行为的一个提取,我们可以通过它来为Zone设置一些监听。

ZoneValues:Zone的变量,私有变量。

你可以将Zone类比为一个代码执行沙箱,不同沙箱的之间是隔离的,沙箱可以捕获、拦截或修改一些代码行为,如Zone中可以捕获日志输出、Timer创建、微任务调度的行为,同时Zone也可以捕获所有未处理的异常


创建Zone

Dart提供了runZoned 或 runZonedGuarded 方法,支持Zone的快速创建, 并在其中运行代码

 // 使用runZonedGuarded函数创建一个新的Zone,并捕获未处理的异常
  runZonedGuarded(() {
// 在这里运行你的代码
  }, (error, stackTrace) {
// 在这里处理异常
  });

// 使用Zone.current.fork方法创建一个新的Zone,并修改print行为
  var zone = Zone.current.fork(specification: ZoneSpecification(
      print: (self, parent, zone, line) {
// 在这里修改print行为
      }
  ));
// 在新的Zone中运行代码
  zone.run(() {
// 在这里运行你的代码
  });


Zone是如何工作的

zone 的工作原理是通过 Zone.current.fork() 方法创建一个新的 zone,并且可以通过 ZoneSpecification 参数来自定义一些 zone 的行为 Zone.run() 方法来在新的 zone 中执行代码 zone 中保持一致的环境,并且可以处理一些异步异常, runApp 和任何扩展 runApp 的东西已经在一个区域中运行。对于Flutter App Developers,我们只需要再添加一个特殊的zone 实现,让一个zone 在app 异常时可以将app exception 错误报告给仍在执行的zone。

简而言之,main 是 Zone 0,任何其他声明的 runZoned 或 runGuardedZone 是下一个子区域。


Zone应用的领域

1 捕获异常

在Dart中,异常分两类:同步异常和异步异常,同步异常可以通过try/catch捕获,根据上边Zone的定义, 异步异常我们就可以通过Zone的包装来捕获

//全局异常的捕捉
class AppCatchError {
  run(Widget app) async {
    ///Flutter 框架异常
    FlutterError.onError = (FlutterErrorDetails details) async {
      ///线上环境
      ///TODO
      if (kReleaseMode) {
        Zone.current.handleUncaughtError(details.exception, details.stack!);
      } else {
        //开发期间 print
        FlutterError.dumpErrorToConsole(details);
      }
    };

    runZonedGuarded(() {
      //受保护的代码块
      Future.delayed(const Duration(seconds: 1), () {
        runApp(app);
      });
    }, (error, stack) => catchError(error, stack));
  }

  ///对搜集的 异常进行处理  上报等等
  catchError(Object error, StackTrace stack) {
    // ToastUtils.show('${error}${stack}');
    // print("AppCatchError>>>>>>>>>>: $kReleaseMode"); //是否是 Release版本
    _reportServer(error, stack);
    // debugPrint('AppCatchError message:$error,stack$stack');
  }

  _reportServer(Object error, StackTrace stack) {
    var errorInfo = '$error,stack$stack';
    var userId = LoginUtil.userId();
    // 自定义上报
    RequestRepository.getInstance().reportError(errorInfo, userId);
  }
}

2 使用Zone.bindCallback来绑定一个异步回调函数

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  String _text = 'Hello';

  void _updateText() async {
// 使用Zone.bindCallback来绑定一个异步回调函数
    var callback = Zone.current.bindCallback(() async {
      await Future.delayed(Duration(seconds: 1));
      return 'World';
    });
// 在回调函数执行后更新状态
    var result = await callback();
    setState(() {
      _text = result;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(_text),
        ElevatedButton(
          onPressed: _updateText,
          child: Text('Update'),
        ),
      ],
    );
  }
}

3 拦截修改微任务调度的行为

1 拦截微任务(Microtask)的调度行为

我们都知道微任务是Dart进行事件循环(event loop)的重要组成部分,同样在zone内也是可以拦截它的调度行为。

main() {
  Zone.root;
  Zone parentZone = Zone.current.fork(specification:
      new ZoneSpecification(registerCallback: <R>(self, parent, zone, f) {
    // 调用我们实际注册的回调函数,第一次这里进来的 f 是 start(),第二次进来则是 end()
    f();
    return f;
  }));
  parentZone.run(() {
    Zone.current.registerCallback(() => start());
    print("hello");
    Zone.current.registerCallback(() => end());
  });
}

void start() {
  print("start");
}

void end() {
  print("end");
}

打印

start
hello
end

2 修改微任务(Microtask)的调度行为

//创建队列存储Microtask的队列
Queue q = Queue();
runZoned(
  () {
    scheduleMicrotask(() => print('schedule Microtask1'));
    scheduleMicrotask(() => print('schedule Microtask2'));
  },
  zoneSpecification: ZoneSpecification(
      // 拦截print
      print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
    parent.print(zone, "${DateTime.now()} —— Interceptor: $line");
  }, 
  //拦截微任务调度行为并修改
  scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void f()) {
    q.add(f);
    if (q.length == 2) {
      while (q.length > 0) {
        final tempF = q.removeLast();
        //从队列取出function并执行
        parent.scheduleMicrotask(self, tempF);
      }
    }
  }),
);

打印

flutter: 2023-03-03 20:20:23.141450 —— Interceptor: schedule Microtask2
flutter: 2023-03-03 20:20:23.145571 —— Interceptor: schedule Microtask1

4 拦截其他行为

zone还能通过ZoneSpecification还能拦截该空间的其他一些行为,比如fork、run、registerCallback

5 Stream

这个用就很常用了,可以参考