写点什么

HarmonyOS 开发常见问题及解答(二)

  • 2026-09-20
    北京
  • 本文字数:3951 字

    阅读完需:约 13 分钟

AI摘要

HarmonyOS中ImageSource与PixelMap资源未显式释放,易引发ashmem/ION内存泄漏,尤其在高频调用getImageProperty或异步图片加载场景。

  1. createPixelMap后必须显式调用pixelMap.release(),否则共享内存不回收。
  2. 解析大图Exif时需清理旋转缓存,避免ION内存持续占用。
  3. 组件销毁时须终止异步下载任务,防止对Image组件的强引用泄漏。

适合HarmonyOS应用开发者、移动端性能优化工程师、系统级内存问题排查人员阅读。

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:HarmonyOS开发常见问题及解答(二)-华为开发者话题 | 华为开发者联盟

FAQ 1:HarmonyOS imageSource.getImageProperty 接口内存泄漏问题如何分析?

【解决方案】:

【问题定位】

一、ImageSource/PixelMap 对象未释放:

调用 createPixelMap 后未主动调用 release()释放资源,导致底层共享内存(如/dev/ashmem/EXT RawData)未回收。

多次调用 getImageProperty 可能累积未释放的临时对象。

二、Exif 数据解析残留:

解析大尺寸图片的 Exif 信息时,若未正确处理旋转后的 PixelMap 缓存,可能占用 ION 内存。

三、异步操作未终止:

网络图片下载过程中组件销毁,但异步任务仍持有对 Image 组件的强引用。

【修改建议】

1、显式释放资源

