What is the Zone

A zone represents an environment that remains stable across asynchronous invocations. The code is always executed in the context of the zone, using zone.current to view the current zone, and initializing the main function running in zone.root. Create a new zone with runZoned. You can also use zone.run to run the code in the context of the existing zone created with zone.fork.

Here’s the official version.

The Zone class performs a sandbox for code that is isolated from each other. The sandbox captures, intercepts, or modifies code behavior, such as logging, Timer creation, and microtask scheduling in the Zone, as well as all unhandled exceptions in the Zone

The program launches into main to create a new zone through runZonedGuardedfork

@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); }Copy the code

You can also get this by printing

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

Copy the code

To be clear:

Zones cannot be subclassed. Using zone.fork to define a child Zone based on the parent Zone is like extending the parent Zone and overwriting the parent Zone, rather than creating a new class. The overridden method is provided as a function, passing in the parent Zone and the current Zone as arguments

Zone using

Catch errors and intercept prints for asynchronous execution

Dart has two types of exceptions: synchronous and asynchronous. We can use try/catch to catch synchronous errors, but we cannot use try/catch to catch asynchronous errors.

try{
    Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx"));
}catch (e){
    print(e)
}
Copy the code

At this point, we can use runZoned() to specify a zone for the executing code. All asynchronous code in this zone is captured by the zone.

runZoned(…) Method definition:

R runZoned<R>(R body(), {
    Map zoneValues, 
    ZoneSpecification zoneSpecification,
}) 
Copy the code

Use zone. fork to create a new Zone based on [zoneSpecification] and [zoneValues]. Then run the code inside the body and return the result

ZoneValues: Private data of a Zone. You can obtain the private data of each sandbox by using Zone [key].

ZoneSpecification: Zone configuration. You can customize some code behaviors, such as intercepting log output and errors, to intercept log output and capture errors in the Zone.

