一、背景说明
随着鸿蒙 HarmonyOS 7(API 26)正式版开启大规模推送,开发者也随之迎来了 API 26 的完整 SDK。HarmonyOS 7.0 Release 在 Ability Kit 中新增了一个非常实用的模块:@ohos.app.ability.appMemoryOptimizer,它提供"主动释放文件页缓存"的能力,让应用可以在退后台等时机把不再使用的 .so、.hap、.hsp 页缓存还给系统,缓解整机内存压力。本文介绍这个模块的原理、完整接入步骤和实战注意事项。
二、技术原理
应用安装后,Native 动态库(.so)和包产物(.hap/.hsp)在被加载执行时,操作系统会将其文件内容以页缓存(page cache)的形式驻留在内存中,即使应用暂时用不到这部分代码,缓存也不会立刻被回收。
appMemoryOptimizer 的思路是"声明式归还":应用主动告诉系统"这些文件的页缓存我暂时不用了",系统在合适的时机将对应页缓存从内存中移除(文件本身在磁盘上,之后再次访问会重新从磁盘读入,正确性不受影响)。该模块提供两个接口:
evictFilePages(fileNames: Array<string>): Promise<void>:按文件名清单释放,文件名必须以.so、.hap或.hsp结尾;evictModuleFilePages(moduleNames: Array<string>): Promise<void>:按模块名释放,系统会读取该模块src/main/resources/rawfile/memory_optimizer.json配置文件中的evictFilePages数组,按配置清单逐个释放。
第二种方式把"哪些文件可以释放"声明化:文件清单跟着模块走,调用方只报模块名,名单变化时不用改业务代码,重新打包即可。
需要注意两点:其一,释放不保证成功------系统根据当前内存状态决定是否实际执行,Promise resolve 不等于内存已下降,真机验证要用 HiDebug 或 Profiler 看实际水位;其二,仅支持 Stage 模型,SystemCapability 为 SystemCapability.Ability.AbilityRuntime.Core。
三、实现步骤
-
环境要求:DevEco Studio 26.0.0.821(Release 版)及以上,
compileSdkVersion/targetSdkVersion为 26.0.0(API 26)。 -
在模块
src/main/resources/rawfile/下新建memory_optimizer.json(方式二使用):
json
{
"evictFilePages": [
"libheavy_model.so",
"libmedia_codec.so"
]
}
- 完整代码
entry/src/main/ets/memory/MemoryOptimizer.ets:
typescript
import { appMemoryOptimizer } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class MemoryOptimizer {
// 方式一:按文件名清单释放(文件名必须以 .so / .hap / .hsp 结尾)
static evictByFiles(fileNames: string[]): Promise<void> {
return new Promise<void>((resolve, reject) => {
appMemoryOptimizer.evictFilePages(fileNames).then(() => {
console.info(`evictFilePages ok: ${fileNames.join(',')}`);
resolve();
}).catch((err: BusinessError) => {
// 16000163: 文件类型错误(后缀不合法)
// 16000164: 配置文件解析失败(方式二)
console.error(`evictFilePages failed, code=${err.code}, msg=${err.message}`);
reject(err);
});
});
}
// 方式二:按模块名释放,清单来自模块 rawfile/memory_optimizer.json
static evictByModules(moduleNames: string[]): Promise<void> {
return new Promise<void>((resolve, reject) => {
appMemoryOptimizer.evictModuleFilePages(moduleNames).then(() => {
console.info(`evictModuleFilePages ok: ${moduleNames.join(',')}`);
resolve();
}).catch((err: BusinessError) => {
console.error(`evictModuleFilePages failed, code=${err.code}, msg=${err.message}`);
reject(err);
});
});
}
}
- 在 Ability 中配合内存水位回调使用,
entry/src/main/ets/entryability/EntryAbility.ets:
typescript
import { AbilityConstant, AbilityStage, UIAbility, Want } from '@kit.AbilityKit';
import { MemoryOptimizer } from '../memory/MemoryOptimizer';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 监听系统内存水位,LOW 时归还页缓存
this.context.getApplicationContext().on('memoryLevel', (level: number) => {
if (level === 1) { // MEMORY_LEVEL_LOW
MemoryOptimizer.evictByFiles(['libheavy_model.so']).catch(() => {});
}
});
}
// 应用退后台是典型时机:把不再使用的 so 页缓存还回去
onBackground(): void {
MemoryOptimizer.evictByModules(['entry']).catch(() => {});
}
}
说明:memoryLevel 回调中 1 对应 MEMORY_LEVEL_LOW(低内存),也可以直接实现 AbilityStage 的 onMemoryLevel。appMemoryOptimizer 不需要额外权限配置,module.json5 无需新增 requestPermissions。
- UI 调用示例
entry/src/main/ets/pages/Index.ets:
typescript
import { MemoryOptimizer } from '../memory/MemoryOptimizer';
@Entry
@Component
struct Index {
@State log: string = '点击按钮释放页缓存';
build() {
Column({ space: 16 }) {
Text(this.log).fontSize(16)
Button('释放指定 so 页缓存')
.onClick(() => {
MemoryOptimizer.evictByFiles(['libheavy_model.so'])
.then(() => this.log = '释放请求已提交')
.catch(() => this.log = '释放失败,见日志');
})
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
}
}
四、应用场景示例
典型场景是端侧 AI 应用:大模型推理引擎的 libllm_runtime.so 动辄几十 MB,推理完一轮对话后短时间不会再执行。可以在推理结束并退后台时归还:
typescript
import { common } from '@kit.AbilityKit';
export async function onInferenceDone(context: common.UIAbilityContext): Promise<void> {
try {
await MemoryOptimizer.evictByFiles(['libllm_runtime.so', 'libtokenizers.so']);
} catch (e) {
// 归还失败不影响业务,下次低内存时系统仍可自行回收
}
}
另一个场景是含多个功能模块的大型应用(如修图 + 视频 + 直播打包在一个 HAP):用户只用修图时,视频、直播模块的 so 页缓存都可以通过 evictModuleFilePages(['video_module']) 一次性声明归还。
五、技术要点
- 不要把 resolve 当成"内存已降" :官方文档明确释放不保证成功,验证请用 HiDebug
getPss或 DevEco Profiler 观察实际水位。 - 时机选择 :最佳挂点是
onBackground/onMemoryLevel(MEMORY_LEVEL_LOW);前台高频调用没有意义,还可能触发二次读盘开销。 - 错误处理 :文件名后缀不合法抛
16000163,配置文件解析失败抛16000164。把用户任意输入的路径塞进evictFilePages没有意义------它只管安装产物,不是通用文件缓存清理器。 - 性能注意:释放后若代码再次执行,页面会从磁盘重新读入,存在一次缺页开销。对延迟敏感的路径(正在播放的解码库、正在渲染的引擎)不要归还。
- 版本限制 :仅 Stage 模型、API 26 及以上可用;低版本上调用会报接口不存在,需做
canIUse或 try/catch 兜底。
六、扩展建议
可以进一步封装一个"水位分级归还"策略:MEMORY_LEVEL moderate 只归还低频模块,low 归还全部可释放清单。用 Promise.all 批量提交:
typescript
Promise.all([
MemoryOptimizer.evictByModules(['video_module']),
MemoryOptimizer.evictByFiles(['libheavy_model.so'])
]).catch(() => {});
在分布式应用中,还可以结合设备负载信息,让算力富裕的设备不做归还、内存紧张的设备积极归还,形成跨设备差异化的内存策略。
结语
appMemoryOptimizer 是 HarmonyOS 7(API 26)带给开发者的一个"小而实"的接口:一个配置文件加几十行代码,就能把空闲 so 的页缓存主动还给系统。配合 onMemoryLevel 监听使用,对大体积 Native 库的应用(AI、音视频、游戏引擎)是实打实的内存收益,建议在适配 API 26 时顺手接入。