zoukankan      html  css  js  c++  java
  • Flutter异常搜集方案

    Flutter中的异常虽然不像Native那样会直接导致app crash,但也是不容忽视的. 比如widget构建过程因为抛出异常会导致界面灰屏,又或是某个网络请求解析失败,所以针对flutter我们也需要有一套规则来捕捉异常.

    Flutter中常见的异常

    Flutter中最最常见的一行就是空指针异常了,关于可选类型这块始终是Flutter这门语言的痛点之一,总之Flutter在数据结构转换这块和可选类型和移动端语言还是有很大差距的,希望官方快点优化吧。

    字典转换,类型推倒,文件读取,网络请求错误,布局溢出,数组越界等,插件通信异常等等,通常用Error和Exception来描述

    1.Error: 用于定义程序执行错误的对象

    Error (dart.core)
        AsyncError (dart.async) 
        JsonUnsupportedObjectError (dart.convert)
            JsonCyclicError (dart.convert)
        LateInitializationErrorImpl (dart._internal)
        FlutterError (assertions.dart)
        RemoteError (dart.isolate)
        UnderflowError (quiver.async)
        MatchError (quiver.testing.equality)
        FallThroughError (dart.core)
        CastError (dart.core)
        UnsupportedError (dart.core)
            UnimplementedError (dart.core)
        ConcurrentModificationError (dart.core)
        LateInitializationError (dart.core)
            LateInitializationErrorImpl (dart._internal)
        OutOfMemoryError (dart.core)
        AbstractClassInstantiationError (dart.core)
        NoSuchMethodError (dart.core)
        TypeError (dart.core)
        UnimplementedError (dart.core)
        NullThrownError (dart.core)
        AssertionError (dart.core)
            FlutterError (assertions.dart)
        StackOverflowError (dart.core)
        CyclicInitializationError (dart.core)
        StateError (dart.core)
        ArgumentError (dart.core)
            IndexError (dart.core)
            RangeError (dart.core)
    
    1. Exception: 由dartVM和自定义的dart代码手动抛出
    Exception (dart.core)
        DeferredLoadException (dart.async)
        TimeoutException (dart.async)
        IsolateSpawnException (dart.isolate)
        IOException (dart.io)
            HttpException (dart._http)
            WebSocketException (dart._http)
            FileSystemException (dart.io)
            ProcessException (dart.io)
            SignalException (dart.io)
            TlsException (dart.io)
            SocketException (dart.io)
            StdoutException (dart.io)
            StdinException (dart.io)
        PlatformException (message_codec.dart)
        MissingPluginException (message_codec.dart)
        TickerCanceled (ticker.dart)
        NetworkImageLoadException (image_provider.dart)
        PathException (path_exception.dart)
        UsageException (usage_exception.dart)
        SourceSpanException (span_exception.dart)
            MultiSourceSpanException (span_exception.dart)
            SourceSpanFormatException (span_exception.dart)
        ImageException (image_exception.dart)
        ParserException (petitparser.core.contexts.exception)
        XmlException (xml.utils.exceptions)
            XmlNodeTypeException (xml.utils.exceptions)
            XmlParserException (xml.utils.exceptions)
            XmlParentException (xml.utils.exceptions)
            XmlTagException (xml.utils.exceptions)
        ClosedException (closed_exception.dart)
        RemoteException (remote_exception.dart)
            _RemoteTestFailure (remote_exception.dart)
        FormatException (dart.core)
            ArchiveException (archive_exception.dart)
            ArgParserException (arg_parser_exception.dart)
            MultiSourceSpanFormatException (span_exception.dart)
            SourceSpanFormatException (span_exception.dart)
            XmlParserException (xml.utils.exceptions)
        IntegerDivisionByZeroException (dart.core)
        _Exception (dart.core)
    
    • ErrorException他们都代表了异常,但是从命名上来看似乎Error级别的日志更倾向于程序执行错误,不可预知的问题,如dart vm内部抛出的异常,数据严重级别较高的异常,甚至会让程序直接瘫痪; Exception更多的则是开发者自定义的异常,预知到的问题,并做相应的try catch去处理这种case.

    • 所以这就要求我们在程序开发时要有较强的安全意思,合理利用Exception定义函数可能出现的异常,并做好相应的try catch和备注

    • 对于Error类型的异常,要提前做好校验,如TypeError,确保代码的准确性,对于FutureStream这类的异步操作如果使用了await关键字,一定要使用try catch,否则它将会直接block后面的执行的代码,尤其是在程序启动阶段需要格外注意。

    Flutter中的异常捕捉

    通过阅读flutter framework层的源代码不难发现捕获异常主要由Isolate和Zone,还有Flutter.error,此外基于Zone封装而来的Future和Stream都可以进行异常捕捉.因为他们的依赖关系如下:

    Stream -> Future -> Zone -> ParentZone -> Isolate, Stream作为最小的单位,如果错误发生在Stream类,我们可以手动hook住优先拦截,如果不做处理将会层层传递, StreamFuture主要用于业务逻辑的编写,我们可以根据业务场景选择捕获和忽略,如果是有await这类的关键字,则一定要使用异常捕捉,不然它会直接抛出一行到当前的zone,会block住后面代码的执行.

    在启动时注册Isolate和currentZone,和FlutterError.onError事件能够获取到app所有的异常了,同时记录当前的异常堆栈。

    此外还可以进一步将异常堆栈数据发送至后台,或者是发送邮件到开发者,当然也可以直接通过设置后门开关,将异常堆栈的信息显示的展示在widget上。

    Flutter error捕捉

    • 它主要侧重于Flutter框架层的异常输出,如widget构建,图片读取,RenderObject绘制,点击事件的分发,测试框架,统计分析。相关的类如下:
    dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart:
    packages/flutter/lib/src/foundation/assertions.dart:
    packages/flutter/lib/src/gestures/binding.dart:
    packages/flutter/lib/src/gestures/pointer_router.dart:
    packages/flutter/lib/src/painting/image_provider.dart:
    packages/flutter/lib/src/widgets/fade_in_image.dart:
    packages/flutter/lib/src/widgets/framework.dart:
    packages/flutter/lib/src/widgets/image.dart:
    packages/flutter/lib/src/widgets/widget_inspector.dart:
    packages/flutter/test/rendering/rendering_tester.dart:
    packages/flutter_test/lib/src/binding.dart:
    
    • 使用方式
        FlutterError.onError = (FlutterErrorDetails details) async {
          _reportError(details.exception, details.stack, errorDetails: details);
        };
    

    Zone和Isolate的异常捕捉

    • 开辟一个新的Zone来捕捉,这种方案有一些局限性,它只能捕捉到Zone内部的异常,由于Zone的异常会逐级上抛给parent,所以我们可以利用这个特点,将程序的根Widget加入到当前的Zone,这样就能捕获到所有遗漏的异常的。
        runZonedGuarded<Future<void>>(() async {
          if (ensureInitialized) {
            WidgetsFlutterBinding.ensureInitialized();
          }
          runApp(rootWidget);
        }, (dynamic error, StackTrace stackTrace) {
          _reportError(error, stackTrace);
        });
    
    • Isolate为flutter的提供了独立的进程空间,程序运行也是依赖于Isolate的创建,所有的函数包括Zone.run都是在这个对应的Isolate下运行,此外系统提供了Isolate的几个钩子函数,方便我们拦截对应的回掉事件,通过注册ErrorListener就能实现全局错误的监听,
      Isolate.current.addErrorListener(new RawReceivePort((dynamic pair) async {
            var isolateError = pair as List<dynamic>;
            _reportError(
              isolateError.first.toString(),
              isolateError.last.toString(),
            );
          }).sendPort);
    

    处理异常数据

    打印的堆栈信息可以发送到指定的后台服务用于测试
    还可以以邮件的形式发送,便于问题排查
    另外Flutter提供了ErrorWidget的构造方法,它主要有以下2个使用场景,局限于Widget的build,在实际应用中,我们可以保存一个全局的GlobalKey用来存储context,这样就能在其他出现的异常部分也能创建错误的Widget并显示在界面上了。

    packages/flutter/lib/src/widgets/layout_builder.dart:
      102          } catch (e, stack) {
      103:           built = ErrorWidget.builder(
      104              _debugReportException(
    
      118        } catch (e, stack) {
      119:         built = ErrorWidget.builder(
      120            _debugReportException(
    
    packages/flutter/lib/src/widgets/sliver.dart:
      1628    FlutterError.reportError(details);
      1629:   return ErrorWidget.builder(details);
    

    总结

    1. 任何时候都不要对于类型的非空判定,只要不是百分百不为空就必须得预处理,设置默认值或者添加可选操作符号,如 optialValue?.propertyoptialValue ?? 0

    2. 合理的定义Expection定义可能出现的异常并捕获,比如解析,类型推导,缓存读取,例如

      try{
        final userInfo = await servie.getUserInfo();
      } on NetExpection catch (e, string){
      ...
      }
    3. 定义全局的异常捕捉

          FlutterError.onError = (FlutterErrorDetails details) async {
            ...
      };
      FlutterError.onError = (FlutterErrorDetails details) async {
      _reportError(details.exception, details.stack, errorDetails: details);
      };
      Isolate.current.addErrorListener(new RawReceivePort((dynamic pair) async {
      ...
      }).sendPort);
      runZonedGuarded<Future<void>>(() async {
      if (ensureInitialized) {
      WidgetsFlutterBinding.ensureInitialized();
      }
      runApp(rootWidget);
      }, (dynamic error, StackTrace stackTrace) {
      ...
      });
    4. 通过GlobalKey<NavigatorState>获取到当前的context,将错误信息通过widget输出到屏幕上;在实际使用的时候会比较频繁,建议设置个后门开关,选择性的弹出;继续扩展,发送邮件调用api,发送至后台;至此,一个简易的 bugReport就搜集完成了,如果不追求排版格式几十行代码就搞定了,这里推荐一个库catcher.

  • 相关阅读:
    Vue-router的实现原理
    get请求被浏览器跨域的同源策略请求机制拦截,但是get请求是否请求到了服务器呢
    合并两个有序链表
    JS实现链式调用 a().b().c()
    CSS知识点总结
    BK-信息查找、摘取
    radar图生成用户guideline
    【转】 mybatis 详解(七)------一对一、一对多、多对多
    【转】 mybatis 详解(六)------通过mapper接口加载映射文件
    【转】 mybatis 详解(五)------动态SQL
  • 原文地址:https://www.cnblogs.com/wwoo/p/flutter-yi-chang-sou-ji-fang-an.html
Copyright © 2011-2022 走看看