Zone是什么
zone表示跨异步调用保持稳定的环境。代码总是在zone的上下文中执行,使用
Zone.current
查看当前zone,初始化主函数在Zone.root
中运行的。使用runZoned
创建新的zone,也可以使用zone.run
在先前使用zone.fork
创建现有区域的上下文中运行代码。
上面是官方说法,我们用可以这样理解:
Zone类为一个代码执行沙箱,不同沙箱的之间是隔离的,沙箱可以捕获、拦截或修改一些代码行为,如Zone中可以捕获日志输出、Timer创建、微任务调度的行为,同时Zone也可以捕获所有未处理的异常
程序启动进入main函数会通过runZonedGuarded
fork一个新的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:事件源本身,一般可用于监听事件或者对事件进行转换,如
listen
、where
。 - StreamSubscription:事件订阅后的对象,表面上用于管理订阅过各类操作,如
cancel
、
Stream工作原理
Stream
在listen
的时候传入onData
回调,这个回调会传入到StreamSubscription
中,之后通过zone.registerUnaryCallback
注册得到_onData
函数对象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.registerUnaryCallback
和zone.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: 偷梁换柱
复制代码
参考文章: