https://dart.dev/articles/archive/zones
概述
Zone,定义是 asynchronous dynamic extents ,Dart中和Zone有关的API是全局函数runZoned。
Zone的最常见的使用场景是用于捕获异步执行流程中的异常,但Zone提供的能力不仅这一项:
- 捕获异步执行流程中的异常,避免App因为未捕获的异常而终止,能够自行决定发生异常后的处理方案;
- 为Zone指定zone-local value,在Zone作用域中的代码可以通过Zone.current[
]访问zone-local value,有时这样做能简化代码,非常方便; - 为Zone作用域重写一系列方法,例如print()和scheduleMicrotask();
- 每当代码进入或离开Zone时可以执行某些操作。
基础概念
每一个runZoned函数都定义了一个新的Zone作用域,也直接称整个Zone作用域为一个Zone。Zone可以嵌套,可以有父子关系,在另一个Zone作用域中定义的Zone是另一Zone的child zone。
异常不可以在不同的Zone之间传播,一个异常只会被一个Zone捕获,在任何情况下都不会传播到另一个Zone中。就算开发者在不同的Zone中都写了对同一个Future的异常捕获及处理代码,这个错误也只会被异常抛出时所处的那个Zone处理,其他Zone中的相关代码根本不会被执行,就好像不存在一样。
注意这个表述:一个被抛出的异常,只会被一个Zone捕获,这个Zone就是抛出异常的代码执行时所处的Zone,开发者可以在一个外部Zone中定义一个抛出异常的Lambda,然后在一个嵌套的Zone中调用Lambda,这时候异常会被哪个Zone处理?按照前面的描述,异常会被lambda执行时所处的Zone捕获,而不是定义时所处的Zone捕获,这个行为在文档里有对应描述:https://dart.dev/articles/archive/zones#example-using-a-stream-with-runzoned
zone-local存储
有时候要在异步流程里记录一些和当前流程关联的信息,这种场景下使用绑定了zone-local存储的Zone会比较简单方便,不需要开发者自己写逻辑维护数据结构。
https://dart.dev/articles/archive/zones#storing-zone-local-values
能力覆盖
调用runZoned函数时可以指定zoneSpecification参数,用于覆盖一部分支持覆盖的Zone能力。
可以覆盖的能力包括:
- 一般用户不用关心的:
- fork child Zone
- 为Zone注册callback
- 一般用户可能会用到的:
- 覆盖函数定义,下面的函数被调用时会寻找在当前Zone上指定的版本,因此可以被覆盖
- microtask和timer
- 处理未捕获异常
- 覆盖函数定义,下面的函数被调用时会寻找在当前Zone上指定的版本,因此可以被覆盖
在覆盖函数定义时,需要定义一个interceptor函数,当调用被覆盖的函数时,调用会被转发到这个interceptor函数,interceptor函数在原函数参数之前增加了三个参数:
- self,Zone类型,就是runZoned当前正在定义的Zone;
- parent,ZoneDelegate类型,是self的parent zone;
- zone,Zone类型,这个参数代表的是操作发生时所处的Zone;
当在Zone中调用了某个被覆盖的函数时,首先会触发当前Zone定义的对应interceptor函数,这时zone参数就是self,但如果当前的Zone没有定义这个函数,或者定义了但希望继续让parent zone来处理,就需要调用 parent 的对应方法将调用委托给parent zone的interceptor函数处理,这时候必须将第三个参数zone原封不动的传给parent。