一、开篇:这个 API 到底在干什么?
很多刚接触 HarmonyOS NEXT 图片开发的同学,看到ImageSource、PixelMap、ImagePacker三个对象时会觉得有点绕——明明只是加载一张图,怎么搞出三个类?
官方文档写得比较抽象,把三个对象的能力列了一遍,但没讲清楚它们在实际调用链路上是怎么分工的。
本文就直接用一个最小可运行的示例,把这三个对象串起来,让你理解它们各自负责什么、怎么协作。
二、Image Kit 解决什么问题
Image Kit(图片处理服务)是 HarmonyOS 提供的专门用于图片解码、编码和处理的基础框架。它的职责非常清晰:
| 能力 | 说明 | 适用场景 |
|---|---|---|
| 解码(Decode) | 将图片文件(JPEG / PNG / WebP / HEIF 等)转换为内存中的PixelMap对象 | 加载图片到界面展示 |
| 编码(Encode) | 将PixelMap压缩成特定格式的二进制数据(比如保存到文件) | 编辑图片后导出 |
| 像素处理 | 通过PixelMap直接读写像素数据 | 滤镜、缩略图、水印 |
和多媒体服务(Media Kit)的区别:Media Kit 处理的是视频、音频流,而 Image Kit 专门处理静态图片的编解码和像素操作。如果你只是需要加载一张图并显示,用Image组件配合image.Source就够了;但如果需要解码后编辑像素再编码保存,那就必须走 Image Kit 的三件套流程。
三、环境说明
DevEco Studio 版本:DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上 目标设备:手机或平板四、核心对象关系与极简示例
4.1 三个对象的分工
- ImageSource:负责读取图片源(文件、资源、Buffer),提供解码参数,输出
PixelMap。 - PixelMap:内存中的位图,可读可写像素数据,是图片处理的核心载体。
- ImagePacker:负责将
PixelMap编码成目标格式(JPEG/PNG)的二进制数据,用于保存或传输。
调用链路非常直观:ImageSource→PixelMap→ 处理 →ImagePacker→ 编码数据
下面代码演示:从rawfile中读取一张图片,解码成PixelMap,然后直接用ImagePacker编码成 JPEG 数据并保存到沙箱文件。
4.2 完整示例代码
// pages/Index.etsimport{image}from'@kit.ImageKit';import{fileIo}from'@kit.CoreFileKit';import{common}from'@kit.AbilityKit';@Entry@Componentstruct Index{@Statemessage:string='';build(){Column(){Button('运行图片编解码').onClick(()=>{this.runImageDecodeEncode();})Text(this.message).margin({top:20})}.width('100%').height('100%').padding(20)}asyncrunImageDecodeEncode(){constcontext:common.UIAbilityContext=getContext(this)ascommon.UIAbilityContext;try{// 1. 从 rawfile 获取图片二进制数据constrawFile:Uint8Array=awaitcontext.resourceManager.getRawFileContent('test.jpg');constbuffer:ArrayBuffer=rawFile.buffer.slice(0);// 复制一份,避免引用问题// 2. 创建 ImageSource(指定解码格式为 JPEG)constimageSource:image.ImageSource=image.createImageSource(buffer);// 可选:设置解码参数(比如缩放到 500x500)constdecodingOptions:image.DecodingOptions={desiredSize:{width:500,height:500}};// 3. 解码得到 PixelMapconstpixelMap:image.PixelMap=awaitimageSource.createPixelMap(decodingOptions);console.info('解码成功, PixelMap尺寸:',pixelMap.getImageInfoSync().size);// 4. (可选)对 PixelMap 进行像素处理,例如旋转或颜色变换,此处略// 5. 创建 ImagePackerconstpacker:image.ImagePacker=image.createImagePacker();constpackOptions:image.PackingOption={format:'image/jpeg',quality:90// 0~100,数值越大质量越高};// 6. 编码得到 ArrayBufferconstencodedData:ArrayBuffer=awaitpacker.pack(pixelMap,packOptions);console.info('编码成功, 数据大小:',encodedData.byteLength);// 7. 保存到沙箱文件constfilePath:string=`${context.filesDir}/encoded_result.jpg`;constfile:fileIo.File=fileIo.openSync(filePath,fileIo.OpenMode.CREATE|fileIo.OpenMode.WRITE_ONLY);fileIo.writeSync(file.fd,encodedData);fileIo.closeSync(file);this.message=`文件已保存:${filePath}`;}catch(error){console.error('编解码失败:',JSON.stringify(error));this.message='操作失败,请查看日志';}}}代码说明:
getRawFileContent返回的是Uint8Array,注意不能直接作为ArrayBuffer传给createImageSource,需要取其buffer属性。createPixelMap是异步方法,需要await。PackingOption中format使用 MIME 类型字符串,例如'image/jpeg'、'image/png'。- 最后保存到
filesDir下,方便用文件管理查看结果。
五、常见踩坑
坑 1:PixelMap 不手动 release 会导致内存泄漏
现象:频繁解码大图后,应用内存只增不降,最终 OOM 崩溃。
原因:PixelMap底层持有 native 的位图内存,ArkTS 的垃圾回收器虽然会回收 JavaScript 对象,但 native 内存不会自动释放。
解法:在不再使用PixelMap后,显式调用pixelMap.release()。上面示例中pack结束后就可以release。建议用finally块确保释放。
try{// ... 解码// 使用完毕后pixelMap.release();}catch(e){// ...}finally{pixelMap?.release();}需要注意:release()不是幂等的,多次调用会报错,请在确定不再使用后调用一次。
坑 2:ImagePacker.pack在编码大图时可能超时阻塞 UI
现象:编码一张 4000x3000 的大图时,界面卡顿几秒钟。
原因:pack虽然是异步方法,但它默认在当前线程(主线程)执行耗时的编码计算。
解法:使用@ohos.taskpool将其移到子线程执行,或者拆分成pack之前先缩放到合理尺寸。推荐在解码时通过desiredSize提前缩小,而不是等编码再处理。
// 在解码选项中限制输出尺寸,减少后续编码压力constdecodingOptions:image.DecodingOptions={desiredSize:{width:1024,height:1024}};// 如果原图很大,desiredSize 会按比例缩放,不会拉伸坑 3:JPEG 编码质量 quality 参数与预期不符
现象:设置quality: 100仍发现图片变大或质量下降。
原因:quality影响的是压缩算法的量化系数,并不是线性关系。对于 JPEG,100依然有损压缩。若要无损输出,应使用 PNG 格式。另外,部分硬件编码器可能忽略quality参数。
建议:对画质要求高的场景使用format: 'image/png',或者先测试实际输出大小与视觉差异。
六、最佳实践
- 解码时尽量指定输出尺寸:避免解码超大图浪费内存。
desiredSize会根据原图宽高比自动缩放,不会拉伸变形。 - 通用
release()模式:在try/catch/finally或使用using语法(API 12+ 支持)释放资源,避免忘记释放。using pixelMap=awaitimageSource.createPixelMap(options);// 使用 pixelMap,离开作用域自动释放 - 不要把
PixelMap存入@State长期持有:PixelMap不是状态变量,ArkUI 不会监听其内部变化。如果需要 UI 上显示编辑后的图片,建议将解码后的PixelMap直接赋给Image组件的source,编辑时生成新的PixelMap替换。
七、Demo 入口
上面示例已经是一个完整可运行的Index.ets文件。只要在rawfile下放一张test.jpg,即可测试。若需要从其他路径加载图片,可使用fileIo读取对应文件构造ArrayBuffer。
示例代码地址:项目地址
八、FAQ
Q:为什么我 decode 出来的 PixelMap 是 null?
A:检查ImageSource创建是否成功。常见原因是文件路径错误或图片格式不支持。可以通过imageSource.getImageInfo()验证是否成功解析图片头。
Q:Image Kit 支持网络图片直接解码吗?
A:不支持。需要先下载网络图片到本地(使用@ohos.net.http),拿到ArrayBuffer后再传给createImageSource。
Q:同时解码多张图片时,如何控制并发?
A:不建议在短时间内大量调用createPixelMap,每个PixelMap会占用独立 native 内存。推荐使用线程池或队列逐个处理,并控制同时活跃的PixelMap数量不超过 3~5 个。可以利用taskpool并行解码,但需要注意PixelMap的跨线程传递是有限制的(必须序列化),实际上建议还是主线程逐个进行。