最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Flutter | 异常处理

    正文概述 掘金(345丶)   2021-01-20   361

    Flutter | 异常处理

    本文示例代码

    在了解 Flutter 异常捕获之前需要先了解一下 Dart 的异常处理以及 Dart 的单线程模型,只有知道了代码的执行流程,我们才能只要该在什么地方去捕获异常

    Dart 中的异常

    Dart 可以抛出和捕获异常,如果没有被捕获,则会抛出,最终导致程序终止运行

    和 Java 不同,Dart 中的所有异常时非检查异常,方法不会声明它们抛出的异常,也不要求捕获任何异常

    Dart 提供了 Exception 和 Error 类型,以及一些子类型。也可以自定义异常类型。此外,Dart 程序可以抛出任何 非null 对象,不仅限 Exception 和 Error 对象。

    • throw

      throw FormatException('Expected at least 1 section');
      
    • 抛出任意的对象

      throw 'Out of llamas!';
      
    • 在使用表达式的地方抛出异常

      void distanceTo(Point other) => throw UnimplementedError();
      
    • Catch

      捕获异常

      try {
        breedMoreLlamas();
      } on OutOfLlamasException {
        // 一个特殊的异常
        buyMoreLlamas();
      } on Exception catch (e) {
        // 其他任何异常
        print('Unknown exception: $e');
      } catch (e) {
        // 没有指定的类型,处理所有异常
        print('Something really unknown: $e');
      }
      

      catch 函数可以指定 1到2个参数,第一个为异常对象,第二个为堆栈信息(StackTrace对象)

      try {
        // ···
      } on Exception catch (e) {
        print('Exception details:\n $e');
      } catch (e, s) {
        print('Exception details:\n $e');
        print('Stack trace:\n $s');
      }
      

      如果部分异常需要处理,可使用 rethrow 将异常重新抛出

      void misbehave() {
        try {
          dynamic foo = true;
          print(foo++); // Runtime error
        } catch (e) {
          print('misbehave() partially handled ${e.runtimeType}.');
          rethrow; // Allow callers to see the exception.
        }
      }
        
      void main() {
        try {
          misbehave();
        } catch (e) {
          print('main() finished handling ${e.runtimeType}.');
        }
      }
      
    • finally

      无论是否 try 住异常,finally 都会执行。如果 try 住异常,会先执行对应的 catch,最后执行 finally

    Dart 单线程模型

    如果程序中发送异常且没有被捕获,那么程序将会被终止,但是这在 Dart 中则不会,根本原因是因为和他的运行机制有关系。例如 java 是多线程模型的编程语言,任意一个线程触发异常且异常没有被捕获时,就会导致整个进程退出,但是 Dart 不会,因为 Dart 是单线程模型,运行机制很相似,但是还是有一些区别,下面根据一张图来大致看一下(翻译自官方提供的图):

    Flutter | 异常处理

    ​ Dart 在单线程机制中是以消息循环机制来运行的,其中包含两个任务队列,一个是 微任务队列 microtask queue,一个是事件队列 event queue 。从图中可知道,微任务队列高于事件队列

    ​ 现在来介绍一下 Dart 线程的运行过程,如上图,入口 main 执行完成之后,消息循环机制就会启动,首先会按照先进先出的顺序逐个执行微任务队列中的任务,事件执行完成之后程序便会退出,但是在事件任务执行的过程中也可以插入新的微任务和事件任务,这种情况下整个县城的执行过程便是一直在循环,不会退出,而在 Flutter 中,主线程的执行过程正是如此,永不终止

    ​ 在 Dart 中,所有的外部事件任务都在事件队列中,如 IO,计时器,点击,以及绘制事件等。而微任务通常来源于 Dart 内部,并且微任务非常少,之所以如此,是因为微任务队列的优先级更高,如果微任务·太多,执行时间就会越久,时间队列的延迟也就越久,对于 UI 来说最直观的感受就是 卡,所以必须要保证微任务队里不会太长。我们可以通过 Future.microtask()方法向微任务队列添加一个任务

    Flutter 异常捕获

    Flutter 框架异常捕获

    Flutter 框架为我们在很多地方都进行了异常补货,例如,当布局发生越界或者不规范时,Flutter 会自动弹出一个错误页面,

    这是因为 Flutter 已经在 build 方法时添加了异常捕获,源码如下:

    @override
    void performRebuild() {
    ........
      try {
        built = build();
      } catch (e, stack) {
        _debugDoingBuild = false;
        built = ErrorWidget.builder(
          _debugReportException(
            ErrorDescription('building $this'),
            e,
            stack,
            informationCollector: () sync* {
              yield DiagnosticsDebugCreator(DebugCreator(this));
            },
          ),
        );
      } finally {
        // We delay marking the element as clean until after calling build() so
        // that attempts to markNeedsBuild() during build() will be ignored.
        _dirty = false;
        assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
      }
      ......
    }
    

    可以看到,在发生异常时,Flutter 的处理方式是弹一个 ErrorWidget,那如果我们自己想要捕获异常并上报到报警平台的话应该怎么做?

    我们进入 _debugReportException() 方法看看:

    FlutterErrorDetails _debugReportException(
      DiagnosticsNode context,
      Object exception,
      StackTrace? stack, {
      InformationCollector? informationCollector,
    }) {
      //构建错误对象  
      final FlutterErrorDetails details = FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'widgets library',
        context: context,
        informationCollector: informationCollector,
      );
      //报告错误
      FlutterError.reportError(details);
      return details;
    }
    

    继续查看 FlutterError.reportError()

    /// Calls [onError] with the given details, unless it is null.
    static void reportError(FlutterErrorDetails details) {
      assert(details != null);
      assert(details.exception != null);
      if (onError != null) {
        onError!(details);
      }
    }
    

    可以发现 onError 是一个静态属性,他有一个默认的处理方法 dumpErrorToConsole ,这个方法定义在 assertions.dart 中,继续跟踪 onError 就可以找到这个方法

    通过上面的代码我们可以知道,如果要将自己捕获的异常上报的话可以直接调用 reportError()方法,这个方法本身是一个静态方法,如下所示:

    class ExceptionTest extends StatelessWidget {
      final error =
          (FlutterErrorDetails detail) => FlutterError.reportError(detail);
    
      @override
      Widget build(BuildContext context) {
        try {
          throw NullThrownError();
        } catch (e, s) {
          var details = FlutterErrorDetails(
            exception: e,
            stack: s,
            library: "lib.exception",
          );
          error(details);
        }
        // TODO: implement build
        throw UnimplementedError();
      }
    }
    

    最终,自己抛出的异常会上报到系统,然后就会弹到 ErrorWidget 中,并且会在控制台打印堆栈信息:

    Flutter | 异常处理

    runZoned()

    Dart 中有一个 runZoned() 方法,可以给执行的对象指定一个 Zone,Zone 表示一个代码执行的环境范围,为了方便理解,读者可以将 Zone 类比作为一个代码执行沙箱,不同沙箱直接是隔离的。沙箱可以捕获,拦截或修改一些代码行为,如 Zone 中可以捕获日志的输出,Timer 创建,微任务调用的行为,同时 Zone 也可以捕获所有未处理的异常,下面看一下 runZoned() 方法的定义:

    R runZoned<R>(R body(),
        {Map<Object?, Object?>? zoneValues,
        ZoneSpecification? zoneSpecification,
        Function? onError}) {
    }
    
    • zoneValues:Zone 的私有数据,可以通过实例 zone[key] 获取,可以理解为每个沙箱的私有数据

    • zoneSpecification:Zone 的一些配置,可以自定义一些行为,比如拦截日志输出行为等,

      runZoned(() {
        print('hello world');
      }, zoneSpecification: new ZoneSpecification(
        print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
          parent.print(zone, "Intercepted $line");
        },
      ));
      

      上面会输入如下:I/flutter ( 4182): Intercepted hello world ;如果不调用 parent.print ,则不会打印

      这样一来,我们 app 中所有调用 print 方法输入日志的行为都会被拦截,通过这种方式,我们也可以在应用中记录日志,等到应用触发未捕获的异常时,将以此和日志进行上报

    • onError

      Zone 中未捕获以此处理回调。如果开发者提供了 onError 回调或者通过 ZoneSpecification.handleUncaughtError 指定了错误回调,那么这个 zone 将会变成一个 error-zone ,该 error-zone 中发生未捕获的异常(无论是同步还是异步)时都会调用开发者提供的回调,如:

      runZoned(() {
        print('hello world');
        throw NullThrownError();
      }, zoneSpecification: new ZoneSpecification(
        print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
          parent.print(zone, "Intercepted $line");
        },
      ), onError: (Object obj, StackTrace stack) {
        var details = FlutterErrorDetails(
          exception: obj,
          stack: stack,
          library: "lib.exception",
        );
        error(details);
      });
      

      这样一来,结合上面的 FlutterError.onError 我们就可以捕获我们 Flutter 应用中的全部错误了。

      需要注意的是 error-zone 内部发生的错误是不会跨越 error-zone 边界的,如果想跨越 error-zone 边界去捕获异常,可以通过共同的源 zone 来捕获,如:

      var future = new Future.value(499);
      runZoned(() {
          var future2 = future.then((_) { throw "error in first error-zone"; });
          runZoned(() {
              var future3 = future2.catchError((e) { print("Never reached!"); });
          }, onError: (e) { print("unused error handler"); });
      }, onError: (e) { print("catches error of first error-zone."); });
      

    最终,我们异常捕获的代码大致如下

    void main() {
      runZoned(() => runApp(MyApp()), zoneSpecification: ZoneSpecification(
          print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
        collectLog(line); //收集日志
      }), onError: (Object obj, StackTrace stack) {
        makeDetails(obj, stack); //构建错误信息,并上报平台
      });
    }
    


    起源地下载网 » Flutter | 异常处理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元