HarmonyOS 应用内存优化实战:从LRUCache到图片尺寸完整方案
图片尺寸调整:最容易忽视的内存浪费,手动调整源文件尺寸使其与组件大小一致,避免不必要的内存浪费,节省大量内存LRUCache缓存管理:使用LRU算法缓存频繁访问的数据,缓存空间不足时替换最近最少使用的数据,确保缓存空间有效利用生命周期管理:及时释放不再使用的资源、销毁对象、注销订阅事件,避免内存泄漏onMemoryLevel监听:根据系统内存变化动态调整应用内存,系统内存不足时释放不必要的资源。
为什么内存优化这么重要
应用功能越来越复杂,内存占用也跟着涨。内存是稀缺资源,一旦占用过高,系统就开始频繁回收和分配内存,应用性能下降、卡顿、甚至崩溃。把内存占用降下来,应用响应速度更快,系统资源节省,设备运行效率提升,续航时间也能延长。
HarmonyOS提供了几个实用的内存管理工具:onMemoryLevel()监听系统内存变化、LRUCache缓存管理、生命周期管理释放资源、Purgeable Memory机制管理可回收内存、图片尺寸调整避免浪费。
图片尺寸:最容易忽视的内存浪费
页面多、图片多、效果丰富的应用,内存最容易超标。一张全屏图片,不同分辨率内存占用差好几倍。比如4032×3024的RGBA格式图片,加载到500×500的Image组件里,图片申请约46.5MB内存,组件实际只需要约1MB。
纹理图片内存大小计算公式:imageWidth × imageHeight × format。4032×3024×4 = 48771072 bytes ≈ 46.5M。
图片尺寸超过控件显示区域,会被裁剪或缩放,频繁裁剪缩放不仅降低视觉效果,还浪费内存增加功耗。最直接的优化方法是手动调整源文件尺寸,使其与组件大小一致,避免不必要的内存浪费。
Column() {
Image($r('app.media.image'))
.width("500px")
.height("500px")
}
用500×500尺寸的Image组件加载一张4032×3024的RGBA格式图片,内存浪费了45.5MB。改成500×500的源文件,内存占用降到约1MB,节省98%内存。
LRUCache:缓存管理的利器

