鸿蒙应用在实际运行中存在一笔"看不见的内存账":应用通过 dlopen 加载 so 库、或通过 mmap 读取 HSP 包内资源时,内核会把文件内容映射进页缓存(page cache),这些页会计入进程的内存占用。在 HarmonyOS 7(API 26)之前,应用对此完全无能为力——页缓存归内核管理,既查不到明细,也没有接口主动归还,只能被动等待系统在内存紧张时回收,时机不受控制。HarmonyOS 7(API 26)Release 新增了 @ohos.app.ability.appMemoryOptimizer 模块,把页缓存的主动权交还给应用,这也是本次版本更新中对内存敏感型应用(图片编辑、地图、大型游戏等)最有实用价值的能力之一。

二、技术原理

页缓存的产生机制:当应用加载 so、hap、hsp 等安装产物时,内核为了加速后续访问,会把文件内容缓存到页缓存中。这部分内存出现在进程账单里,但应用既无法枚举也无法释放。

appMemoryOptimizer 提供两个接口(均从 @kit.AbilityKit 导入,仅支持 Stage 模型,SystemCapability 为 SystemCapability.Ability.AbilityRuntime.Core):

  1. evictFilePages(fileNames: Array<string>): Promise<void>——请求系统释放指定文件的页缓存,文件名必须以 .so、.hap 或 .hsp 结尾,否则抛出错误码 16000163(文件类型错误)。
  2. evictModuleFilePages(moduleNames: Array<string>): Promise<void>——按模块名释放页缓存。系统会读取该模块 src/main/resources/rawfile/memory_optimizer.json 配置文件中的 evictFilePages 数组,按声明的文件清单逐个释放。这种方式把"哪些文件可以释放"声明化:清单跟着模块走,名单变化不需要改调用代码,重新打包即可;配置解析失败会抛出错误码 16000164。

需要注意:释放不保证成功。系统根据当前内存状态决定是否实际执行释放,调用成功(Promise resolve)不代表内存一定下降,真机验证要用 HiDebug 或 Profiler 观察实际水位。

三、实现步骤
  1. 环境要求:DevEco Studio 26.0.0.821 及以上,targetSdkVersion/编译 SDK 为 26.0.0(API 26),Stage 模型工程。

  2. (可选)在模块的 src/main/resources/rawfile/memory_optimizer.json 中声明可释放清单,配合 evictModuleFilePages 使用:

{
  "evictFilePages": [
    "libimage_process.so",
    "libmap_engine.so"
  ]
}
  1. 完整代码文件 src/main/ets/pages/MemoryOptimizerPage.ets,演示两种释放方式、错误处理与 onMemoryLevel 组合:
import { appMemoryOptimizer, common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'MemoryOptimizerPage';

@Entry
@Component
struct MemoryOptimizerPage {
  @State resultMsg: string = '未执行';
  private context: common.UIAbilityContext | undefined =
    getContext(this) as common.UIAbilityContext;

  // 方式一:按文件名释放(只接受 .so / .hap / .hsp 结尾)
  private async evictByFileNames(): Promise<void> {
    try {
      await appMemoryOptimizer.evictFilePages([
        'libimage_process.so',
        'libmap_engine.so'
      ]);
      this.resultMsg = '按文件名释放请求已提交';
      hilog.info(0x0000, TAG, 'evictFilePages ok');
    } catch (err) {
      // 16000163: 文件类型错误;16000164: 配置文件解析失败
      this.resultMsg = `释放失败 code=${err.code} msg=${err.message}`;
      hilog.error(0x0000, TAG, 'evictFilePages failed: %{public}d', err.code);
    }
  }

  // 方式二:按模块名释放(清单由 memory_optimizer.json 声明)
  private async evictByModuleNames(): Promise<void> {
    try {
      await appMemoryOptimizer.evictModuleFilePages(['entry']);
      this.resultMsg = '按模块释放请求已提交';
    } catch (err) {
      this.resultMsg = `释放失败 code=${err.code} msg=${err.message}`;
    }
  }

  build() {
    Column({ space: 16 }) {
      Text('鸿蒙页缓存主动释放示例')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Text(`状态:${this.resultMsg}`)
        .fontSize(14)
      Button('按文件名释放')
        .onClick(() => {
          this.evictByFileNames();
        })
      Button('按模块释放')
        .onClick(() => {
          this.evictByModuleNames();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
  1. 与内存级别监听组合(在 UIAbility 中,退后台或收到低内存信号时执行释放):
import { AbilityConstant, UIAbility, Want, ConfigurationConstant } from '@kit.AbilityKit';
import { appMemoryOptimizer } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 监听系统内存级别
    this.context.getApplicationContext().on('memoryLevel', (level) => {
      if (level === 1 /* MEMORY_LEVEL_LOW */ || level === 2 /* MEMORY_LEVEL_CRITICAL */) {
        appMemoryOptimizer.evictFilePages(['libimage_process.so'])
          .then(() => {
            console.info('page cache evict requested');
          })
          .catch((err: BusinessError) => {
            console.error(`evict failed: ${err.code}`);
          });
      }
    });
  }
}
四、应用场景示例

图片编辑器在用户切走大工程、应用退后台时,把不再使用的图像处理 so 页缓存归还系统;地图应用在用户离线后释放地图引擎 so 的页缓存;游戏在切出对局后释放大体积 HSP 资源缓存。以下是在 Ability 的 onBackground 生命周期中调用的示例:

import { UIAbility } from '@kit.AbilityKit';
import { appMemoryOptimizer } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onBackground(): void {
    // 退后台时归还不再使用的 so 页缓存
    appMemoryOptimizer.evictFilePages(['libimage_process.so'])
      .catch((err: BusinessError) => {
        console.error(`evict failed: ${err.code}`);
      });
  }
}
五、技术要点
  1. 鉴权与适用范围:该能力为系统开放接口,无需用户授权,但仅支持 Stage 模型,API 26 起可用;FA 模型工程无法使用。
  2. 时机选择:释放动作建议挂 onBackground 或 onMemoryLevel() 的低内存档位,二者是一对组合拳——监听是"知道该还了",appMemoryOptimizer 是"还的动作"。前台高频调用没有意义。
  3. 错误处理:文件名不以 .so/.hap/.hsp 结尾抛 16000163,配置文件解析失败抛 16000164。把用户任意输入的路径直接塞进 evictFilePages 没有意义,它只管这三类安装产物,不是通用的文件缓存清理器。
  4. 结果校验:不要只看 Promise 是否 resolve,真机用 HiDebug 或 Profiler 观察释放前后的进程内存水位,确认收益真实存在。
六、扩展建议

进阶方向一:把可释放清单全部迁移到 evictModuleFilePages 的声明式配置,业务代码只报模块名,so 名单变化不用改代码。进阶方向二:结合支配树分析(DevEco Studio 26.0.0.821 的 ArkTS Snapshot 新增一键获取 Dominator Tree)先定位真正的内存大头,再有针对性地决定哪些 so 需要归还页缓存,避免盲目释放。

结语

本文介绍了 HarmonyOS 7(API 26)新增的 appMemoryOptimizer 页缓存主动释放能力,完整代码不到 100 行,配合 memory_optimizer.json 声明式配置即可接入。对内存敏感的鸿蒙应用来说,这是本次版本更新中投入产出比最高的新能力之一,建议尽快在退后台与低内存场景中落地。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