Flutter 异常处理之图片篇

背景

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

若是你是这么想的,那么本篇说不定会给你惊喜哦~网络

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

场景

学以至用,有具体的应用场景,可以加深咱们对知识的掌握。async

咱们以简书的文章列表为例,以下图:ide

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

无论处理方式是怎样,首先咱们要作的就是可以知道图片加载失败。测试

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

Image.network

咱们看下源码,以下:this

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 为不一样值的状况。url

1.一个图片的 url

Widget _buildWidget() {
    return Image.network('https://upload-images.jianshu.io/upload_images/5361063-e413832da0038304.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800');
  }
复制代码

可以正常显示以下图:

2.不可访问 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();
    }
  }
复制代码

依然无法捕获

3.可访问非图片 url,好比 mp.weixin.qq.com/mp/homepage…

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 地址为 pub.dartlang.org/packages/ca…

由于项目有用到这个库,因此用这个来举例,并非为其打广告,至于你实际开发是否用这个库,仍是有其余更好的库,须要你本身去评估。

由于这个是项目组 iOS 同事选择的,我这边并无深刻研究过。

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

1.一个图片的 url

Widget _buildWidget() {
    return Image(image: new CachedNetworkImageProvider('https://upload-images.jianshu.io/upload_images/5361063-e413832da0038304.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800'));
  }
复制代码

可以正常显示以下图:

2.不可访问 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. 复制代码

3.可访问非图片 url,好比 mp.weixin.qq.com/mp/homepage…

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://upload-images.jianshu.io/upload_images/5361063-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;
}
复制代码

图片加载成功。

2.不可访问 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
复制代码

能够看到确实进入错误回调了。

3.可访问非图片 url,好比 mp.weixin.qq.com/mp/homepage…

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

最后来一个彩蛋表情包:

相关文章
相关标签/搜索