runZoned( () { Future.delayed(Duration(seconds: 1)).then((e) => Future.error("xxx")); Future.delayed(Duration(seconds: 2)).then((e) => print('ABCD')); }, zoneSpecification: zoneSpecification (// intercept print print: (Zone self, ZoneDelegate parent, Zone zone, String line) { parent.print(zone, "Interceptor: $line"); }, // Intercept an unhandled asynchronous error handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Object error, StackTrace stackTrace) { parent.print(zone, '${error.toString()} $stackTrace'); },),);Copy the code

Zone intercepts and modifies Timer creation behavior

Intercept timer creation

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 (// intercept print print: (Zone self, ZoneDelegate parent, Zone Zone, String line) {parent. Print (Zone, "${datetime.now ()} -- Interceptor: $line"); }, // Intercept an unhandled asynchronous error handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Object error, StackTrace stackTrace) { parent.print( zone, '${DateTime. Now ()} -' + '${error. The toString ()} $stackTrace'); }, // intercept timer createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) { return parent.createTimer(self, duration, f);  },),);Copy the code

Print the result

20:01:51 flutter: 2022-03-03. 497784 - xxxError flutter: 20:01:52 2022-03-03. 495162 - Interceptor: FutureDelayedFunction flutter: 2022-03-03 20:01:53.491801 -- Interceptor: timer1F flutter: 2022-03-03 20:01:54.492453 -- Interceptor: timer2FCopy the code

Modify the Timer creation behavior

We can’t tell if it’s just thatTimerWhether it’s actually intercepted, let’s change the code

// Intercept the timer creation behavior and modify createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) { Timer newTimer = parent.createTimer( self, Duration(seconds: 3), () => print(' you are replaced ')); return newTimer; },Copy the code

Print the result

You need to get a new one, get a new one, a new one, a new one. You are intercepting a flutter: you are intercepting a flutter: You've been replacedCopy the code

Pay attention to

Because future.delayed is also internally implemented by Timer, it will also be blocked and modified.

Intercepting the behavior of microtask scheduling

Intercepting the scheduling behavior of microtasks

As we all know, microtasks are an important part of Dart’s event loop, and their scheduling behavior can also be intercepted within the zone.

runZoned( () { scheduleMicrotask(() => print('schedule Microtask')); }, zoneSpecification: zoneSpecification (// intercept print print: (Zone self, ZoneDelegate parent, Zone Zone, String line) {parent. Print (Zone, "${datetime.now ()} -- Interceptor: $line"); }, // Intercept the behavior scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void f()) { parent.scheduleMicrotask(self, f); }));Copy the code

Modify the scheduling behavior of microtasks

So can we intercept the schedule? Can we modify it?

Certainly can, otherwise I write these why 😄

Function Queue q = Queue(); runZoned( () { scheduleMicrotask(() => print('schedule Microtask1')); scheduleMicrotask(() => print('schedule Microtask2')); }, zoneSpecification: zoneSpecification (// intercept print print: (Zone self, ZoneDelegate parent, Zone Zone, String line) {parent. Print (Zone, "${datetime.now ()} -- Interceptor: $line"); }, // Block scheduleMicrotask scheduling behavior and modify scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone Zone, void f()) {q.a. d(f); if (q.length == 2) { while (q.length > 0) { final tempF = q.removeLast(); // Fetch function from the queue and execute parent.scheduleMicroTask (self, tempF); }}}));Copy the code

Print the result

141450 -- Interceptor: Interceptor: Microtask2 Flutter: 2022-03-03 20:20:23.145571 -- Interceptor: Schedule Microtask1Copy the code

Some other behavior interception

The zone can also intercept other behaviors of the space, such as fork, run, and registerCallback, via ZoneSpecification

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;
Copy the code

See the implementation principle of Stream through Zone

Details about Stream and how it works can be found here

The Stream object

  • StreamController: Used to control the Stream process and provides interfaces for creating various streams of events.
  • StreamSink: the entry point of an event,add.addStreamAnd so on.
  • Stream: the source of the event itself, usually used to listen for events or to transform events, as inlisten,where.
  • StreamSubscription: An event subscription object that is ostensibly used to manage subscribed operations such ascancel,

Stream working principle

  1. StreaminlistenWhen passed inonDataCallback, this callback is passed toStreamSubscriptionMedium, then passzone.registerUnaryCallbackTo register for_onDataThe function object
  2. StreamSinkWhen an event is added, theStreamSubscriptionIn the_sendDataMethod, and then through_zone.runUnaryGuarded(_onData, data)Obtained in 1_onDataFunction object, triggerlistenIs passed to the callback method

We see this in the source code as well

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; } // To save space, some code has been omitted // here, OnData = _registerDataHandler<T>(_zone, onData) Static void Function(T) _registerDataHandler<T>(Zone Zone, void Function(T)? handleData) { return zone.registerUnaryCallback<void, T>(handleData ?? _nullDataHandler); }Copy the code
//2. Sink added the StreamSubscription._sendData event and called _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); }Copy the code

So, zone. RegisterUnaryCallback and zone. RunUnaryGuarded what be respectively?

// Register a given callback in the zone to get a function object, ZoneUnaryCallback ZoneUnaryCallback<R, T> registerUnaryCallback<R, T>(R callback(T arg)); Typedef R ZoneUnaryCallback<R, T>(T arg);Copy the code
// Try to find a system for guarded <T>(void runUnaryGuarded (T argument)).Copy the code

Of course there are other methods such as registerCallback, registerBinaryCallback, the difference is the number of parameters

Now that we know how a Stream works through a zone, we can intercept and modify this behavior as well

runZoned( () { final streamController = StreamController(); streamController.sink.add('ABC'); streamController.stream.listen((event) { print(event); }); }, zoneSpecification: zoneSpecification (// intercept 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'; },),);Copy the code

Print the result

Flutter: 2022-03-03 21:45:15.872001 -- Interceptor: InterceptorCopy the code

Reference article:

Juejin. Cn/post / 684490…

API. Flutter – IO. Cn/flutter/dar…

Book. Flutterchina. Club/chapter2 / th…