LRU(最近最少使用)算法基于时间局部性原理,最近被访问的数据在未来被访问概率较高。LRUCache是ArkTS中常用的缓存工具,基于LRU算法实现,主要用于缓存频繁访问的数据,如图片和网络请求结果。
LRUCache通过LinkedHashMap实现,HashMap用于快速查找数据,LinkedHashMap双向链表用于记录数据顺序。LruCache中LinkedHashMap的顺序设置为LRU顺序,链表头部的对象为近期最少用到的对象。
缓存空间不足时,根据LRU算法替换最近最少使用的数据,确保缓存空间有效利用。
import { util } from '@kit.ArkTS';
export class LRUCacheUtil {
private static instance: LRUCacheUtil;
private lruCache: util.LRUCache<string, Object>;
private constructor() {
this.lruCache = new util.LRUCache(64);
}
public static getInstance(): LRUCacheUtil {
if (!LRUCacheUtil.instance) {
LRUCacheUtil.instance = new LRUCacheUtil();
}
return LRUCacheUtil.instance;
}
public isEmpty(): boolean {
return this.lruCache.isEmpty();
}
public getCapacity(): number {
return this.lruCache.getCapacity();
}
public updateCapacity(newCapacity: number): void {
this.lruCache.updateCapacity(newCapacity);
}
public putCache(key: string, value: Object): void {
this.lruCache.put(key, value);
}
public remove(key: string): void {
this.lruCache.remove(key);
}
public getCache(key: string): Object | undefined {
return this.lruCache.get(key);
}
public contains(key: string): boolean {
return this.lruCache.contains(key);
}
public clearCache(): void {
this.lruCache.clear();
this.lruCache.updateCapacity(64);
}
}
设计缓存工具类,包含LRUCache单例及操作LRUCache的方法,通过静态方法获取实例确保全局唯一。缓存工具类支持各组件间共享缓存数据,避免重复创建实例和数据冗余,提高系统性能和效率,减少内存占用,提升数据访问速度。
在组件中使用缓存:
import { LRUCacheUtil } from '../utils/LRUCacheUtil';
@Entry
@Component
struct Index {
aboutToAppear(): void {
const lruCache: LRUCacheUtil = LRUCacheUtil.getInstance();
lruCache.putCache('nation', 10);
lruCache.putCache('menu', 8);
const result0: number = lruCache.getCache('2') as number;
console.log('result0:' + result0);
lruCache.remove('2');
const result2: boolean = lruCache.contains('1');
console.log('result2:' + result2);
lruCache.updateCapacity(110);
}
build() {
Row() {
Column() {
Text('Hello World')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
LRUCache常用方法:
- get():根据key查询对应value,查询到后将对象移到链表尾端并返回
- put():将key-value对添加到缓存,新对象存储在链表尾端,缓存达到最大值时移除链表头部对象
- remove():删除key对应的缓存value
- updateCapacity():设置缓存存储容量,新容量小于原容量时仅保留新容量大小的数据
生命周期管理:及时释放资源
管理对象生命周期,释放资源、销毁对象、优化ArkTS内存。在UIAbility组件生命周期中,Create或Foreground方法中创建资源,Background或Destroy方法中销毁资源。在页面生命周期中,onPageShow()方法中创建资源,onPageHide()方法中销毁对应资源。在组件生命周期中,aboutToAppear()方法中创建资源,aboutToDisappear()方法中销毁不再使用的对象、注销不再使用的订阅事件。
aboutToDisappear函数在组件销毁前执行,取消订阅默认网络状态变化的通知:
import { connection } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { Logger } from '../utils/Logger';
@Entry
@Component
struct Index {
@State networkId: string = '123';
@State netMessage: string = '初始化网络成功';
@State connectionMessage: string = '链接成功';
@State netStateMessage: string = '';
private netCon: connection.NetConnection | null = null;
aboutToDisappear(): void {
this.unUseNetworkRegister();
}
useNetworkRegister(): void {
this.netCon = connection.createNetConnection();
this.netStateMessage += '连接';
this.netCon.register((error) => {
if (error) {
Logger.error('register error:' + error.message);
return;
}
this.getUIContext().getPromptAction().showToast({
message: '连接成功',
duration: 1000
});
})
this.netCon.on('netAvailable', (netHandle) => {
this.netStateMessage += '连接' + netHandle.netId + '\n';
})
this.netCon.on('netBlockStatusChange', (data) => {
this.netStateMessage += '更换' + data.netHandle.netId + '\n';
})
}
unUseNetworkRegister(): void {
if (this.netCon) {
this.netCon.unregister((error: BusinessError) => {
if (error) {
Logger.error('unregister error:' + error.message);
return;
}
this.netStateMessage += 'listener';
})
} else {
this.netStateMessage += 'listener_fail';
}
}
build() {
Column() {
Text('Hello Word')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width(200)
}
}
onMemoryLevel:监听系统内存变化
onMemoryLevel()是HarmonyOS提供的监听系统内存变化的接口,开发者可以调整应用内存。回调包括三种方式:AbilityStage、UIAbility和EnvironmentCallback。
MemoryLevel分为三种等级:
- MEMORY_LEVEL_MODERATE(值为0):系统内存达到中等水平,系统根据LRU缓存规则开始杀死进程
- MEMORY_LEVEL_LOW(值为1):系统内存不足,应释放不必要的资源以提升系统性能
- MEMORY_LEVEL_CRITICAL(值为2):系统内存严重不足,应立即释放所有不必要的资源,系统可能终止所有缓存中的进程,并开始终止应当保持运行的进程
结合onMemoryLevel()监听内存变化,设置对应清理缓存的机制:
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { LRUCacheUtil } from '../utils/LRUCacheUtil';
export default class EntryAbility extends UIAbility {
onMemoryLevel(level: AbilityConstant.MemoryLevel): void {
if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_CRITICAL) {
console.log('The memory of device is critical, release memory.');
if (!LRUCacheUtil.getInstance().isEmpty()) {
LRUCacheUtil.getInstance().clearCache();
}
}
}
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
console.info('Ability onCreate');
}
onDestroy(): void {
console.info('Ability onDestroy');
}
}
后台已冻结的应用,AbilityStage、UIAbility和EnvironmentCallback的onMemoryLevel()不可回调。
Purgeable Memory:C++内存管理机制
Purgeable Memory是HarmonyOS中native层的内存管理机制,适用于图像处理的Bitmap、流媒体应用的一次性数据和图片。应用可使用Purgeable Memory存放内部缓存数据,系统根据淘汰策略管理所有Purgeable内存。当系统内存不足时,系统通过丢弃Purgeable内存快速回收资源,释放更多内存给其他应用程序。
访问Purgeable内存的流程:判断Purgeable内存的数据是否已被回收,如果已回收需重建数据。访问Purgeable内存时,引用计数refcnt加1;访问结束后,refcnt减1。当refcnt为0时,Purgeable内存可被系统回收。
Purgeable内存回收流程:当引用计数为0时,丢弃Purgeable内存中的数据,并标记为已回收。
urgeable内存访问流程图
Purgeable内存回收流程图
在CMakeLists.txt文件中引入Purgeable对应的动态链接库:
cmake_minimum_required(VERSION 3.4.1)
project(MyNativeApplication)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libpurgeable_memory_ndk.z.so)
使用Purgeable Memory:
#include "napi/native_api.h"
#define DATASIZE (4 * 1024 * 1024)
#include "purgeable_memory/purgeable_memory.h"
bool ModifyFunc(void *data, size_t size, void *param) {
data = param;
return true;
}
static napi_value Add(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
double value0;
napi_get_value_double(env, args[0], &value0);
double value1;
napi_get_value_double(env, args[1], &value1);
double result = value0 + value1;
OH_PurgeableMemory *pPurgmem = OH_PurgeableMemory_Create(DATASIZE, ModifyFunc, &result);
OH_PurgeableMemory_BeginRead(pPurgmem);
size_t size = OH_PurgeableMemory_ContentSize(pPurgmem);
ReqObj *pReqObj = (ReqObj *)OH_PurgeableMemory_GetContent(pPurgmem);
OH_PurgeableMemory_EndRead(pPurgmem);
OH_PurgeableMemory_BeginWrite(pPurgmem);
double newResult = value0 + value0;
OH_PurgeableMemory_AppendModify(pPurgmem, ModifyFunc, &newResult);
OH_PurgeableMemory_EndWrite(pPurgmem);
OH_PurgeableMemory_Destroy(pPurgmem);
napi_value sum;
napi_create_double(env, result, &sum);
return sum;
}
读取PurgeableMemory对象内容时,需要调用OH_PurgeableMemory_BeginRead,读取完成后调用OH_PurgeableMemory_EndRead。OH_PurgeableMemory_GetContent可以获取PurgeableMemory对象的内存数据。
修改PurgeableMemory对象内容时,需要调用OH_PurgeableMemory_BeginWrite,修改完成后调用OH_PurgeableMemory_EndWrite。OH_PurgeableMemory_AppendModify可以更新PurgeableMemory对象重建规则。
其他内存优化技巧
使用弱引用(Weak Reference):在HarmonyOS应用开发中,使用弱引用避免内存泄漏,避免循环引用导致的内存泄漏问题,确保对象在不再需要时能够被正确释放。
使用Sendable:符合Sendable协议的数据可以在ArkTS并发实例间传递,减少拷贝的开销及其内存。
使用可共享对象:共享对象SharedArrayBuffer拥有固定长度,可以存储任何类型的数据,包括数字、字符串等。SharedArrayBuffer支持在多线程之间传递,传递之后的SharedArrayBuffer对象和原始的SharedArrayBuffer对象指向同一块内存,达到内存共享的目的。
优化总结
内存优化不是一蹴而就的,需要从多个维度入手:
- 图片尺寸调整:最容易忽视的内存浪费,手动调整源文件尺寸使其与组件大小一致,避免不必要的内存浪费,节省大量内存
- LRUCache缓存管理:使用LRU算法缓存频繁访问的数据,缓存空间不足时替换最近最少使用的数据,确保缓存空间有效利用
- 生命周期管理:及时释放不再使用的资源、销毁对象、注销订阅事件,避免内存泄漏
- onMemoryLevel监听:根据系统内存变化动态调整应用内存,系统内存不足时释放不必要的资源
- Purgeable Memory:适用于图像处理的Bitmap、流媒体应用的一次性数据,系统内存不足时快速回收资源
- 其他优化技巧:弱引用避免内存泄漏、Sendable减少拷贝开销、SharedArrayBuffer实现内存共享
通过这些优化手段,可以有效减少应用内存占用,提高应用性能和响应速度,节省系统资源,提升设备运行效率,延长设备续航时间。开发者在应用开发过程中应注重内存管理,采取措施减少内存占用,优化应用性能和用户体验。
更多推荐
所有评论(0)