Flutter 异常处理之图片篇

作者: 安卓小煜 | 来源:发表于2019-04-15 10:08 被阅读53次

    背景

    说到异常处理,你可能直接会认为不就是 try-catch 的事情,至于写一篇文章单独来说明吗?

    如果你是这么想的,那么本篇说不定会给你惊喜哦~

    而且本篇聚焦在图片的异常处理。

    场景

    学以致用,有具体的应用场景,能够加深我们对知识的掌握。

    我们以简书的文章列表为例,如下图:

    假设产品有这样的需求,当右边的封面图加载失败的时候,用一个默认图片替换或者直接让文本横向填充原有图片位置。

    不管处理方式是怎样,首先我们要做的就是能够知道图片加载失败。

    如何获知图片加载失败呢?下面我们通过 Flutter 自带网络加载 API 和一个第三方网络库来进行对比说明。

    Image.network

    我们看下源码,如下:

    Image.network(String src, {
        Key key,
        double scale = 1.0,
        this.semanticLabel,
        this.excludeFromSemantics = false,
        this.width,
        this.height,
        this.color,
        this.colorBlendMode,
        this.fit,
        this.alignment = Alignment.center,
        this.repeat = ImageRepeat.noRepeat,
        this.centerSlice,
        this.matchTextDirection = false,
        this.gaplessPlayback = false,
        this.filterQuality = FilterQuality.low,
        Map<String, String> headers,
      }) : image = NetworkImage(src, scale: scale, headers: headers),
           assert(alignment != null),
           assert(repeat != null),
           assert(matchTextDirection != null),
           super(key: key);
    

    可以看到只有 src 是必填参数,因此我们给出 src 为不同值的情况。

    1. 一个图片的 url
      Widget _buildWidget() {
        return Image.network('https://img.haomeiwen.com/i5361063/e413832da0038304.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800');
      }
    

    能够正常显示如下图:

    1. 不可访问 url,如随便一个字符串 test
      Widget _buildWidget() {
        return Image.network('test');
      }
    

    终端报错如下:

    flutter: ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
    flutter: The following ArgumentError was thrown resolving an image codec:
    flutter: Invalid argument(s): No host specified in URI file:///test
    

    模拟器显示空白。

    这种场景假设我们要捕获异常,增加 try-catch,如下:

      Widget _buildWidget() {
        try {
          return Image.network('test');
        } catch (e) {
          print('enter catch exception start');
          print(e);
          print('enter catch exception end');
          return Container();
        }
      }
    

    依然没法捕获

    1. 可访问非图片 url,比如 http://mp.weixin.qq.com/mp/homepage?__biz=MzI3OTAyNzAwNg==&hid=5&sn=7e4598d8b00537fe2846f2e85d746b9a&scene=18#wechat_redirect
      Widget _buildWidget() {
        try {
          return Image.network('http://mp.weixin.qq.com/mp/homepage?__biz=MzI3OTAyNzAwNg==&hid=5&sn=7e4598d8b00537fe2846f2e85d746b9a&scene=18#wechat_redirect');
        } catch (e) {
          print('enter catch exception start');
          print(e);
          print('enter catch exception end');
          return Container();
        }
      }
    

    控制台抛出如下异常

    [VERBOSE-2:codec.cc(97)] Failed decoding image. Data is either invalid, or it is encoded using an unsupported format.
    flutter: ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
    flutter: The following _Exception was thrown resolving an image codec:
    flutter: Exception: operation failed
    
    

    可以看到 try-catch 一样没法生效。没有打印相关日志。

    cached_network_image

    这是一个第三方开发的网络库,pub 地址为 https://pub.dartlang.org/packages/cached_network_image

    因为项目有用到这个库,所以用这个来举例,并不是为其打广告,至于你实际开发是否用这个库,还是有其他更好的库,需要你自己去评估。

    因为这个是项目组 iOS 同事选择的,我这边并没有深入研究过。

    我们仿照上面的依次执行 3 种 case。

    1. 一个图片的 url
      Widget _buildWidget() {
        return Image(image: new CachedNetworkImageProvider('https://img.haomeiwen.com/i5361063/e413832da0038304.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800'));
      }
    

    能够正常显示如下图:

    1. 不可访问 url,如随便一个字符串 test
      Widget _buildWidget() {
        return Image(image: new CachedNetworkImageProvider('test'));
      }
    

    终端报错如下:

    flutter: ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
    flutter: The following message was thrown resolving an image codec:
    flutter: Couldn't download or retrieve file.
    

    模拟器显示空白。

    这种场景假设我们要捕获异常,增加 try-catch,如下:

      Widget _buildWidget() {
        try {
          return Image(image: new CachedNetworkImageProvider('test'));
        } catch (e) {
          print('enter catch exception start');
          print(e);
          print('enter catch exception end');
          return Container();
        }
      }
    

    依然没法捕获。

    但是我们通过其自带的错误回调,如下:

      Widget _buildWidget() {
        return Image(
            image: new CachedNetworkImageProvider(
                'test',
                errorListener: () {
                  print('enter errorListener');
                }
            )
        );
      }
    

    可以看到控制台进入了 errorListener,打印了对应日志。

    虽然 Flutter 自带的错误日志依然输出了,但是通过 errorListener 我们可以获得这种异常情况。

    flutter: enter errorListener
    flutter: ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
    flutter: The following message was thrown resolving an image codec:
    flutter: Couldn't download or retrieve file.
    
    1. 可访问非图片 url,比如 http://mp.weixin.qq.com/mp/homepage?__biz=MzI3OTAyNzAwNg==&hid=5&sn=7e4598d8b00537fe2846f2e85d746b9a&scene=18#wechat_redirect
      Widget _buildWidget() {
        return Image(
            image: new CachedNetworkImageProvider(
                'http://mp.weixin.qq.com/mp/homepage?__biz=MzI3OTAyNzAwNg==&hid=5&sn=7e4598d8b00537fe2846f2e85d746b9a&scene=18#wechat_redirect',
                errorListener: () {
                  print('enter errorListener');
                }
            )
        );
      }
    

    运行,控制台会报错,并且没法捕获,控制台输出如下:

    [VERBOSE-2:codec.cc(97)] Failed decoding image. Data is either invalid, or it is encoded using an unsupported format.
    flutter: ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
    flutter: The following _Exception was thrown resolving an image codec:
    flutter: Exception: operation failed
    

    使用 try-catch 也是一样,这里就不赘余了。

    图片通用异常捕获处理

    通过上面的学习,我们可以发现不管是 Image.network 还是 cached_network_image 没法覆盖全上面两种异常的捕获处理。

    不过这两个的共同点就是他们都返回 Image。

    所以对于图片的异常捕获可以使用下面通用模板:

    //    Image image = Image(image: new CachedNetworkImageProvider(''));
    Image image = Image.network('');
    final ImageStream stream = image.image.resolve(ImageConfiguration.empty);
    stream.addListener((_, __) {}, onError: (dynamic exception, StackTrace stackTrace) {
      //TODO error callback
    });
    

    这里首先是获得 Image,如果获得的是 ImageProvider,只需要把 image.image 换为你的 ImageProvider 即可,当然这个笔者没测试,只是看源码上面模板 image.image 的类型是 ImageProvider。

    addListener 有两个回调,其中成功回调是必填的,有两个参数,因为这里不需要用到,因此第一个参数是一个下划线,第二个参数是两个下划线。可能你会说不需要用到,可不可以直接填 null。不行,这边测试了,填 null 当图片加载成功时控制台会抛异常。所以提供一个不需要任何实现的回调即可。

    错误回调是可选的,因为我们本篇的主题就是要获取错误回调,所以这里提供了实现。

    针对我们上面的 3 个例子,我们看看通用模板是否可以全部捕获。

    1. 一个图片的 url
    Widget _buildWidget() {
      Image image = Image.network('https://img.haomeiwen.com/i5361063/e413832da0038304.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800');
      final ImageStream stream = image.image.resolve(ImageConfiguration.empty);
      stream.addListener((_,__){}, onError: (dynamic exception, StackTrace stackTrace) {
        print('enter onError start');
        print(exception);
        print(stackTrace);
        print('enter onError end');
      });
      return image;
    }
    

    图片加载成功。

    1. 不可访问 url,如随便一个字符串 test
    Widget _buildWidget() {
      Image image = Image.network('test');
      final ImageStream stream = image.image.resolve(ImageConfiguration.empty);
      stream.addListener((_,__){}, onError: (dynamic exception, StackTrace stackTrace) {
        print('enter onError start');
        print(exception);
        print(stackTrace);
        print('enter onError end');
      });
      return image;
    }
    

    控制台输出如下:

    flutter: enter onError start
    flutter: Invalid argument(s): No host specified in URI file:///test
    flutter: #0      _HttpClient._openUrl (dart:_http/http_impl.dart:2121:9)
    #1      _HttpClient.getUrl (dart:_http/http_impl.dart:2056:48)
    #2      NetworkImage._loadAsync (package:flutter/src/painting/image_provider.dart:486:57)
    <asynchronous suspension>
    #3      NetworkImage.load (package:flutter/src/painting/image_provider.dart:471:14)
    #4      ImageProvider.resolve.<anonymous closure>.<anonymous closure> (package:flutter/src/painting/image_provider.dart:267:86)
    #5      ImageCache.putIfAbsent (package:flutter/src/painting/image_cache.dart:143:20)
    #6      ImageProvider.resolve.<anonymous closure> (package:flutter/src/painting/image_provider.dart:267:63)
    #7      SynchronousFuture.then (package:flutter/src/foundation/synchronous_future.dart:38:29)
    #8      ImageProvider.resolve (package:flutter/src/painting/image_provider.dart:265:30)
    #9      MyApp._buildWidget (package:my_flutter/main.dart:20:42)
    #10     MyApp.build (package:my_flutter/main.dart:12:18)
    #11     StatelessElement.build (package:flutter/<…>
    flutter: enter onError end
    

    可以看到确实进入错误回调了。

    1. 可访问非图片 url,比如 http://mp.weixin.qq.com/mp/homepage?__biz=MzI3OTAyNzAwNg==&hid=5&sn=7e4598d8b00537fe2846f2e85d746b9a&scene=18#wechat_redirect
    Widget _buildWidget() {
      Image image = Image.network('http://mp.weixin.qq.com/mp/homepage?__biz=MzI3OTAyNzAwNg==&hid=5&sn=7e4598d8b00537fe2846f2e85d746b9a&scene=18#wechat_redirect');
      final ImageStream stream = image.image.resolve(ImageConfiguration.empty);
      stream.addListener((_,__){}, onError: (dynamic exception, StackTrace stackTrace) {
        print('enter onError start');
        print(exception);
        print(stackTrace);
        print('enter onError end');
      });
      return image;
    }
    

    控制台输出如下:

    [VERBOSE-2:codec.cc(97)] Failed decoding image. Data is either invalid, or it is encoded using an unsupported format.
    flutter: enter onError start
    flutter: Exception: operation failed
    flutter: #0      NetworkImage._loadAsync (package:flutter/src/painting/image_provider.dart:498:12)
    <asynchronous suspension>
    #1      NetworkImage.load (package:flutter/src/painting/image_provider.dart:471:14)
    #2      ImageProvider.resolve.<anonymous closure>.<anonymous closure> (package:flutter/src/painting/image_provider.dart:267:86)
    #3      ImageCache.putIfAbsent (package:flutter/src/painting/image_cache.dart:143:20)
    #4      ImageProvider.resolve.<anonymous closure> (package:flutter/src/painting/image_provider.dart:267:63)
    #5      SynchronousFuture.then (package:flutter/src/foundation/synchronous_future.dart:38:29)
    #6      ImageProvider.resolve (package:flutter/src/painting/image_provider.dart:265:30)
    #7      MyApp._buildWidget (package:my_flutter/main.dart:20:42)
    #8      MyApp.build (package:my_flutter/main.dart:12:18)
    #9      StatelessElement.build (package:flutter/src/widgets/framework.dart:3774:28)
    #10     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3721:15<…>
    flutter: enter onError end
    

    可以看到确实进入错误回调了。

    更多阅读:

    Flutter 入门系列博客
    Flutter & Dart

    最后来一个彩蛋表情包:

    相关文章

      网友评论

        本文标题:Flutter 异常处理之图片篇

        本文链接:https://www.haomeiwen.com/subject/hxyrwqtx.html