import image from '@kit.ImageKit';async function loadImage() {  let imageSource: image.ImageSource | null = null;  try {    imageSource = image.createImageSource(filePath);    const pixelMap = await imageSource.createPixelMap();    // 使用pixelMap后显式释放    pixelMap.release();  } catch (err) {    console.error("Error:", err);  } finally {    // 释放ImageSource    imageSource?.release();  }}
复制代码

2、组件生命周期管理

import { image } from '@kit.ImageKit';@Entry@Componentstruct ImageComponent {  @State pixelMap: image.PixelMap | null = null;  aboutToDisappear() {    this.pixelMap?.release(); // 组件销毁前释放资源  }  build() {    Column() {      Image(this.pixelMap)        .onClick(async () => {          const src = image.createImageSource(networkUrl);          this.pixelMap = await src.createPixelMap();        })    }  }}
复制代码

3、异步任务管理

import { taskpool } from '@kit.ArkTS';// 使用TaskPool避免持有组件引用taskpool.execute(async () => {  const src = image.createImageSource(filePath);  const orientation = src.getImageProperty('Orientation');  src.release();  return orientation;});
复制代码

【典型错误场景】:

案例:未关闭文件流导致 ION 泄漏

// 错误示例:未释放ImageSourceimageSource.createPixelMap().then(pixelMap => {  this.image = pixelMap;});// 正确修正:在Promise链中释放imageSource.createPixelMap().then(pixelMap => {  this.image = pixelMap;  imageSource.release(); // 显式释放});
复制代码

FAQ 2:har 文件间相互依赖导致运行时模块导入错误,无法正确使用导出的代码,导致运行崩溃,如何分析?

【解决方案】:

HAR 模块间动态 import 依赖解耦

当应用包含多个 HAR 包,HAR 包之间的依赖关系比较复杂。在 DevEco Studio 中配置依赖关系时,可能会形成循环依赖。这时,如果 HAR 之间的依赖关系中仅有变量动态 import,可以将 HAR 包之间直接依赖关系转移到 HAP/HSP 中配置,HAR 包之间无需配置依赖关系,从而达到 HAR 包间依赖解耦的目的。如下示意图:

HAR 之间的依赖关系转移至 HAP/HSP 后:

使用限制

   • 仅限在本地源码 HAR 包之间存在循环依赖时,使用该规避方案。

   • 被转移依赖的 HAR 之间只能通过变量动态 import,不能有静态 import 或常量动态 import。

   • 转移依赖时,需同时转移 dependencies 和 runtimeOnly 依赖配置。

   • HSP 不支持转移依赖。即:HAP->HSP1->HSP2->HSP3,这里的 HSP2 和 HSP3 不能转移到 HAP 上面。

   • 转移依赖的整个链路上只能有 HAR 包,不能跨越 HSP 转移。即:HAP->HAR1->HAR2->HSP->HAR3->HAR4,HAR1 对 HAR2 的依赖可以转移到 HAP 上,HAR3 对 HAR4 的依赖可以转移到 HSP 上。但是,不能将 HAR3 或 HAR4 转移到 HAP 上。

   • 如果引用了其他工程模块、远程包或集成 HSP,需确保在工程级build-profile.json5文件中的 useNormalizedOHMUrl 配置一致,同时设置为 true 或 false,否则可能导致运行错误:Cannot find dynamic-import module library。

FAQ 3:通过 resourceManager.getStringResource 接口获取 HSP 资源文件报“Resource id invalid”错误 如何解决?

【详细场景】:

通过 this.resourceManager.getStringResource($r('app.string.PlayCount').id)获取 HSP 资源文件时出现错误。

错误消息:资源 ID 无效。

错误代码:9001001

SourceCode:returnResource = this.context.resourceManager.getStringSync(id)。
复制代码

【解决方案】:

根据模块名创建上下文 Context,然后使用 getStringByNameSync 方法获取指定资源名称对应的字符串。具体示例代码如下:

import { common, application } from '@kit.AbilityKit';import { BusinessError } from '@kit.BasicServicesKit';import { JSON } from '@kit.ArkTS'; @Entry@Componentstruct Index {  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;   build() {    Column() {      Button()        .onClick(() => {          // Create a context based on the module name          let moduleName: string = 'library';          application.createModuleContext(this.context, moduleName)            .then((data: common.Context) => {              console.info(`CreateModuleContext success, data: ${JSON.stringify(data)}`);              if (data !== null) {                this.getUIContext().getPromptAction().showToast({                  message: ('get Context success')                });              }               // Then run getStringByNameSync to obtain the string corresponding to the specified resource name              try {                let str = data.resourceManager.getStringByNameSync('shared_desc');                console.info(`getStringByNameSync, data: ${JSON.stringify(str)}`);              } catch (error) {                let code = (error as BusinessError).code;                let message = (error as BusinessError).message;                console.error(`getStringByNameSync failed, error code: ${code}, message: ${message}.`);              }            })            .catch((err: BusinessError) => {              console.error(`CreateModuleContext failed, err code:${err.code}, err msg: ${err.message}`);            });        })    }  }}
复制代码

参考链接

 应用上下文Context、获取本应用中其他module的context、getStringByNameSync

FAQ 4:对于数据量大的赋值操作造成的卡顿和崩溃,该怎么解决?

【解决方案】:

数据精准懒加载:

当数据源总长度较长,或数据项加载耗时较长时,可使用 Repeat 数据精准懒加载特性,避免在初始化时加载所有数据。Repeat 数据精准懒加载特性从 API version 19 开始支持。

开发者可以设置.virtualScroll()的 totalCount 属性值或 onTotalCount 自定义方法用于计算期望加载的数据项总数,设置 onLazyLoading 属性实现数据精准懒加载,实现在节点首次渲染时加载对应的数据。详细说明和注意事项见VirtualScrollOptions。

示例

数据源总长度较长,在首次渲染、滑动屏幕、跳转显示区域时,动态加载对应区域内的数据。

@Entry@ComponentV2struct RepeatLazyLoadingLongData {  // 假设数据源总长度较长,为1000。初始数组未提供数据。  @Local arr: Array<string> = [];  scroller: Scroller = new Scroller();   build() {    Column({ space: 5 }) {      // 初始显示位置为index = 100,数据可通过懒加载自动获取。      List({ scroller: this.scroller, space: 5, initialIndex: 100 }) {        Repeat(this.arr)          .virtualScroll({            // 期望的数据源总长度为1000。            onTotalCount: () => {              return 1000;            },            // 实现数据懒加载。            onLazyLoading: (index: number) => {              this.arr[index] = index.toString();            }          })          .each((obj: RepeatItem<string>) => {            ListItem() {              Row({ space: 5 }) {                Text(`${obj.index}: Item_${obj.item}`)              }            }            .height(50)          })      }      .height('80%')      .border({ width: 1 })      // 显示位置跳转至index = 500,数据可通过懒加载自动获取。      Button('ScrollToIndex 500')        .onClick(() => {          this.scroller.scrollToIndex(500);        })    }  }}
复制代码

更多实例及运行效果详见:Repeat:可复用的循环渲染-学习UI范式渲染控制-UI开发 (ArkTS声明式开发范式)-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

FAQ 5:使用向导创建的 worker 线程文件中导出一个 TestThrdMsg 类必现 JSCrash,如何解决?

【解决方案】:

Worker 简介的其它注意事项中有说明:在 Worker 文件中禁止使用 export 语法导出任何内容,否则会导致 jscrash 问题。

使用过程中可参考 Worker 简介:Worker简介-多线程并发-ArkTS并发-ArkTS(方舟编程语言)-应用框架- 华为HarmonyOS开发者


🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5

🔗 社区 DFX 专题文章: https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001

【扫码加入 HarmonyOS DFX 技术交流群】