返回顶部
首页 > 资讯 > 移动开发 >Flutter Zone异常处理方法及基本原理
  • 574
分享到

Flutter Zone异常处理方法及基本原理

Flutter Zone异常处理Flutter Zone 2023-01-18 12:01:03 574人浏览 安东尼
摘要

目录1. 认识Zone1.1 ZoneValues1.2 ZoneSpecification1.3 通过runZoned快速创建Zone2. 异步基本原理和异常捕获3. Handle

1. 认识Zone

Zone像一个沙盒,是我们代码执行的一个环境。

我们的main函数默认就运行在Root Zone当中。

子Zone的构造有点像linux中的进程,它支持从当前的Zone中Fork出一个子Zone:

Zone myZone = Zone.current.fork(...)

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

  • ZoneSpecification
  • ZoneValues

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

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

类似Linux 通过Fork创建的 myZone默认也具有源Zone的ZoneSpecification和ZoneValues。

1.1 ZoneValues

和Linux类似地,当Zone做Fork的时候,会将父Zone所持有的ZoneSpecification、ZoneValues会继承下来,可以直接使用。并且是支持追加的,secondZone在firstZone的基础之上,又追加了extra_values属性,不会因为secondZone的ZoneValues就导致name属性被替换掉。

Zone firstZone = Zone.current
    .fork(specification: zoneSpecification, zoneValues: {"name": "bob"});
