探索Dart中的Zone

Zone是什么

zone表示跨异步调用保持稳定的环境。代码总是在zone的上下文中执行,使用Zone.current查看当前zone,初始化主函数在Zone.root中运行的。使用runZoned创建新的zone,也可以使用zone.run在先前使用zone.fork创建现有区域的上下文中运行代码。

上面是官方说法,我们用可以这样理解:

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

程序启动进入main函数会通过runZonedGuardedfork一个新的zone

@pragma('vm:entry-point')
// ignore: unused_element
void _runMainZoned(Function startMainIsolateFunction,
                   Function? dartPluginRegistrant,
                   Function userMainFunction,
                   List<String> args) {
  startMainIsolateFunction(() {
    runZonedGuarded<void>(() {
      if (dartPluginRegistrant != null) {
        dartPluginRegistrant();
      }
      if (userMainFunction is _ListStringArgFunction) {
        (userMainFunction as dynamic)(args);
      } else {
        userMainFunction();
      }
    }, (Object error, StackTrace stackTrace) {
      _reportUnhandledException(error.toString(), stackTrace.toString());
    });
  }, null);
}
复制代码

通过打印也可得知

print(Zone.current.parent == Zone.root); //flutter: true

复制代码

需要说明的是:

Zone是不能子类化的,通过Zone.fork基于父Zone自定义子Zone时,类似于扩展了该父Zone并覆盖了父Zone的方法,实际上并不是新创建一个类。重写的方法会以函数的形式提供,并会把父Zone和当前Zone作为参数传入

Zone使用

捕获异步执行的错误和拦截打印

在Dart中,异常分为两种:同步和异步,我们可以使用try/catch来捕获同步下的错误,但是在异步情况下无法使用try/catch捕获,

try{
    Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
}catch (e){
    print(e)
}
复制代码

此时我们可以使用runZoned()给执行代码指定一个zone。在此区域内的异步代码都会被该zone捕获到。

runZoned(...)方法定义:

R runZoned<R>(R body(), {
    Map zoneValues, 
    ZoneSpecification zoneSpecification,
}) 
复制代码

通过Zone.fork创建一个基于[zoneSpecification][zoneValues]的新zone。
然后运行body内的代码,并返回结果

zoneValues:Zone 的私有数据,可以通过实例zone[key]获取,可以理解为每个“沙箱”的私有数据。

zoneSpecification:Zone的一些配置,可以自定义一些代码行为,比如拦截日志输出和错误等,拦截日志输出并捕获该区域内的错误:

runZoned(
  () {
    Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
    Future.delayed(Duration(seconds: 2)).then((e) => print('ABCD'));
  },
  zoneSpecification: ZoneSpecification(
    // 拦截print
    print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
      parent.print(zone, "Interceptor: $line");
    },
    // 拦截未处理的异步错误
    handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,
        Object error, StackTrace stackTrace) {
      parent.print(zone, '${error.toString()} $stackTrace');
    },
  ),
);
复制代码

用Zone拦截及修改Timer创建行为

拦截timer的创建行为

runZoned(
  () {
    Future.delayed(Duration(seconds: 1))
        .then((e) => Future.error("xxxError"));
    Future.delayed(Duration(seconds: 2))
        .then((e) => print('FutureDelayedFunction'));
    Timer(Duration(seconds: 3), () => print('timer1F'));
    Timer(Duration(seconds: 4), () => print('timer2F'));
  },
  zoneSpecification: ZoneSpecification(
    // 拦截print
    print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
      parent.print(zone, "${DateTime.now()} —— Interceptor: $line");
    },
    // 拦截未处理的异步错误
    handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,
        Object error, StackTrace stackTrace) {
      parent.print(
          zone, '${DateTime.now()} ——' + '${error.toString()} $stackTrace');
    },
    //拦截timer
    createTimer: (Zone self, ZoneDelegate parent, Zone zone,
        Duration duration, void f()) {
      return parent.createTimer(self, duration, f);
    },
  ),
);
复制代码

打印结果

flutter: 2022-03-03 20:01:51.497784 ——xxxError
flutter: 2022-03-03 20:01:52.495162 —— Interceptor: FutureDelayedFunction
flutter: 2022-03-03 20:01:53.491801 —— Interceptor: timer1F
flutter: 2022-03-03 20:01:54.492453 —— Interceptor: timer2F
复制代码

修改Timer的创建行为

如果只是这样我们并不能看出来Timer是否被真正拦截,我们修改一下代码

//拦截timer创建行为 并修改
createTimer: (Zone self, ZoneDelegate parent, Zone zone,
    Duration duration, void f()) {
  Timer newTimer = parent.createTimer(
      self, Duration(seconds: 3), () => print('你们被替换了'));
  return newTimer;
},
复制代码

打印结果

flutter: 2022-03-03 20:05:41.343419 —— Interceptor: 你们被替换了
flutter: 2022-03-03 20:05:41.348701 —— Interceptor: 你们被替换了
flutter: 2022-03-03 20:05:41.348984 —— Interceptor: 你们被替换了
flutter: 2022-03-03 20:05:41.349245 —— Interceptor: 你们被替换了
复制代码

注意

由于Future.delayed内部也是由Timer实现,所以也会被拦截修改。

拦截微任务调度的行为

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

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

