Flutter 三方库适配实战在 OpenHarmony 上实现图片压缩功能附超详细踩坑记录欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net哈喽大家好呀我是一名上海某高校的大一计算机新生最近一头扎进了 Flutter for OpenHarmony 的开发大坑里不是一边上课一边捣鼓鸿蒙 App 开发踩了数不清的坑也收获了超多干货今天就来跟大家唠唠我在项目里实现「图片压缩」功能的全过程从依赖选型到代码实现再到鸿蒙设备上的各种奇葩报错和解决办法全都是我亲测有效的经验希望能帮到同样在入门的小伙伴们 为什么我一定要做图片压缩功能不知道有没有和我一样的小伙伴刚学做 App 的时候以为图片处理就是调用一下 API结果上手才发现图片上传不压缩App 直接卡成PPT用户也会因为上传慢直接跑路尤其是在鸿蒙设备上原生的图片处理 API 不仅难用而且和 Flutter 的交互还容易出各种兼容问题。所以我就想着能不能找一个适配鸿蒙的 Flutter 三方库把图片压缩这件事搞定而且还要做成一个通用的模块后面不管是做头像上传、内容发布还是图片缓存都能直接用。于是就有了我项目里的「Image Compress」模块✨它支持这几个超实用的功能质量压缩不改变尺寸通过降低画质减小体积尺寸压缩按比例缩小图片宽高适配不同场景批量压缩一次性处理多张图片不用用户一张一张传格式转换PNG/JPG 互转满足不同的存储和上传需求️ 第一步依赖选型我差点踩了个大坑一开始我想偷懒直接用了 pub.dev 上下载量最高的flutter_image_compress库结果噩梦开始了…❌ 踩坑 1第三方库编译失败鸿蒙适配不兼容我开开心心在pubspec.yaml里加了依赖运行flutter pub get也成功了结果一编译鸿蒙平台的代码直接报了一堆红全是和原生代码相关的错误比如找不到.so文件、原生方法未实现之类的。我去翻了这个库的 issues才发现它的鸿蒙适配还在社区适配阶段很多原生桥接的代码都没完善直接用根本跑不起来。对于我这种大一新生来说自己去改原生适配代码简直是天方夜谭只能含泪放弃这个库。✅ 换个思路纯 Dart 库才是鸿蒙入门的神后来我在 OpenHarmony TPC 的 Flutter 三方库仓库里翻了好久发现了宝藏——纯 Dart 实现的image库它的优点简直不要太香纯 Dart 代码实现不需要任何原生桥接在鸿蒙上零适配就能直接用功能超全解码、编码、缩放、裁剪、格式转换全都支持社区维护很活跃文档也很全新手也能看懂同时我还搭配了image_picker_ohos这个专门适配鸿蒙的图片选择器解决图片选择的问题避免了官方image_picker在鸿蒙上的权限坑。1. 最终的依赖配置亲测有效在pubspec.yaml里添加这两个依赖dependencies:flutter:sdk:flutterimage_picker_ohos:^1.0.4# 鸿蒙适配版图片选择器解决相册权限问题image:^4.1.3# 纯Dart图片处理库压缩、格式转换全靠它划重点根据社区规范所有代码托管平台都要使用 AtomGit所以我直接选择了社区维护的适配版本完全不用自己折腾省心又省力2. 安装依赖准备起飞打开终端在项目根目录执行命令flutter pub get这次终于没有报错了依赖安装成功接下来就是激动人心的代码环节啦~ 第二步代码实现一步一步拆解给你看我把整个图片压缩的流程拆成了 5 个小部分每个部分都有完整的代码和我踩过的坑大家可以直接复制到项目里用1. 图片选择从相册里选图多图支持首先要让用户能从相册里选择图片这里我用了image_picker_ohos而且做了多图选择的支持用户可以一次性选好几张图一起压缩importdart:typed_data;importpackage:image_picker_ohos/image_picker_ohos.dart;// 选择多张图片返回图片字节流列表FutureListUint8ListpickImagesFromGallery()async{try{finalImagePickerpickerImagePicker();// 支持多图选择设置图片质量为最高避免选择时就被压缩finalListXFileselectedImagesawaitpicker.pickMultiImage(imageQuality:100,);if(selectedImages.isEmpty){print(用户没有选择任何图片);return[];}// 把选中的图片转成字节流方便后续处理ListUint8ListimageBytesList[];for(varimageinselectedImages){finalbytesawaitimage.readAsBytes();imageBytesList.add(bytes);print(选中图片大小${bytes.length/1024}KB);}returnimageBytesList;}catch(e){print(选择图片出错$e);rethrow;}}❌ 踩坑 2鸿蒙相册权限没配置一直读取失败我第一次运行的时候点击选择图片App 直接没反应也不报错就是选不了图。查了好久才发现鸿蒙设备上读取相册必须要在module.json5里配置权限打开ohos/entry/src/main/module.json5在requestPermissions里加上这一段requestPermissions:[{name:ohos.permission.READ_MEDIA_IMAGES,reason:$string:permission_read_media_images_reason,usedScene:{abilities:[EntryAbility],when:inuse}}]加上权限之后重启应用终于能正常读取相册里的图片了鸿蒙的权限管理真的很严格新手小伙伴们一定要记得配置不然连图片都选不了2. 质量压缩不改变尺寸只压画质质量压缩是最常用的压缩方式原理就是降低图片的编码质量在肉眼几乎看不出区别的情况下大幅减小图片体积。importpackage:image/image.dartasimg;// 按质量压缩图片0-100数值越低质量越差体积越小Uint8ListcompressWithQuality(Uint8ListoriginalBytes,int quality){// 第一步解码图片字节流finalimg.Image?originalImageimg.decodeImage(originalBytes);if(originalImagenull){throwException(图片解码失败可能是格式不支持);}// 第二步按指定质量重新编码为JPG格式finalcompressedBytesimg.encodeJpg(originalImage,quality:quality);print(质量压缩后图片大小${compressedBytes.length/1024}KB);returnUint8List.fromList(compressedBytes);}❌ 踩坑 3质量设置为 0图片直接变全黑我一开始测试的时候为了压到最小直接把quality设置成了 0结果压缩出来的图片直接变成了全黑的方块差点以为代码写崩了后来才知道image库的quality参数范围是 0-1000 代表最低画质虽然体积小但很多图片会出现严重的失真甚至无法正常显示。实际开发中设置 70-80 是最合适的肉眼几乎看不出区别体积却能缩小一半以上3. 尺寸压缩按比例缩小适配不同场景有时候用户上传的图片是几千像素的原图直接上传不仅慢还占服务器空间这时候就需要按比例缩小图片的尺寸同时保持宽高比不变避免图片变形。// 按目标宽度等比例压缩尺寸保持宽高比Uint8ListcompressWithSize(Uint8ListoriginalBytes,int targetWidth){// 解码图片finalimg.Image?originalImageimg.decodeImage(originalBytes);if(originalImagenull){throwException(图片解码失败可能是格式不支持);}// 如果原图宽度已经小于目标宽度就不做缩放避免放大导致模糊if(originalImage.widthtargetWidth){print(图片宽度已小于目标宽度无需缩放);returnoriginalBytes;}// 计算目标高度保持宽高比finaldouble aspectRatiooriginalImage.height/originalImage.width;finalint targetHeight(targetWidth*aspectRatio).round();print(原图尺寸${originalImage.width}x${originalImage.height});print(压缩后尺寸${targetWidth}x${targetHeight});// 缩放图片finalimg.ImageresizedImageimg.copyResize(originalImage,width:targetWidth,height:targetHeight,interpolation:img.Interpolation.linear,// 线性插值缩放更平滑);// 编码为JPG返回finalcompressedBytesimg.encodeJpg(resizedImage,quality:80);print(尺寸压缩后图片大小${compressedBytes.length/1024}KB);returnUint8List.fromList(compressedBytes);}❌ 踩坑 4没处理宽高比图片直接被拉变形我最开始写的时候直接硬传了目标宽高结果用户上传的竖图被压成了横图完全变形了巨丑无比后来加上了宽高比计算根据原图的宽高比自动计算目标高度才解决了图片变形的问题而且还加了原图尺寸判断避免把小图放大变模糊。4. 批量压缩一次性处理多张图片用户经常会一次性选好几张图片一张一张压缩体验太差了所以我写了一个批量压缩的方法把上面的两个压缩方式结合起来同时支持质量和尺寸压缩// 批量压缩图片先按尺寸压缩再按质量压缩FutureListUint8ListbatchCompressImages({requiredListUint8ListoriginalImages,int quality80,int targetWidth1080,})async{ListUint8ListcompressedList[];for(varindex0;indexoriginalImages.length;index){try{print(正在压缩第${index1}张图片...);// 先按尺寸压缩finalresizedBytescompressWithSize(originalImages[index],targetWidth);// 再按质量压缩finalcompressedBytescompressWithQuality(resizedBytes,quality);compressedList.add(compressedBytes);print(第${index1}张图片压缩完成);}catch(e){print(第${index1}张图片压缩失败$e);// 压缩失败时返回原图避免整个批量操作中断compressedList.add(originalImages[index]);}}returncompressedList;}❌ 踩坑 5批量压缩导致主线程卡顿App 直接卡死我第一次测试批量压缩的时候一次性选了 10 张高清原图结果 App 直接卡住了连点击事件都没反应差点崩溃了后来我才知道图片压缩是耗时操作如果放在主线程执行会阻塞 UI 渲染导致 App 卡顿甚至无响应。解决办法就是把压缩操作放到后台 isolate 里执行用compute函数来实现importpackage:flutter/foundation.dart;// 把压缩操作放到后台执行避免主线程卡顿FutureUint8ListcompressInBackground(Uint8Listbytes)async{returnawaitcompute((Uint8ListinputBytes){// 这里面的代码会在后台isolate里执行finalimg.Image?imageimg.decodeImage(inputBytes);if(imagenull)throwException(图片解码失败);// 同时做尺寸和质量压缩finalresizedimg.copyResize(image,width:1080);returnUint8List.fromList(img.encodeJpg(resized,quality:80));},bytes);}改完之后批量压缩 10 张图片UI 依然丝滑流畅再也不会卡顿了5. 格式转换PNG/JPG 互转很多时候用户上传的是 PNG 格式的图片带透明通道体积会比 JPG 大很多所以我加了一个格式转换的功能支持 PNG 和 JPG 互转// 图片格式转换支持jpg、png互转Uint8ListconvertImageFormat(Uint8ListoriginalBytes,StringtargetFormat){finalimg.Image?originalImageimg.decodeImage(originalBytes);if(originalImagenull){throwException(图片解码失败无法转换格式);}switch(targetFormat.toLowerCase()){casejpg:casejpeg:// 转换为JPG格式去掉透明通道returnUint8List.fromList(img.encodeJpg(originalImage,quality:90));casepng:// 转换为PNG格式保留透明通道returnUint8List.fromList(img.encodePng(originalImage));default:throwException(不支持的目标格式$targetFormat);}}这个功能也解决了我之前遇到的一个问题有些 PNG 图片带透明通道压缩成 JPG 之后会出现黑底后来我给encodeJpg加了背景填充把透明通道改成白色就解决了黑底的问题// 给图片填充白色背景避免透明通道转JPG出现黑底img.ImageimageWithWhiteBackground(img.Imageoriginal){finalimg.ImagenewImageimg.Image(width:original.width,height:original.height,);// 填充白色背景img.fill(newImage,color:img.ColorRgb8(255,255,255));// 把原图叠加到白色背景上img.copyInto(newImage,original);returnnewImage;} 第三步鸿蒙设备上的完整测试代码写好了接下来就是激动人心的真机测试环节我把所有功能都整合到了项目的「Image Compress」模块里做了一个简单的 UI用户可以选择不同的压缩方式还能看到压缩前后的图片大小对比。测试结果超棒在我的鸿蒙设备上测试整个流程非常流畅图片选择点击按钮能正常打开相册选择多张图片没有权限报错质量压缩选择 70 的质量一张 2MB 的图片压缩后变成了 600KB 左右画质几乎没区别尺寸压缩把 4000x3000 的原图压缩成 1080x810体积缩小了 80%而且没有变形批量压缩一次性选 5 张图片后台压缩的时候 UI 依然丝滑压缩完成后能正常预览格式转换PNG 转 JPG 没有黑底透明通道被正确填充成白色测试时的最后一个坑部分特殊格式图片解码失败有几张用户拍的 HEIC 格式的图片用image库解码的时候直接报错了原来image库不支持 HEIC 格式的解码解决办法是在图片选择的时候限制只选择 JPG/PNG 格式的图片或者给用户提示不支持的格式。我给图片选择器加了一个格式过滤finalListXFileselectedImagesawaitpicker.pickMultiImage(imageQuality:100,requestFullMetadata:false,);// 过滤掉不支持的格式finalsupportedFormats[jpg,jpeg,png];ListXFilefilteredImagesselectedImages.where((file){finalextfile.path.split(.).last.toLowerCase();returnsupportedFormats.contains(ext);}).toList();这样就不会再遇到解码失败的问题了用户也不会因为选择了不支持的格式而导致功能报错。 大一新生的踩坑心得总结这次实现图片压缩功能我前前后后折腾了快一个星期踩了无数的坑也学到了超多东西给大家总结几个新手必看的要点依赖选型优先选纯 Dart 库对于鸿蒙入门来说纯 Dart 实现的三方库比依赖原生的库兼容性好太多了像image这种完全不用适配拿来就能用新手友好度拉满鸿蒙权限一定要提前配置不管是相册、相机还是存储只要用到系统能力一定要在module.json5里配置权限不然 App 只会静默失败连个报错都没有耗时操作一定要放到后台图片压缩、网络请求这些耗时操作一定要用compute或者Isolate放到后台执行不然主线程阻塞App 直接卡顿甚至崩溃一定要做异常捕获和兼容处理比如图片解码失败、格式不支持、压缩失败这些情况都要加 try-catch 处理给用户友好的提示而不是直接让 App 崩溃。多去社区仓库找适配好的库OpenHarmony TPC 的 Flutter 三方库仓库里有很多社区维护的适配好的库比自己去 pub.dev 瞎找靠谱多了-