You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:35:54