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

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

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

    Dingtalk_20211203145217.jpg

    上图是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 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];

    }

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

    原文链接
    本文为阿里云原创内容,未经允许不得转载。

  • 相关阅读:
    1104 Sum of Number Segments (20 分)(数学问题)
    1092 To Buy or Not to Buy (20 分)(hash散列)
    1082 Read Number in Chinese (25 分)(字符串处理)【背】
    1105 Spiral Matrix (25 分)(模拟)
    初识网络安全及搭建网站(内网)
    HTML5开发者需要了解的技巧和工具汇总(转)
    native+web开发模式之web前端经验分享
    移动平台3G手机网站前端开发布局技巧汇总(转)
    Asp.net 中图片存储数据库以及页面读取显示通用方法详解附源码下载
    使用H3Viewer来查看VS2010的帮助文档
  • 原文地址:https://www.cnblogs.com/yunqishequ/p/15682213.html
Copyright © 2011-2022 走看看