Zone secondZone = firstZone.fork(zoneValues: {"extra_values": 12345});
secondZone.run(() {
  print(secondZone["name"]); // bob
  print(secondZone["extra_values"]); // 12345
}

我们可以使用Zone.current,访问当前的代码执行在哪一个Zone当中,默认情况下,代码执行在Root Zone当中,后续会根据需求分化出多个Zone,也可以使用Zone.root访问到RootZone的实例。

1.2 ZoneSpecification

和ZoneValues不同,ZoneValues支持追加不同的属性,而ZoneSpecification只支持重写,并且RootZone已经预设好了一系列的Zone中运行的规则,一旦我们重写了ZoneSpecification的一些方法回调,之前的一些功能可能会消失。

这种基于配置对象的扩展方法和基于继承的子类的重写是不一样的,该方法具有更强的扩展性,但是在类似于特性保留的机制上就明显不如继承来的方便,一旦重写某个方法,该方法原有的特性需要重新实现一遍,否则原有的功能会消失。

如果你只重写了其中的一个方法,那么其他方法不会被覆盖,依然采用默认配置。

ZoneSpecification的构造方法中,包含非常多的参数,其中绝大多数都是以回Callback形式出现,首先来看看run系列的方法:

RunHandler? run,
RunUnaryHandler? runUnary,
RunBinaryHandler? runBinary,

其实这三个方法的区别在于参数,我们看看RunHandlerRunUnaryHandlerRunBinaryHandler的具体定义:

typedef RunHandler = R Function<R>(
    Zone self, ZoneDelegate parent, Zone zone, R Function() f);​
typedef RunUnaryHandler = R Function<R, T>(
    Zone self, ZoneDelegate parent, Zone zone, R Function(T arg) f, T arg);​
typedef RunBinaryHandler = R Function<R, T1, T2>(Zone self, ZoneDelegate parent,
    Zone zone, R Function(T1 arg1, T2 arg2) f, T1 arg1, T2 arg2);

不难发现,三者除了固定的:selfparentzone之外,区别就在于

UnaryHandlerBinaryHandler提供了分别提供了一个参数、两个参数的选项。这个参数的作用是提供给另外一个参数:f,类型是一个Function,显然它是我们调用Zone.run方法传进来的body参数,以RunHandler为例,我们对run做出如下的定义:

Zone secondZone = firstZone.fork(
    zoneValues: {"extra_values": 12345},
    specification: ZoneSpecification(
      run: <int>(self, parent, zone, f) {
        int output = f();
        return output;
      },
    ));

我们在外部调用secondZone.run(()=>...)时,就可以在run方法的开始、结尾做一些其他的事情了:

secondZone.run(body);// 执行
run: <int>(self, parent, zone, f) {
    // 1.
    print("before");
    int output = f();// 这里的f就是body,它是可执行的
    print("after");
    return output;
    // 2.
},

直觉告诉我,1/2之间的代码应该是在Second Zone中执行的,但是打印一下Zone.root,我们发现实际上是在Root Zone中执行的,二者的HashCode相同。

// 在body内部打印的
body internal Zone:195048515 // 
Root Zone:195048515 // 
first Zone:700091970
second Zone:707932504

大致上去跟了一下代码,发现默认的run方法的实现,被我们新编写的run参数覆盖掉了,所以会导致本该在secondZone中执行的body结果在Root Zone中执行。然后再run参数的注释里,发现了这么一段话:

Since the root zone is the only zone that can modify the value of [current], custom zones intercepting run should always delegate to their parent zone. They may take actions before and after the call.

大致上的意思是:

因为Root Zone是唯一能够修改Zone.current参数的Zone,所以自定义的Zone拦截run方法必须总是将方法交给它们的父Zone去代为处理。而run自己可以在run调用之前或者之后采取一些行动。

也就是说,我们不能直接return f();,而要把f()委托给parent来执行,像这样:

secondZone.run(body);// 执行
​
run: <int>(self, parent, zone, f) {
    // 1.这里执行在Root Zone
    print("before");
    Function output = parent.run(self, () {
      // 这里执行在second Zone
      return f(); 
    });
    print("after");
    return output;
    // 2.
},

委托之后,由Root Zone去做统一的调度、Zone的切换。这样,我们再去打印一下执行的Zone,发现正常了,secondZone.run方法(其实是被ZoneSpecification中的run指定的方法)的Zone仍然是Root Zone,而我们传递过去的任务被执行在了self之中,也就是SecondZone 当中,符合我们的预期:

current zone:692810917
body internal Zone:558922284
Root Zone:692810917
firstZone Zone:380051056
second Zone:558922284

额外地,可以牵出ZoneDelegate是做什么的,它允许子Zone,访问父Zone的一些方法,与此同时保留自己额外的一些行为:绿框表示额外的行为,当Zone A调用Zone B的run时,它通常执行在调用者的Zone当中,也就是ZoneA。

1.3 通过runZoned快速创建Zone

dart提供了runZoned方法,支持Zone的快速创建:

R runZoned<R>(R body(),
    {Map<Object?, Object?>? zoneValues,
    ZoneSpecification? zoneSpecification,
    @Deprecated("Use runZonedGuarded instead") Function? onError}) {

其中body、zoneValues、zoneSpecification都是老熟人了,关键在于它对于run方法的处理:

/// Runs [body] in a new zone based on [zoneValues] and [specification].
R _runZoned<R>(R body(), Map<Object?, Object?>? zoneValues,
        ZoneSpecification? specification) =>
    Zone.current
        .fork(specification: specification, zoneValues: zoneValues)
        .run<R>(body);

如果我们不显式地传递一个ZoneSpecififation进来,fork时传进去的是null,自然不会导致Specification被我们重写,因此代码能按照Dart默认的实现方式,运行在一个新的、Fork出来的Zone当中(至少能看出不是Root Zone):

runZoned(() {
 &nbsp;print("body internal Zone:" + Zone.current.hashCode.toString());
 &nbsp;print("Root Zone:" + Zone.root.hashCode.toString());
});
​
// 打印结果
body internal Zone:253994638
Root Zone:1004225004

但是如果你像之前手动fork一样,指定它的ZoneSpecification,又不把f委托给上层Zone处理,那么就会:

body internal Zone:44766141
Root Zone:44766141

2. 异步基本原理和异常捕获

默认大家已经知道什么事单线程模型,以及Future的执行机制了,Dart的单线程模型和事件循环机制。

来看看这段简单的代码:

void asyncFunction() {
  print('1');
  Future((){
    print('2');
  }).then((e) {
    print('3');
  });
  print('4');
}

大家都知道,这段代码的输出的顺序是:1423,它的大致流程是:

print 1
创建一个Future,并扔到Event Queue末尾
print 4
// 从Event Queue中取出,并执行下一个消息......
执行Future构造函数中的方法:->  print 2
print 2执行完成,即Future完成,回调它的then: ->  print 3

我们为他加上await和async,并稍作改造,写成async、await的同步形式,同时删掉4

void asyncFunction() async {
  print('1');
  await Future(() {
    print('2');
  });
  print('3');
  print('4');
}

它的输出是:1234,他所做的是:

print 1;
创建一个Future@1,并扔到Event Queue末尾;
// 从Event Queue中取出,并执行下一个消息......
取出Future@1,立刻执行它构造中的方法: -> print 2;
并将之后的代码打包,重新放到Event Queue的末尾(这里一般会等待IO完成,之后就会去执行和这个回调)
执行完成之后,执行之后的代码:
print 3;
print 4;

今天我们不是讨论Async和Await的,就不再展开。

但是大家可以比较一下这两次调用,发现第二种和第一种相比,第二种调用的代码是会 “回来” 继续执行的,而第一种的Future创建不搭配await/async的就好比脱缰的野马,这种代码我们并不关心它的结果,自然也不要求代码在此await,执行起来就无法控制,但在Dart中我们也无法通过try/catch捕获异常。

关键点在于:async + await是会回到异步阻塞的代码处(await处)执行的。既然回来了,那么try/catch自然而然是能够继续监听是否有异常抛出的。

而第一种的Future,即使我们在外面包裹上了try/catch,而Future的代码却是在未来的某个时间内,在Event Queue的末尾的某个位置解包执行的,上下文和try/catch所在的代码并没什么关联,自然不能拦截到异常。我们可以从Stack Trace中看看这两种代码抛出异常时的执行栈:

左侧是一种方法的执行栈,throwExceptionFunction()项相关的栈帧已经消失了,异常自然没有办法通过throwExceptionFunction()中的try/catch进行捕获。

问题就出在这了, 对于这种错误我们是否有办法去捕获呢?

答案仍然还是是今天的主题 : Zone。

3. HandleUncaughtErrorHandler

虽然异步代码的执行,可能会横跨多个Event,让代码前后的上下文失去联系,导致异常无法被正常捕获,但是它仍然在一个Zone之内。

就像仙剑奇侠传三中,李逍遥对景天说“邪剑仙(Exception)虽身处六界(Event)之外却是在道(Zone)之内”。

Zone提供了一些特殊的编程接口,让我们能够对当前这个Zone沙盒内的未捕获的异常进行集中处理。

它就是HandleUncaughtErrorHandler。作为ZoneSpecification的一个参数,它支持将Zone当中未被处理的错误统一归到这里进行处理(Dart和Java不一样,Dart的异常本身通常不会导致程序的退出),因此,常使用HandleUncaughtErrorHandler来做异常的统计、上报等等。

另外,因为Dart执行环境的单线程 + 事件队列机制本身,Dart的try/catch对于异步代码是无法处理的,如下的代码异常会穿透(或者说根本不经过)try/catch后抛出,会在控制台中留下红色的报错。

// Zone.run(()=>throwExceptionFunctino());
void throwExceptionFunction() {
  try {
    Future.delayed(const Duration(seconds: 1))
        .then((e) => throw("This is an Exception"));
  } catch (e) {
    print("an Exception has been Captured: ${e.toString()}");
  }
}

显然,异步的异常并没有被捕获:

Unhandled exception:
This is an Exception
#0      throwExceptionFunction.<anonymous closure> (file:///Users/rEd/ideaProjects/dartProjs/zone/bin/zone.dart:140:22)
#1      _rootRunUnary (dart:async/zone.dart:1434:47)
#2      _CustomZone.runUnary (dart:async/zone.dart:1335:19)
<asynchronous suspension>

但是我们改成这样呢?

void throwExceptionFunction() async{
  try {
    await Future.delayed(const Duration(seconds: 1))
        .then((e) => throw ("This is an Exception"));
  } catch (e) {
    print("an Exception has been Captured: ${e.toString()}");
  }
}

我们对异步的方法throwExceptionFunction()加了await/async关键字。我们会发现异常,又能被捕获了:

an Exception has been Captured: This is an Exception
Process finished with exit code 0

其实上文已经提到了是异步时Dart代码上下文切换的原因,这里也不做过多的赘述了,我们像这样,将我们的App包裹在一个额外的Zone里面,并在它的HandleUncaughtErrorHandler相关方法做如下定义:

void main() {
  runZoned(() => runApp(const MyExceptionApp()),
      zoneSpecification: ZoneSpecification(
          // print: (self, parent, zone, line) {},
          handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,
              Object error, StackTrace stackTrace) {
            // 同样将print代理给上层Zone,这样就可以在上层捕获到这些异常了。
            parent.print(self," ### \n $stackTrace \n ### ");
          }));
}

随便找个地方抛出个异常:

floatingActionButton: FloatingActionButton(
  onPressed: () => Future((){
    throw ("ERROR!");
  }),
),

我们可以发现,异常在此处被HandleUncaughtErrorHandler集中捕获了。

或者我们也可以使用runZoned自带的回调来处理,而不是去自己重写ZoneSpecification:

// runZonedGuarded替换runZoned
runZonedGuarded(() => runApp(const MyExceptionApp()),
    (Object error, StackTrace stack) {
  print('stack: $stack');
});

不过,我们去它内部看看,其实它还是HandleUncaughtErrorHandler实现的。

注意:如果重写了ZoneSpecification的run相关的方法,可能会导致当前的Zone无法捕获到异常,就像1.中所说的那样,基于配置类的重写将原有特性覆盖掉了,导致当前代码并不一定在我们直觉认为的Zone中执行。

这需要编写者自己去解决这个问题,所以,如果没有特殊的需求,一般不给Zone传递ZoneSpecification选项,如果要传递,需要去实现它,以保证相关的功能特性可用。

以上就是Flutter Zone异常处理方法及基本原理的详细内容,更多关于Flutter Zone异常处理的资料请关注编程网其它相关文章!

--结束END--

本文标题: Flutter Zone异常处理方法及基本原理

本文链接: https://lsjlt.com/news/178100.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Flutter Zone异常处理方法及基本原理
    目录1. 认识Zone1.1 ZoneValues1.2 ZoneSpecification1.3 通过runZoned快速创建Zone2. 异步基本原理和异常捕获3. Handle...
    99+
    2023-01-18
    Flutter Zone异常处理 Flutter Zone
  • Java.lang.NumberFormatException 异常及处理方法
    Java.lang.NumberFormatException 异常及处理方法 引言: 在Java编程中,我们常常遇到各种异常情况。其中之一是 java.lang.NumberFormatExcept...
    99+
    2023-10-28
    java python 开发语言 Java
  • Java异常类型及处理方法
    这篇文章主要介绍“Java异常类型及处理方法”,在日常操作中,相信很多人在Java异常类型及处理方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java异常类型及处理方法”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-20
  • Python异常及处理方法总结
    调试Python程序时,经常会报出一些异常,异常的原因一方面可能是写程序时由于疏忽或者考虑不全造成了错误,这时就需要根据异常Traceback到出错点,进行分析改正;另一方面,有些异常是不可避免的,但我们可以对异常进行捕获处理,防止程序终...
    99+
    2023-01-31
    异常 方法 Python
  • Java异常处理try catch的基本用法
    try catch介绍 我们编译运行程序出错的时候,编译器就会抛出异常。抛出异常要比终止程序灵活许多,这是因为Java提供了一个“捕获”异常的的处理器(处理器)对异常情况进行处理。 ...
    99+
    2024-04-02
  • 详解SpringBoot异常处理流程及原理
    异常处理流程 执行目标方法,目标方法运行期间有任何异常都会被catch捕获,并标志当前请求结束,dispatchException抛出异常 进入视图解析流程,并渲染页面,发生异常时...
    99+
    2024-04-02
  • 常见的Python异常及处理方法总结
    目录一、错误与异常1.什么是错误2.什么是异常3.回溯信息二、常见异常常见异常类三.异常处理1.异常处理2.简单的异常处理格式3.执行顺序4.except分支可以有多个5.执行顺序6...
    99+
    2024-04-02
  • 关于python常见异常以及处理方法
    一、常见的异常 1、NameError 未定义变量异常 print(a) # 输出:NameError: name 'a' is not defined 2、IndexError 下...
    99+
    2023-05-16
    python常见异常 python异常处理
  • Java 基础语法 异常处理
    目录1. 异常的背景1.1 邂逅异常1.2 异常和错误1.3 Java 异常的体系(含体系图)1.4 异常的核心思想1.5 异常的好处2. 异常的基本用法2.1 捕获异常2.1.1 ...
    99+
    2024-04-02
  • Java异常类型介绍及处理方法
    前言: Java异常,大家都很熟悉。但是对于具体怎么分类的,JVM对其怎么处理的,代码中怎么处理的,应该怎么使用,底层怎么实现的等等,可能就会有些不是那么清晰。本文基于此详细捋一下异...
    99+
    2024-04-02
  • Python基础知识方法重写+文件处理+异常处理
    目录一、方法重写二、运算符重载三、文件处理打开关闭文件文件读写四、异常处理异常抛出异常最后前言; Python基础知识+结构+数据类型 Python基础学习列表+元组+字典+集合 P...
    99+
    2024-04-02
  • Python基础:异常处理
    如何处理异常   无论发生什么情况,finally block 中的语句都会被执行,哪怕前面的 try 和 excep block 中使用了 return 语句。 import sys try: f = open('file....
    99+
    2023-01-31
    异常 基础 Python
  • ASP.NET MVC基于异常处理的解决方法
    今天就跟大家聊聊有关ASP.NET MVC基于异常处理的解决方法,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。EntLib的异常处理应用块(Exception Handling Ap...
    99+
    2023-06-17
  • Python异常类型以及处理方法汇总
    目录前言1 异常类型1.1 Python内置异常1.2 requests模块的相关异常1.3 用户自定义异常2. 异常捕获2.1 捕获所有异常2.2 捕...
    99+
    2024-04-02
  • Java程序常见异常及处理方法有哪些
    Java程序常见的异常包括NullPointerException、ArrayIndexOutOfBoundsException、N...
    99+
    2023-08-18
    Java
  • Java异常处理try catch的基本使用
    目录1. 异常1.1 try…catch异常处理1.2 多catch并行处理1.3 throw和throws 关键字的使用1.4 finally代码块1.5 Runti...
    99+
    2024-04-02
  • Java常见异常及处理方式总结
    目录一、概述二、异常分类三、声明及抛出四、捕获异常五、捕获多个异常六、自定义异常七、异常堆栈一、概述 异常指不期而至的各种状况,它在程序运行的过程中发生。作为开发者,我们都希望自己写...
    99+
    2024-04-02
  • Python基础入门之魔法方法与异常处理
    目录一.魔法方法1.属性访问2.描述符3.定制序列5.生成器二.异常处理1.异常类型2.try-except语句3.try-finally语句4.raise语句5.丰富的else语句...
    99+
    2024-04-02
  • SpringBoot异常处理的原理分析
    这篇文章主要介绍“SpringBoot异常处理的原理分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringBoot异常处理的原理分析”文章能帮助大家解决问题。异常处理流程执行目标方法,目标方...
    99+
    2023-06-08
  • Python包装异常处理方法
    目录前言一、异常1.1、忽略1.2、捕获1.3、异常链1.4、自定义1.5、抛出二、异常的显示方式2.1、打印信息2.2、控制台警告2.2、存储文件前言 相比java,python的...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作