runZoned(
  () {
    scheduleMicrotask(() => print('schedule Microtask'));
  },
  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()) {
      parent.scheduleMicrotask(self, f);
    }
  ),
);
复制代码

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

那么我们能拦截调度,能修改吗?

肯定能,不然我写这些干嘛?

//创建队列存储Microtask的function
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: 2022-03-03 20:20:23.141450 —— Interceptor: schedule Microtask2
flutter: 2022-03-03 20:20:23.145571 —— Interceptor: schedule Microtask1
复制代码

其他的一些行为拦截

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

const factory ZoneSpecification(
    {HandleUncaughtErrorHandler? handleUncaughtError,
    RunHandler? run,
    RunUnaryHandler? runUnary,
    RunBinaryHandler? runBinary,
    RegisterCallbackHandler? registerCallback,
    RegisterUnaryCallbackHandler? registerUnaryCallback,
    RegisterBinaryCallbackHandler? registerBinaryCallback,
    ErrorCallbackHandler? errorCallback,
    ScheduleMicrotaskHandler? scheduleMicrotask,
    CreateTimerHandler? createTimer,
    CreatePeriodicTimerHandler? createPeriodicTimer,
    PrintHandler? print,
    ForkHandler? fork}) = _ZoneSpecification;
复制代码

通过Zone来窥探Stream的实现原理

关于Stream的详细介绍及原理可以看这里

Stream对象

  • StreamController:用于整个Stream过程的控制,提供各类接口用于创建各种事件流。
  • StreamSink:事件的入口,add,addStream等。
  • Stream:事件源本身,一般可用于监听事件或者对事件进行转换,如listenwhere
  • StreamSubscription:事件订阅后的对象,表面上用于管理订阅过各类操作,如cancel

Stream工作原理

  1. Streamlisten的时候传入onData回调,这个回调会传入到StreamSubscription中,之后通过zone.registerUnaryCallback注册得到_onData函数对象
  2. StreamSink在添加事件时,会执行到StreamSubscription中的_sendData方法,然后通过_zone.runUnaryGuarded(_onData, data)执行1中得到的_onData函数对象,触发listen时传入的回调方法

我们在源码出也能看到确实如此

//1.listen传入onData回调到StreamSubscription中
StreamSubscription<T> listen(void onData(T data)?,
    {Function? onError, void onDone()?, bool? cancelOnError}) {
  cancelOnError ??= false;
  StreamSubscription<T> subscription =
      _createSubscription(onData, onError, onDone, cancelOnError);
  _onListen(subscription);
  return subscription;
}

//为节省篇幅,已省略部分代码
//在此,已经获取到_onData函数对象
_onData = _registerDataHandler<T>(_zone, onData),
      
//把onData传入进行注册
static void Function(T) _registerDataHandler<T>(
    Zone zone, void Function(T)? handleData) {
  return zone.registerUnaryCallback<void, T>(handleData ?? _nullDataHandler);
}
复制代码
//2.sink添加事件StreamSubscription._sendData,然后调用_zone.runUnaryGuarded(_onData, data),
/* _EventDispatch interface. */

void _sendData(T data) {
  assert(!_isCanceled);
  assert(!_isPaused);
  assert(!_inCallback);
  bool wasInputPaused = _isInputPaused;
  _state |= _STATE_IN_CALLBACK;
  _zone.runUnaryGuarded(_onData, data);
  _state &= ~_STATE_IN_CALLBACK;
  _checkState(wasInputPaused);
}
复制代码

那么,zone.registerUnaryCallbackzone.runUnaryGuarded分别是什么?

//注册一个给定的回调在这个zone里面,得到一个函数对象,类型是ZoneUnaryCallback
ZoneUnaryCallback<R, T> registerUnaryCallback<R, T>(R callback(T arg));

//ZoneUnaryCallback的定义
typedef R ZoneUnaryCallback<R, T>(T arg);
复制代码
//执行一个给定的action(就是上面的_onData函数对象),参数是argument,并且捕获异步的错误
void runUnaryGuarded<T>(void action(T argument), T argument);
复制代码

当然这里还有其他的方法比如registerCallback,registerBinaryCallback,区别就是参数数量的问题

至此,我们已清楚Stream是如何通过zone进行工作的,同样这些行为我们也是可以进行拦截并修改的

runZoned(
  () {
    final streamController = StreamController();
    streamController.sink.add('ABC');
    streamController.stream.listen((event) {
      print(event);
    });
  },
  zoneSpecification: ZoneSpecification(
    // 拦截print
    print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
      parent.print(zone, "${DateTime.now()} —— Interceptor: $line");
    },
    registerUnaryCallback: <R, T>(Zone self, ZoneDelegate parent, Zone zone,
        R Function(T arg) f) {
      return parent.registerUnaryCallback(self, f);
    },
    runUnary: <R, T>(Zone self, ZoneDelegate parent, Zone zone,
        R Function(T arg) f, T arg) {
      return parent.runUnary(zone, f, '偷梁换柱' as T);
    },
  ),
);
复制代码

打印结果

flutter: 2022-03-03 21:45:15.872001 —— Interceptor: 偷梁换柱
复制代码

参考文章:

juejin.cn/post/684490…

api.flutter-io.cn/flutter/dar…

book.flutterchina.club/chapter2/th…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享