zoukankan      html  css  js  c++  java
  • 技术实践第二期|Flutter异常捕获

    作者:友盟+技术专家 彦克

    一、背景

    应用性能稳定是良好用户体验中非常关键的一环,为了更好保障应用性能稳定,异常捕获在保证线上产品稳定中扮演着至关重要的角色。我们团队在推出了U-APM移动应用性能监控的产品后,帮助开发者定位并解决掉很多线上的疑难杂症。随着使用人数的增多,关注度的提高,在拜访客户和开发者的留言中,很多开发者都提出希望该产品可以支持flutter框架的异常捕获。本身我并没有做过flutter开发,所以主要是通过在现有产品能力基础上做插件实现异常的上报,这篇文章就记录我学习flutter错误处理的过程和遇到的问题。

     

    二、Flutter异常

    Flutter 异常指的是,Flutter 程序中 Dart 代码运行时意外发生的错误事件。

     

    三、Flutter异常特点

    Dart是单进程机制,所以在这个进程中出现问题时仅仅会影响当前进程,Dart 采用事件循环的机制来运行任务,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的,各个任务的运行状态是互相独立的。

    如:我们可以通过与 Java 类似的 try-catch 机制来捕获它。但与 Java 不同的是,Dart 程序不强制要求我们必须处理异常。

    四、Flutter异常分类

    在Flutter开发中,根据异常来源的不同,可以将异常分为Framework异常和App异常。Flutter对这两种异常提供了不同的捕获方式,Framework异常是由Flutter框架引发的异常,通常是由于错误的应用代码造成Flutter框架底层的异常判断引起的。而对于App异常,就是应用代码的异常,通常由未处理应用层其他模块所抛出的异常引起。根据异常代码的执行时序,App 异常可以分为两类,即同步异常和异步异常。

     


    五、捕获方式

    1.App 异常的捕获方式

    捕获同步异常使用try-catch 机制:

    // 使用 try-catch 捕获同步异常
    try {
      throw StateError('This is a Dart exception.');
    }
    catch(e) {
      print(e);
    }
    

    捕获异步异常使用Future 提供的 catchError 语句:

    // 使用 catchError 捕获异步异常
    Future.delayed(Duration(seconds: 1))
        .then((e) => throw StateError('This is a Dart exception in Future.'))
        .catchError((e)=>print(e));
    

      

    看到这里估计很多人心里会问,就不能有一种方式既可以监控同步又可以监控异步异常吗?

    答案是有的。

    Flutter 提供了 Zone.runZoned 方法来管理代码中的所有异常。我们可以给代码执行对象指定一个 Zone,在 Dart 中,Zone 表示一个代码执行的环境范围,其概念类似沙盒,不同沙盒之间是互相隔离的。如果我们想要观察沙盒中代码执行出现的异常,沙盒提供了 onError 回调函数,拦截那些在代码执行对象中的未捕获异常。废话不多说,

    Show me the code

    runZoned(() {
      // 同步异常
      throw StateError('This is a Dart exception.');
    }, onError: (dynamic e, StackTrace stack) {
      print('Sync error caught by zone');
    });
    
    runZoned(() {
      // 异步异常
      Future.delayed(Duration(seconds: 1))
          .then((e) => throw StateError('This is a Dart exception in Future.'));
    }, onError: (dynamic e, StackTrace stack) {
      print('Async error aught by zone');
    });
    

    为了能够集中捕获 Flutter 应用中的未处理异常,最终我把main函数中的 runApp 语句也放置在 Zone 中。这样在检测到代码中运行异常时,就能根据获取到的异常上下文信息,进行统一处理了:

    runZoned<Future<Null>>(() async {
      runApp(MyApp());
    }, onError: (error, stackTrace) async {
     //Do sth for error
    });
    

      

    2.Framework异常捕获方式

    Flutter 框架为我们在很多关键的方法进行了异常捕获。如果我们想自己上报异常,只需要提供一个自定义的错误处理回调即可,如:

    void main() {
      FlutterError.onError = (FlutterErrorDetails details) {
        reportError(details);
      };
     ...
    }

    有没有一套从天而降的代码,能够统一处理以上异常呢?

    3.总结(一套代码捕获所有异常)

    runZonedGuarded(() async {
        WidgetsFlutterBinding.ensureInitialized();
    FlutterError.onError = (FlutterErrorDetails details) {
          myErrorsHandler.onError(details.exception,details.stack);
        };
        runApp(MyApp());
      }, (Object error, StackTrace stack) {
        myErrorsHandler.onError(error, stack);
      });

    代码中出现了一句,上诉从没有出现过的代码即WidgetsFlutterBinding.ensureInitialized(),当我把这行代码注释掉的时候,框架异常是捕获不到的。

    当时困扰了好久最后终于查到了原因:

     

    上图是Flutter的架构层,WidgetFlutterBinding用于与 Flutter 引擎交互。 我们的APM产品需要调用 native 代码来初始化,并且由于插件需要使用平台 channel 来调用 native 代码,这是异步完成的,因此必须调用ensureInitialized()确保你有一个 WidgetsBinding 的实例.

    来自 docs :

    Returns an instance of the WidgetsBinding, creating and initializing it if necessary. If one is created, it will be a WidgetsFlutterBinding. If one was previously initialized, then it will at least implement WidgetsBinding.

    注:如果你的应用在 runApp 中调用了 WidgetsFlutterBinding.ensureInitialized() 方法来进行一些初始化操作,则必须在 runZonedGuarded 中调用 WidgetsFlutterBinding.ensureInitialized()

    六、异常上报

    异常上报的整体方案是通过已有的插件增加接口,桥接Android APM 和 iOS APM库的自定义异常上报接口。

    插件增加函数

    static void postException(error, stack) {
        List<dynamic> args = [error,stack];
        //将异常和堆栈上报至umapm
        _channel.invokeMethod("postException",args);
      }
    

    Android 端调用自定义异常上报:

    private void postException(List args){
        String error = (String)args.get(0);
        String stack = (String)args.get(1);
        UMCrash.generateCustomLog(stack,error);
      }  

    iOS端调用自定义异常上报:

    if ([@"postException" isEqualToString:call.method]){
            NSString* error = arguments[0];
            NSString* stack = arguments[1];
            [UMCrash reportExceptionWithName:@"Flutter" reason:error stackTrace:stack terminateProgram:NO];
     }
    

      

    以上就是本期干货内容的介绍,希望我们的技术内容可以更好地帮助开发者们解决问题,我们将陪伴开发者们一起进步,一起成长。

     

    扫一扫加入友盟+ 技术社群

    与超过1000+移动开发者共同讨论移动开发最新动态

    欢迎点击【友盟+】,了解友盟+ 最新移动技术

    欢迎关注【友盟全域数据】公众号

     

     

      

      

      

  • 相关阅读:
    js 兼容阻止事件冒泡stopPropagation
    php session cookie
    js 给父元素的每个子元素绑定事件
    php 调用系统命令 超时
    php textarea换行
    php 中文字符串截取子串
    Predefined Asp.net skins(Themes) 你下载了吗?
    自定义DataFilter实现Atlas客户端DataView的数据筛选
    用Altas Behaviors实现就地编辑(1) [译]
    组件开发之Asp.net服务器控件Collection[集合]属性的设计时支持--编辑、保存
  • 原文地址:https://www.cnblogs.com/umengplus/p/15650555.html
Copyright © 2011-2022 走看看