Flutter使用Image.memory加载图片时如何校验字节有效性避免解码失败
问题背景
现有逻辑为将接口返回的base64格式图片数据校验格式后解码存入SQLite,后续从数据库读取字节数据渲染到UI,逻辑正常时可加载图片或展示默认占位图,但存在部分通过base64格式校验的数据解码为字节后无法作为图片加载,抛出原生层图片解码异常。
现有同步入库逻辑
... for (var item in items) { if (item['photo'] != null) { if (isBase64(item['photo'])) { Uint8List photo = base64Decode(item['photo']); batch.insert('items', { 'sku': sku, 'name': name, 'description': description, 'price': price, 'type_name': itemType, 'pictureBlob': photo }); } else { batch.insert('items', { 'sku': sku, 'name': name, 'description': description, 'price': price, 'type_name': itemType }); } } else { batch.insert('items', { 'sku': sku, 'name': name, 'description': description, 'price': price, 'type_name': itemType }); } } ...
现有base64格式校验逻辑
static bool isBase64(dynamic value) { if (value.runtimeType == String) { final RegExp rx = RegExp( r'^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$', multiLine: true, unicode: true, ); final bool isBase64Valid = rx.hasMatch(value); if (isBase64Valid) { return true; } else { return false; } } else { return false; } }
现有UI图片加载逻辑
... CircleAvatar( radius: 65, backgroundImage: productItem.pictureBlob != null ? Image.memory(productItem.pictureBlob!).image : const AssetImage("assets/default.jpg")) ), ...
抛出的异常信息
E/FlutterJNI(10713): Failed to decode image E/FlutterJNI(10713): android.graphics.ImageDecoder$DecodeException: Failed to create image decoder with message 'unimplemented'Input contained an error. E/FlutterJNI(10713): at android.graphics.ImageDecoder.nCreate(Native Method) E/FlutterJNI(10713): at android.graphics.ImageDecoder.access$200(ImageDecoder.java:172) E/FlutterJNI(10713): at android.graphics.ImageDecoder$ByteBufferSource.createImageDecoder(ImageDecoder.java:242) E/FlutterJNI(10713): at android.graphics.ImageDecoder.decodeBitmapImpl(ImageDecoder.java:1870) E/FlutterJNI(10713): at android.graphics.ImageDecoder.decodeBitmap(ImageDecoder.java:1863) E/FlutterJNI(10713): at io.flutter.embedding.engine.FlutterJNI.decodeImage(FlutterJNI.java:514)
解决方案
问题核心原因:现有正则校验仅能判断字符串符合base64编码规则,无法保证解码后的字节序列为合法可渲染的图片格式,普通文本、损坏的图片文件转成的base64字符串都可以通过正则校验,但解码后无法作为图片加载。
可通过两层防护彻底解决该问题:
- 入库前增加图片字节有效性校验
base64解码得到字节数组后,调用Flutter引擎自带的图片解码方法尝试解码,解码成功才将字节存入数据库,解码失败则跳过pictureBlob字段存储,后续走默认图逻辑。示例代码如下:import 'dart:ui' as ui; // 原有base64解码逻辑 Uint8List photo = base64Decode(item['photo']); bool validImage = false; try { // 尝试解码图片字节,该方法会调用原生层图片解码器,解码成功即说明是合法图片 await ui.instantiateImageCodec(photo); validImage = true; } catch (_) { validImage = false; } if (validImage) { batch.insert('items', { 'sku': sku, 'name': name, 'description': description, 'price': price, 'type_name': itemType, 'pictureBlob': photo }); } else { batch.insert('items', { 'sku': sku, 'name': name, 'description': description, 'price': price, 'type_name': itemType }); } - UI加载层增加异常兜底
替换原有直接取Image.provider的写法,使用Image.memory自带的errorBuilder参数捕获解码异常,异常时直接渲染默认占位图,避免红屏或控制台报错。示例代码如下:CircleAvatar( radius: 65, child: ClipOval( child: SizedBox( width: 130, height: 130, child: productItem.pictureBlob != null ? Image.memory( productItem.pictureBlob!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Image.asset( "assets/default.jpg", fit: BoxFit.cover, ); }, ) : Image.asset("assets/default.jpg", fit: BoxFit.cover), ), ), )
不需要额外开发Android原生侧捕获逻辑,上述Dart层的校验和错误兜底已经可以完整捕获原生层抛出的图片解码异常。
内容的提问来源于stack exchange,提问作者Kyle Lynch
相关产品推荐
相关产品推荐

