HarmonyOS 技术精讲Scan Kit 码图生成——创建二维码与条形码一、这个 API 到底用来干什么很多刚接触 Scan Kit 的开发者第一反应是“扫码”其实它的能力远不止于此。它自带的码图生成能力在很多场景下能省掉引入第三方库的麻烦。说白了它解决的就是**“把一段文本变成可被扫码识别的图形”**这件事。常见场景包括分享页面链接生成二维码微信/支付宝支付码二维码商品条码EAN-13 / CODE128机票/快递单号CODE128 / PDF417对比一下常见的方案方案优势劣势原生 Canvas 绘制不依赖 SDK需要自行实现编码逻辑、纠错算法、定位图形开发成本高第三方 JS 库功能丰富需要引入额外包Kotlin/JS 混编容易出兼容性问题Scan Kit generateBarcode系统级 API性能稳定支持自定义样式码图格式有上限部分特殊需求需绕行说明官方 API 提供了 QR_CODE、CODE128、EAN-13、PDF417、AZTEC 等多种格式覆盖基本能满足 95% 以上的场景。如果需要非标准码图比如工业级特定格式那就得考虑其他方案了。二、环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机另外需要先完成 Scan Kit 的开发准备——在 AppGallery Connect 中开通扫码服务。这一步如果没做API 会返回 20183401 错误码。具体参见官方文档的开发准备章节。三、核心实现3.1 基础二维码生成先跑通最基础的功能把一串文本转成二维码显示到 Image 组件上。// pages/GenerateCodePage.etsimport{scan}fromkit.ScanKit;EntryComponentstruct GenerateCodePage{StateqrCodePixelMap:PixelMap|nullnull;build(){Column(){// 生成按钮Button(生成二维码).onClick((){this.generateQRCode(https://developer.huawei.com);})// 显示码图的 Image 组件Image(this.qrCodePixelMap).width(200).height(200).backgroundColor(#FFFFFF)}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}asyncgenerateQRCode(content:string){try{// 1. 构建生成参数letbarcodeConfig:scan.BarcodeGenerateParams{// 码图内容barcodeContent:content,// 码图格式QR_CODE 对应二维码barcodeFormat:scan.BarcodeFormat.QR_CODE,// 码图宽度像素width:400,// 码图高度像素height:400,// 前景色黑色foregroundColor:0xFF000000,// 背景色白色backgroundColor:0xFFFFFFFF,// 边距像素margin:10,// 输入数据类型文本inputData:scan.InputDataType.Text};// 2. 调用生成接口返回 PixelMapletpixelMap:PixelMapawaitscan.generateBarcode(barcodeConfig);// 3. 赋值给 State 变量触发 UI 刷新this.qrCodePixelMappixelMap;}catch(error){console.error(generateBarcode failed, error code:${error.code}, message:${error.message});}}}这里重点解释几个参数barcodeFormat决定生成哪种码图。QR_CODE 用于二维码CODE128 用于条形码。width / height设置码图的分辨率像素。这个值不建议太小否则密集的二维码区域会糊成一团。实际项目中建议设为显示区域的 2-3 倍方便缩放。margin码图四周的空白边距单位像素。默认值是 10二维码扫码器对边距有要求太窄可能导致识别困难。这个实现有个明显的问题——页面退出后pixelMap可能不会被回收。下面会讲怎么处理。3.2 添加 Logo 到二维码中心大部分应用场景下二维码中间需要放一个 Logo 增强品牌感。官方 API 并不直接支持 Logo 嵌入需要自己处理图片叠加。// utils/ImageUtils.etsimport{image}fromkit.ImageKit;import{common}fromkit.AbilityKit;/** * 将 Logo 图片叠加到基础二维码的 PixlMap 上 * * 注意这个操作会修改原 PixelMap建议先拷贝一份 */asyncfunctionoverlayLogoOnBarcode(basePixelMap:PixelMap,logoPixelMap:PixelMap,context:common.Context):PromisePixelMap{// 1. 获取图片源letbaseSource:image.ImageSourceimage.createImageSource(basePixelMap);letlogoSource:image.ImageSourceimage.createImageSource(logoPixelMap);// 2. 创建解码器把 PixelMap 解码为 32 位 RGBA 格式letbasePacker:image.ImagePackerimage.createImagePacker();letbasePackInfo:image.PackingOption{format:image/png,quality:100};// 这里不直接输出编码结果而是获取像素数据letbaseImageInfo:image.ImageInfoawaitbaseSource.getImageInfo(0);letbaseRowData:ArrayBuffernewArrayBuffer(baseImageInfo.size.width*baseImageInfo.size.height*4);awaitbasePixelMap.readPixelsToBuffer(baseRowData);// 3. 类似地获取 Logo 的像素数据// 4. 计算 Logo 缩放比例letlogoWidthlogoImageInfo.size.width;letlogoHeightlogoImageInfo.size.height;letscale0.2;// Logo 占二维码 20% 的宽度lettargetLogoWidthbaseImageInfo.size.width*scale;lettargetLogoHeightbaseImageInfo.size.height*scale;// 5. 计算居中位置letoffsetX(baseImageInfo.size.width-targetLogoWidth)/2;letoffsetY(baseImageInfo.size.height-targetLogoHeight)/2;// 6. 循环遍历二维码的像素数据将 Logo 区域覆盖// 这里省略逐像素混合的循环代码实际效果等同于 Image 叠加// 更推荐的做法是用 Canvas 来叠加代码更简洁}这个做法太啰嗦了更重要的是——它引入了巨大的性能开销。每生成一次都要做像素级的拷贝和混合而且在 ArkUI 里操作大量像素数据并不高效。实际项目里建议用更简单的方法把 Logo 和二维码各自作为 Image 组件用 Stack 布局叠在一起。// pages/GenerateWithLogoPage.etsimport{scan}fromkit.ScanKit;import{image}fromkit.ImageKit;EntryComponentstruct GenerateWithLogoPage{StateqrCodePixelMap:PixelMap|nullnull;// 这个 resource 代表应用资源中的 logo 图片privatelogoResource:Resource$r(app.media.app_logo);build(){Column(){Button(生成带 Logo 的二维码).onClick((){this.generateQRCode(https://developer.huawei.com);})// 用 Stack 实现叠加Stack(){Image(this.qrCodePixelMap).width(200).height(200).objectFit(ImageFit.Contain)if(this.qrCodePixelMap!null){Image(this.logoResource).width(40).height(40).borderRadius(5)// 白色背景防止 Logo 区域被误扫.backgroundColor(#FFFFFF).padding(2)}}.width(200).height(200).clip(true)}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}asyncgenerateQRCode(content:string){try{letbarcodeConfig:scan.BarcodeGenerateParams{barcodeContent:content,barcodeFormat:scan.BarcodeFormat.QR_CODE,width:400,height:400,foregroundColor:0xFF000000,backgroundColor:0xFFFFFFFF,margin:10,inputData:scan.InputDataType.Text};letpixelMap:PixelMapawaitscan.generateBarcode(barcodeConfig);this.qrCodePixelMappixelMap;}catch(error){console.error(generateBarcode failed:${JSON.stringify(error)});}}}这种做法有个隐含问题用户截图保存时只能保存底层的二维码 ImageLogo 层并不会被包含进去。如果需要保存带 Logo 的完整图片必须走像素混合路径这个下面会聊。3.3 生成条形码CODE128 格式条形码的生成方式与二维码完全一致只是换个barcodeFormat。// pages/GenerateBarcodePage.etsimport{scan}fromkit.ScanKit;EntryComponentstruct GenerateBarcodePage{StatebarcodePixelMap:PixelMap|nullnull;build(){Column(){Button(生成条形码).onClick((){this.generateCode128(ABC123456789);})Image(this.barcodePixelMap).width(300).height(100).backgroundColor(#FFFFFF)}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}asyncgenerateCode128(content:string){try{letbarcodeConfig:scan.BarcodeGenerateParams{barcodeContent:content,barcodeFormat:scan.BarcodeFormat.CODE128,// 条形码对宽度更敏感建议 width 设大一些width:600,height:200,foregroundColor:0xFF000000,backgroundColor:0xFFFFFFFF,margin:5,inputData:scan.InputDataType.Text};// 条形码建议开启纠错 (ECC)barcodeConfig.eccLevel0;// 0-4 可选CODE128 一般用 0letpixelMap:PixelMapawaitscan.generateBarcode(barcodeConfig);this.barcodePixelMappixelMap;}catch(error){console.error(generateCode128 failed:${JSON.stringify(error)});}}}几个注意点宽度一定要设够CODE128 码图由若干条和空组成宽度不够会导致条码挤出画面扫码器识别失败。建议宽度不少于 400px。高度与识别率条码高度至少应为宽度的 30%否则扫码器难以定位。输入内容CODE128 支持 ASCII 字符集但不能包含中文。如果内容包含中文就得用其他格式如 QR_CODE 或 PDF417。3.4 保存到相册生成码图之后通常需要提供保存功能。保存到相册涉及媒体读写权限。// utils/SaveToGallery.etsimport{photoAccessHelper}fromkit.MediaLibraryKit;import{image}fromkit.ImageKit;import{common}fromkit.AbilityKit;import{fileIo}fromkit.CoreFileKit;/** * 把 PixelMap 保存到系统相册 * param pixelMap 要保存的图片数据 * param context UIAbility 的 Context */asyncfunctionsaveBarcodeToGallery(pixelMap:PixelMap,context:common.Context):Promisevoid{// 1. 先检查是否有写入相册的权限// 在 module.json5 中配置ohos.permission.WRITE_IMAGEVIDEO// 运行时还需要弹窗授权// 2. 创建一个临时文件用于写入lettempDir:stringcontext.tempDir;letfileName:stringbarcode_${Date.now()}.png;letfilePath:string${tempDir}/${fileName};// 3. 使用 ImagePacker 将 PixelMap 编码为 PNGletimagePackerApi:image.ImagePackerimage.createImagePacker();letpackOpts:image.PackingOption{format:image/png,quality:100};letpackedData:ArrayBufferawaitimagePackerApi.packing(pixelMap,packOpts);// 4. 写入临时文件letfile:fileIo.FilefileIo.openSync(filePath,fileIo.OpenMode.CREATE|fileIo.OpenMode.WRITE_ONLY);fileIo.writeSync(file.fd,packedData);fileIo.closeSync(file);// 5. 通过 MediaLibrary 写入相册lethelper:photoAccessHelper.PhotoAccessHelperphotoAccessHelper.getPhotoAccessHelper(context);leturi:stringawaithelper.saveToPublicDir(photoAccessHelper.PhotoType.IMAGE,filePath);console.info(Barcode saved to gallery, uri:${uri});// 清理临时文件fileIo.unlinkSync(filePath);}这个流程涉及多个异步操作和文件 IO建议封装成 Promise 调用。需要注意packing方法在 API 12 后返回ArrayBuffer之前版本返回的是Uint8Array。如果遇到类型错误检查一下 SDK 版本。四、常见问题踩坑记录坑 1生成二维码后前景色设为白色却成了黑色现象有人想生成白底黑图把foregroundColor设为0xFFFFFFFF白色backgroundColor设为0xFF000000黑色结果输出的码图还是白底黑点扫码完全失效。原因这不是 API 的 bug而是二维码原理决定的。二维码中深色模块代表 1浅色模块代表 0。大部分扫码器默认深色是前景色。如果不反转标准扫码器就会把背景当信息去读整个码图识别失败。解法保持深色前景、浅色背景即可。如果一定要反色需要在生成后对 PixelMap 做逐像素反转同时告知扫码器反转模式生成时就标注但 Scan Kit 不提供这个参数。建议直接放弃反色方案。坑 2添加 Logo 后部分手机扫码失败现象在二维码中心放了 Logo某些手机能扫出来另一部分手机死活扫不出来。原因二维码本身有纠错等级ECC默认可能是 L 级7% 纠错。Logo 占据了中心区域破坏了定位图案和数据区域纠错能力不足以修复丢失的信息。解法生成时设置eccLevel参数barcodeConfig.eccLevel3;// H 级约 30% 纠错barcodeConfig.mask-1;// 自动选择最佳掩模另外 Logo 大小不要超过二维码尺寸的 20%同时给 Logo 加白色背景框上面示例里已经做了。如果 Logo 占据更大面积需要设计师重新设计。坑 3页面跳转返回后二维码图片变成空白现象生成二维码后跳转到详情页返回时 Image 组件显示空白。原因PixelMap是异步生成的它的引用没有被持久化。当页面重建时State变量被重置为null或者PixelMap被 GC 回收。解法使用LocalStorage或自定义AppStorage保存 PixelMap 的数据副本存为 ArrayBuffer 或文件路径页面返回后重新读取。如果只是临时跳转可以用StateProp通过路由参数传递。也可以在onPageHide中将 PixelMap 转换为 Base64 字符串存入LocalStorage返回后再解码。推荐用第 2 种方案简单且不会把图片数据留在内存中太久。五、最佳实践不要在 build() 中每次生成新的 PixelMap每个二维码生成都是异步 IO 编解码操作直接放在 build() 里会阻塞 UI 渲染。应该放在点击回调或生命周期函数中。合理复用 ByteArray 缓冲区如果频繁生成码图例如扫一扫页面里的快速切换建议先用new ArrayBuffer(size)创建固定大小的缓冲区避免多次创建大对象触发 GC。对 PixelMap 做引用计数或兜底释放每个scan.generateBarcode都会创建新的 PixelMap 对象。如果页面频繁刷新容易积累大量无用 PixelMap 导致内存上涨。建议在onPageHide或页面销毁前手动置 nullonPageHide():void{this.qrCodePixelMapnull;// 如果需要还可以调用 pixelMap.release()}六、FAQQ1为什么真机调试正常模拟器上生成二维码总是失败A模拟器没有完整的相机模块Scan Kit 的部分能力尤其是二维码纠错和掩模计算在模拟器上可能受限。建议真机调试或者检查模拟器是否启用了相机模拟。Q2生成的条形码扫码器提示无效条码但内容明明是正确的A检查两件事第一条形码格式是否选对比如内容是纯数字但用了 CODE128第二可选内容是否包含校验位。EAN-13 等格式内置校验位生成时确保参数eccLevel为 0内容长度为 12 位系统自动补第 13 位校验码。Q3保存到相册时Permission 已经申请了但依然报错 201A多媒体权限需要运行时授权并且在module.json5中声明ohos.permission.WRITE_IMAGEVIDEO。另外注意相册写入操作需要在 UIAbility 的 Context 中执行不要在 ServiceAbility 中调用。如果是在 Page 级调用需要从getContext()拿到正确的 Context。
HarmonyOS技术精讲-Scan Kit:码图生成——创建二维码与条形码
发布时间:2026/7/8 4:41:06
HarmonyOS 技术精讲Scan Kit 码图生成——创建二维码与条形码一、这个 API 到底用来干什么很多刚接触 Scan Kit 的开发者第一反应是“扫码”其实它的能力远不止于此。它自带的码图生成能力在很多场景下能省掉引入第三方库的麻烦。说白了它解决的就是**“把一段文本变成可被扫码识别的图形”**这件事。常见场景包括分享页面链接生成二维码微信/支付宝支付码二维码商品条码EAN-13 / CODE128机票/快递单号CODE128 / PDF417对比一下常见的方案方案优势劣势原生 Canvas 绘制不依赖 SDK需要自行实现编码逻辑、纠错算法、定位图形开发成本高第三方 JS 库功能丰富需要引入额外包Kotlin/JS 混编容易出兼容性问题Scan Kit generateBarcode系统级 API性能稳定支持自定义样式码图格式有上限部分特殊需求需绕行说明官方 API 提供了 QR_CODE、CODE128、EAN-13、PDF417、AZTEC 等多种格式覆盖基本能满足 95% 以上的场景。如果需要非标准码图比如工业级特定格式那就得考虑其他方案了。二、环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机另外需要先完成 Scan Kit 的开发准备——在 AppGallery Connect 中开通扫码服务。这一步如果没做API 会返回 20183401 错误码。具体参见官方文档的开发准备章节。三、核心实现3.1 基础二维码生成先跑通最基础的功能把一串文本转成二维码显示到 Image 组件上。// pages/GenerateCodePage.etsimport{scan}fromkit.ScanKit;EntryComponentstruct GenerateCodePage{StateqrCodePixelMap:PixelMap|nullnull;build(){Column(){// 生成按钮Button(生成二维码).onClick((){this.generateQRCode(https://developer.huawei.com);})// 显示码图的 Image 组件Image(this.qrCodePixelMap).width(200).height(200).backgroundColor(#FFFFFF)}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}asyncgenerateQRCode(content:string){try{// 1. 构建生成参数letbarcodeConfig:scan.BarcodeGenerateParams{// 码图内容barcodeContent:content,// 码图格式QR_CODE 对应二维码barcodeFormat:scan.BarcodeFormat.QR_CODE,// 码图宽度像素width:400,// 码图高度像素height:400,// 前景色黑色foregroundColor:0xFF000000,// 背景色白色backgroundColor:0xFFFFFFFF,// 边距像素margin:10,// 输入数据类型文本inputData:scan.InputDataType.Text};// 2. 调用生成接口返回 PixelMapletpixelMap:PixelMapawaitscan.generateBarcode(barcodeConfig);// 3. 赋值给 State 变量触发 UI 刷新this.qrCodePixelMappixelMap;}catch(error){console.error(generateBarcode failed, error code:${error.code}, message:${error.message});}}}这里重点解释几个参数barcodeFormat决定生成哪种码图。QR_CODE 用于二维码CODE128 用于条形码。width / height设置码图的分辨率像素。这个值不建议太小否则密集的二维码区域会糊成一团。实际项目中建议设为显示区域的 2-3 倍方便缩放。margin码图四周的空白边距单位像素。默认值是 10二维码扫码器对边距有要求太窄可能导致识别困难。这个实现有个明显的问题——页面退出后pixelMap可能不会被回收。下面会讲怎么处理。3.2 添加 Logo 到二维码中心大部分应用场景下二维码中间需要放一个 Logo 增强品牌感。官方 API 并不直接支持 Logo 嵌入需要自己处理图片叠加。// utils/ImageUtils.etsimport{image}fromkit.ImageKit;import{common}fromkit.AbilityKit;/** * 将 Logo 图片叠加到基础二维码的 PixlMap 上 * * 注意这个操作会修改原 PixelMap建议先拷贝一份 */asyncfunctionoverlayLogoOnBarcode(basePixelMap:PixelMap,logoPixelMap:PixelMap,context:common.Context):PromisePixelMap{// 1. 获取图片源letbaseSource:image.ImageSourceimage.createImageSource(basePixelMap);letlogoSource:image.ImageSourceimage.createImageSource(logoPixelMap);// 2. 创建解码器把 PixelMap 解码为 32 位 RGBA 格式letbasePacker:image.ImagePackerimage.createImagePacker();letbasePackInfo:image.PackingOption{format:image/png,quality:100};// 这里不直接输出编码结果而是获取像素数据letbaseImageInfo:image.ImageInfoawaitbaseSource.getImageInfo(0);letbaseRowData:ArrayBuffernewArrayBuffer(baseImageInfo.size.width*baseImageInfo.size.height*4);awaitbasePixelMap.readPixelsToBuffer(baseRowData);// 3. 类似地获取 Logo 的像素数据// 4. 计算 Logo 缩放比例letlogoWidthlogoImageInfo.size.width;letlogoHeightlogoImageInfo.size.height;letscale0.2;// Logo 占二维码 20% 的宽度lettargetLogoWidthbaseImageInfo.size.width*scale;lettargetLogoHeightbaseImageInfo.size.height*scale;// 5. 计算居中位置letoffsetX(baseImageInfo.size.width-targetLogoWidth)/2;letoffsetY(baseImageInfo.size.height-targetLogoHeight)/2;// 6. 循环遍历二维码的像素数据将 Logo 区域覆盖// 这里省略逐像素混合的循环代码实际效果等同于 Image 叠加// 更推荐的做法是用 Canvas 来叠加代码更简洁}这个做法太啰嗦了更重要的是——它引入了巨大的性能开销。每生成一次都要做像素级的拷贝和混合而且在 ArkUI 里操作大量像素数据并不高效。实际项目里建议用更简单的方法把 Logo 和二维码各自作为 Image 组件用 Stack 布局叠在一起。// pages/GenerateWithLogoPage.etsimport{scan}fromkit.ScanKit;import{image}fromkit.ImageKit;EntryComponentstruct GenerateWithLogoPage{StateqrCodePixelMap:PixelMap|nullnull;// 这个 resource 代表应用资源中的 logo 图片privatelogoResource:Resource$r(app.media.app_logo);build(){Column(){Button(生成带 Logo 的二维码).onClick((){this.generateQRCode(https://developer.huawei.com);})// 用 Stack 实现叠加Stack(){Image(this.qrCodePixelMap).width(200).height(200).objectFit(ImageFit.Contain)if(this.qrCodePixelMap!null){Image(this.logoResource).width(40).height(40).borderRadius(5)// 白色背景防止 Logo 区域被误扫.backgroundColor(#FFFFFF).padding(2)}}.width(200).height(200).clip(true)}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}asyncgenerateQRCode(content:string){try{letbarcodeConfig:scan.BarcodeGenerateParams{barcodeContent:content,barcodeFormat:scan.BarcodeFormat.QR_CODE,width:400,height:400,foregroundColor:0xFF000000,backgroundColor:0xFFFFFFFF,margin:10,inputData:scan.InputDataType.Text};letpixelMap:PixelMapawaitscan.generateBarcode(barcodeConfig);this.qrCodePixelMappixelMap;}catch(error){console.error(generateBarcode failed:${JSON.stringify(error)});}}}这种做法有个隐含问题用户截图保存时只能保存底层的二维码 ImageLogo 层并不会被包含进去。如果需要保存带 Logo 的完整图片必须走像素混合路径这个下面会聊。3.3 生成条形码CODE128 格式条形码的生成方式与二维码完全一致只是换个barcodeFormat。// pages/GenerateBarcodePage.etsimport{scan}fromkit.ScanKit;EntryComponentstruct GenerateBarcodePage{StatebarcodePixelMap:PixelMap|nullnull;build(){Column(){Button(生成条形码).onClick((){this.generateCode128(ABC123456789);})Image(this.barcodePixelMap).width(300).height(100).backgroundColor(#FFFFFF)}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}asyncgenerateCode128(content:string){try{letbarcodeConfig:scan.BarcodeGenerateParams{barcodeContent:content,barcodeFormat:scan.BarcodeFormat.CODE128,// 条形码对宽度更敏感建议 width 设大一些width:600,height:200,foregroundColor:0xFF000000,backgroundColor:0xFFFFFFFF,margin:5,inputData:scan.InputDataType.Text};// 条形码建议开启纠错 (ECC)barcodeConfig.eccLevel0;// 0-4 可选CODE128 一般用 0letpixelMap:PixelMapawaitscan.generateBarcode(barcodeConfig);this.barcodePixelMappixelMap;}catch(error){console.error(generateCode128 failed:${JSON.stringify(error)});}}}几个注意点宽度一定要设够CODE128 码图由若干条和空组成宽度不够会导致条码挤出画面扫码器识别失败。建议宽度不少于 400px。高度与识别率条码高度至少应为宽度的 30%否则扫码器难以定位。输入内容CODE128 支持 ASCII 字符集但不能包含中文。如果内容包含中文就得用其他格式如 QR_CODE 或 PDF417。3.4 保存到相册生成码图之后通常需要提供保存功能。保存到相册涉及媒体读写权限。// utils/SaveToGallery.etsimport{photoAccessHelper}fromkit.MediaLibraryKit;import{image}fromkit.ImageKit;import{common}fromkit.AbilityKit;import{fileIo}fromkit.CoreFileKit;/** * 把 PixelMap 保存到系统相册 * param pixelMap 要保存的图片数据 * param context UIAbility 的 Context */asyncfunctionsaveBarcodeToGallery(pixelMap:PixelMap,context:common.Context):Promisevoid{// 1. 先检查是否有写入相册的权限// 在 module.json5 中配置ohos.permission.WRITE_IMAGEVIDEO// 运行时还需要弹窗授权// 2. 创建一个临时文件用于写入lettempDir:stringcontext.tempDir;letfileName:stringbarcode_${Date.now()}.png;letfilePath:string${tempDir}/${fileName};// 3. 使用 ImagePacker 将 PixelMap 编码为 PNGletimagePackerApi:image.ImagePackerimage.createImagePacker();letpackOpts:image.PackingOption{format:image/png,quality:100};letpackedData:ArrayBufferawaitimagePackerApi.packing(pixelMap,packOpts);// 4. 写入临时文件letfile:fileIo.FilefileIo.openSync(filePath,fileIo.OpenMode.CREATE|fileIo.OpenMode.WRITE_ONLY);fileIo.writeSync(file.fd,packedData);fileIo.closeSync(file);// 5. 通过 MediaLibrary 写入相册lethelper:photoAccessHelper.PhotoAccessHelperphotoAccessHelper.getPhotoAccessHelper(context);leturi:stringawaithelper.saveToPublicDir(photoAccessHelper.PhotoType.IMAGE,filePath);console.info(Barcode saved to gallery, uri:${uri});// 清理临时文件fileIo.unlinkSync(filePath);}这个流程涉及多个异步操作和文件 IO建议封装成 Promise 调用。需要注意packing方法在 API 12 后返回ArrayBuffer之前版本返回的是Uint8Array。如果遇到类型错误检查一下 SDK 版本。四、常见问题踩坑记录坑 1生成二维码后前景色设为白色却成了黑色现象有人想生成白底黑图把foregroundColor设为0xFFFFFFFF白色backgroundColor设为0xFF000000黑色结果输出的码图还是白底黑点扫码完全失效。原因这不是 API 的 bug而是二维码原理决定的。二维码中深色模块代表 1浅色模块代表 0。大部分扫码器默认深色是前景色。如果不反转标准扫码器就会把背景当信息去读整个码图识别失败。解法保持深色前景、浅色背景即可。如果一定要反色需要在生成后对 PixelMap 做逐像素反转同时告知扫码器反转模式生成时就标注但 Scan Kit 不提供这个参数。建议直接放弃反色方案。坑 2添加 Logo 后部分手机扫码失败现象在二维码中心放了 Logo某些手机能扫出来另一部分手机死活扫不出来。原因二维码本身有纠错等级ECC默认可能是 L 级7% 纠错。Logo 占据了中心区域破坏了定位图案和数据区域纠错能力不足以修复丢失的信息。解法生成时设置eccLevel参数barcodeConfig.eccLevel3;// H 级约 30% 纠错barcodeConfig.mask-1;// 自动选择最佳掩模另外 Logo 大小不要超过二维码尺寸的 20%同时给 Logo 加白色背景框上面示例里已经做了。如果 Logo 占据更大面积需要设计师重新设计。坑 3页面跳转返回后二维码图片变成空白现象生成二维码后跳转到详情页返回时 Image 组件显示空白。原因PixelMap是异步生成的它的引用没有被持久化。当页面重建时State变量被重置为null或者PixelMap被 GC 回收。解法使用LocalStorage或自定义AppStorage保存 PixelMap 的数据副本存为 ArrayBuffer 或文件路径页面返回后重新读取。如果只是临时跳转可以用StateProp通过路由参数传递。也可以在onPageHide中将 PixelMap 转换为 Base64 字符串存入LocalStorage返回后再解码。推荐用第 2 种方案简单且不会把图片数据留在内存中太久。五、最佳实践不要在 build() 中每次生成新的 PixelMap每个二维码生成都是异步 IO 编解码操作直接放在 build() 里会阻塞 UI 渲染。应该放在点击回调或生命周期函数中。合理复用 ByteArray 缓冲区如果频繁生成码图例如扫一扫页面里的快速切换建议先用new ArrayBuffer(size)创建固定大小的缓冲区避免多次创建大对象触发 GC。对 PixelMap 做引用计数或兜底释放每个scan.generateBarcode都会创建新的 PixelMap 对象。如果页面频繁刷新容易积累大量无用 PixelMap 导致内存上涨。建议在onPageHide或页面销毁前手动置 nullonPageHide():void{this.qrCodePixelMapnull;// 如果需要还可以调用 pixelMap.release()}六、FAQQ1为什么真机调试正常模拟器上生成二维码总是失败A模拟器没有完整的相机模块Scan Kit 的部分能力尤其是二维码纠错和掩模计算在模拟器上可能受限。建议真机调试或者检查模拟器是否启用了相机模拟。Q2生成的条形码扫码器提示无效条码但内容明明是正确的A检查两件事第一条形码格式是否选对比如内容是纯数字但用了 CODE128第二可选内容是否包含校验位。EAN-13 等格式内置校验位生成时确保参数eccLevel为 0内容长度为 12 位系统自动补第 13 位校验码。Q3保存到相册时Permission 已经申请了但依然报错 201A多媒体权限需要运行时授权并且在module.json5中声明ohos.permission.WRITE_IMAGEVIDEO。另外注意相册写入操作需要在 UIAbility 的 Context 中执行不要在 ServiceAbility 中调用。如果是在 Page 级调用需要从getContext()拿到正确的 Context。