在Isolate中进行Flutter图片缩放压缩时抛出UnimplementedError
问题:Isolate中调用FlutterImageCompress.compressWithList抛出UnimplementedError
为避免UI卡顿,尝试在Isolate中执行图片缩放和压缩操作,但调用FlutterImageCompress.compressWithList时抛出UnimplementedError。非Isolate环境下,相同功能的代码可正常运行。
出错代码(Isolate版本)
import 'dart:isolate'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:image/image.dart' as img; // Helper function to perform image processing in an isolate Future<Uint8List> _processImageInIsolate( Uint8List imageData, int quality, int width) async { try { // Decode the image img.Image? image = img.decodeImage(imageData); if (image == null) { throw Exception("Failed to decode image"); } // Crop to square final squareImage = Uint8ListHelper.cropToSquare(image); // Resize the image to the desired size final resizedImage = Uint8ListHelper.resizeToSquare(squareImage, width); // Compress the image final compressed = await FlutterImageCompress.compressWithList( Uint8List.fromList(img.encodePng(resizedImage)), quality: quality, ); return Uint8List.fromList(compressed); } catch (e) { debugPrint(e.toString()); return imageData; // Return original if processing fails } } extension Uint8ListHelper on Uint8List { static img.Image cropToSquare(img.Image image) { int xOffset = 0; int yOffset = 0; int squareSize; if (image.width > image.height) { // If the image is wider than it is tall, crop the sides squareSize = image.height; xOffset = (image.width - image.height) ~/ 2; } else { // If the image is taller than it is wide, crop the top and bottom squareSize = image.width; yOffset = (image.height - image.width) ~/ 2; } // Crop the image to a square return img.copyCrop( image, x: xOffset, y: yOffset, width: squareSize, height: squareSize, ); } static img.Image resizeToSquare(img.Image image, int size) { // Resize the image to the target square size return img.copyResize( image, width: size, height: size, // Both dimensions are the same to make it a square interpolation: img.Interpolation.linear, ); } Future<Uint8List> process({ int quality = 20, int width = 1000, }) async { try { // Use `compute` to run the image processing in an isolate final compressed = await Isolate.run( () { return _processImageInIsolate(this, quality, width); }, ); // Ensure that the compressed image isn't larger than the original if (compressed.length > length) { return this; // Return the original if the processed image is larger } return compressed; } catch (e) { FirebaseCrashlytics.instance.recordError( 'EasyCatch: Uint8List.process failed', null, reason: e.toString(), information: [e], ); return this; } } }
正常运行代码(非Isolate版本)
import 'dart:typed_data'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/material.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:image/image.dart' as img; import 'package:wishlists/extensions/extensions.dart'; extension Uint8ListHelper on Uint8List { static img.Image cropToSquare(img.Image image) { int xOffset = 0; int yOffset = 0; int squareSize; if (image.width > image.height) { // If the image is wider than it is tall, crop the sides squareSize = image.height; xOffset = (image.width - image.height) ~/ 2; } else { // If the image is taller than it is wide, crop the top and bottom squareSize = image.width; yOffset = (image.height - image.width) ~/ 2; } // Crop the image to a square return img.copyCrop( image, x: xOffset, y: yOffset, width: squareSize, height: squareSize, ); } static img.Image resizeToSquare(img.Image image, int size) { // Resize the image to the target square size return img.copyResize( image, width: size, height: size, // Both dimensions are the same to make it a square interpolation: img.Interpolation.linear, ); } /// Resizes the image to 1000x1000 pixels and compresses it with 40% quality. /// Returns a compressed Uint8List. /// If the process fails, it returns the given Uint8List. Future<Uint8List> process({ int quality = 20, int width = 1000, }) async { try { // return this; final resized = resize( width: width, ); final compressed = await (resized ?? this).compress( quality: quality, ); debugPrint( 'Original size: ${length.toFormattedFileSize()}', ); debugPrint( 'Compressed size: ${compressed?.length.toFormattedFileSize()}', ); if ((compressed?.length ?? 0) > length) { // Sometimes images are already small so resizing and compressing them, // will actually make them bigger. return this; } return compressed ?? resized ?? this; } catch (e) { return this; } } // Compress Uint8List and get another Uint8List. Future<Uint8List?> compress({ int quality = 20, }) async { try { final result = await FlutterImageCompress.compressWithList( this, quality: quality, ); return result; } on Exception catch (e) { FirebaseCrashlytics.instance.recordError( 'EasyCatch: Uint8List.compress failed', null, reason: e.toString(), information: [e], ); return null; } } Uint8List? resize({ int width = 1000, }) { try { img.Image? image = img.decodeImage(this); if (image == null) { throw Exception("Failed to decode image"); } final squareImage = Uint8ListHelper.cropToSquare( image, ); final resizedImage = Uint8ListHelper.resizeToSquare( squareImage, width, ); final byteList = Uint8List.fromList( img.encodePng(resizedImage), ); return byteList; } on Exception catch (e) { FirebaseCrashlytics.instance.recordError( 'EasyCatch: Uint8List.resize failed', null, reason: e.toString(), information: [e], ); return null; } } }
问题原因
FlutterImageCompress依赖原生平台(Android/iOS)的图片压缩能力,这类插件的底层实现基于Flutter的平台通道(Platform Channel)。而Isolate是独立的执行线程,无法访问主Isolate的平台通道资源,因此在Isolate中调用这类需要原生支持的方法时,会触发UnimplementedError。
解决方案
将图片处理流程拆分:
- 把纯Dart实现的计算密集型操作(图片解码、裁剪、缩放)放在后台Isolate执行,避免UI卡顿;
- 把依赖原生平台的压缩操作移回主Isolate执行,确保插件能正常调用原生能力。
修改后的代码示例:
import 'dart:isolate'; import 'dart:typed_data'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:image/image.dart' as img; extension Uint8ListHelper on Uint8List { static img.Image cropToSquare(img.Image image) { int xOffset = 0; int yOffset = 0; int squareSize; if (image.width > image.height) { squareSize = image.height; xOffset = (image.width - image.height) ~/ 2; } else { squareSize = image.width; yOffset = (image.height - image.width) ~/ 2; } return img.copyCrop( image, x: xOffset, y: yOffset, width: squareSize, height: squareSize, ); } static img.Image resizeToSquare(img.Image image, int size) { return img.copyResize( image, width: size, height: size, interpolation: img.Interpolation.linear, ); } Future<Uint8List> process({ int quality = 20, int width = 1000, }) async { try { // 仅在后台Isolate执行纯Dart的图片处理 final resizedData = await Isolate.run(() { img.Image? image = img.decodeImage(this); if (image == null) throw Exception("Failed to decode image"); final squareImage = Uint8ListHelper.cropToSquare(image); final resizedImage = Uint8ListHelper.resizeToSquare(squareImage, width); return Uint8List.fromList(img.encodePng(resizedImage)); }); // 回到主Isolate执行压缩操作 final compressed = await FlutterImageCompress.compressWithList( resizedData, quality: quality, ); // 若压缩后体积更大,返回原数据 if (compressed.length > length) { return this; } return compressed; } catch (e) { FirebaseCrashlytics.instance.recordError( 'EasyCatch: Uint8List.process failed', null, reason: e.toString(), information: [e], ); return this; } } }
说明
这样拆分后,既利用了Isolate避免UI线程阻塞,又保证了依赖原生的压缩操作能正常工作。所有需要调用原生插件的代码,都必须放在主Isolate中执行。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

