内存优化指导
1. 概述
随着业务功能增加,RN页面会逐渐变得复杂,应用内存占用也会随之增加。RN场景中的内存不仅包括 JS运行时对象,还包括图片解码内存、页面缓存、组件状态、ArkUI节点和业务侧临时数据等。当应用占用过多内存时,系统可能频繁进行内存回收和重新分配,导致页面卡顿、掉帧、后台驻留能力下降,严重时还可能触发进程被系统终止。
为了帮助开发者优化应用内存占用,RNOH提供了以下几类能力和适配点:
- onMemoryLevel():开发者可通过该接口监听系统内存变化,并在不同内存压力等级下执行对应的内存回收策略。
- 虚拟机后台GC:在应用进入后台时触发虚拟机GC,释放可回收的JS内存。
- 虚拟机调参:通过JSVM启动参数预设,在内存占用和运行性能之间做平衡。
- 图片Resize适配:在图片尺寸明显大于显示区域时,优先使用resize解码策略,减少图片解码占用。
2. 使用onMemoryLevel监听内存变化
2.1 原理介绍
HarmonyOS提供了onMemoryLevel()接口,用于通知应用当前系统内存压力状态。参考HarmonyOS最佳实践,内存等级通常分为以下三档:
- MEMORY_LEVEL_MODERATE:系统内存达到中等水平。
- MEMORY_LEVEL_LOW:系统内存不足,应释放不必要资源。
- MEMORY_LEVEL_CRITICAL:系统内存非常紧张,应尽快释放更多资源。
RNOH已接入该能力。当应用继承RNAbility时,框架会接收系统的内存等级变化,并将内存压力信号传递到RN运行时,用于协助执行内存回收。
对于开发者而言,onMemoryLevel()的主要作用是:在系统真正进入极低内存前,提前清理业务可重建资源,例如图片缓存、列表缓存、离屏页面临时状态和预加载数据等,从而降低内存持续增长带来的风险。
注意:后台已冻结的应用不会收到onMemoryLevel()回调,因此该接口更适合用于前台或可运行状态下的分级回收。
2.2 适配方法
对于RNOH文档,开发者通常更关注JS侧如何感知内存压力并执行回收策略。只要应用已按RNOH标准方式接入RNAbility,框架会负责将系统onMemoryLevel()事件透传到JS侧。
开发者可以通过AppState.addEventListener('memoryLevelChange', callback)监听内存压力变化。
memoryLevelChange回调参数格式如下:
{ level: number; levelName: string;}
常见取值如下:
表1 内存等级对照表
| level | levelName | 说明 |
|---|---|---|
| 0 | MEMORY_LEVEL_MODERATE | 系统内存达到中等水平 |
| 1 | MEMORY_LEVEL_LOW | 系统内存不足,应释放不必要资源 |
| 2 | MEMORY_LEVEL_CRITICAL | 系统内存非常紧张,应尽快释放更多资源 |
示例代码:
import React, {useEffect} from 'react';
import {AppState} from 'react-native';
export default function MemoryPressureDemo() {
useEffect(() => {
const memoryLevelSubscription = AppState.addEventListener(
'memoryLevelChange',
info => {
console.log(
'[memory-demo] memoryLevelChange level=',
info.level,
'levelName=',
info.levelName,
);
// 可按需清理缓存、停止预加载、释放大对象引用等
},
);
const memoryWarningSubscription = AppState.addEventListener(
'memoryWarning',
() => {
console.log('[memory-demo] memoryWarning');
},
);
return () => {
memoryLevelSubscription.remove();
memoryWarningSubscription.remove();
};
}, []);
return null;
}
其中:
- memoryLevelChange:每次系统回调onMemoryLevel()时都会收到。
- memoryWarning:仅在MEMORY_LEVEL_CRITICAL时额外收到。
如果当前TypeScript类型尚未同步memoryLevelChange,可先通过类型断言临时监听该事件,按照内存等级做分级处理:
- MEMORY_LEVEL_MODERATE:暂停低优先级预加载、减少缓存写入。
- MEMORY_LEVEL_LOW:清理可重建缓存,例如图片内存缓存、列表数据缓存。
- MEMORY_LEVEL_CRITICAL:立即释放大对象、终止非必要任务、清理离屏资源。
2.3 注意事项
- 不建议在回调中执行耗时较长的同步操作,以免影响当前交互。
- 后台冻结状态下不会收到该回调,因此该接口不能替代后台内存回收策略。
3. 使用虚拟机后台GC优化应用内存
3.1 原理介绍
RNOH提供了enableBackgroundGC配置项。开启后,应用进入后台时,框架会向JS运行时发送后台内存压力信号,触发一次GC,以释放当前会话中已经不可达的JS对象。
该能力适合用于降低后台态的JS堆占用,减少应用在后台驻留时对系统内存的持续占用。
3.2 适配方法
在创建RN实例时开启enableBackgroundGC:
import { RNApp } from '@rnoh/react-native-openharmony';
RNApp({
rnInstanceConfig: {
name: 'app_name',
createRNPackages: getRNOHPackages,
enableBackgroundGC: true,
},
appKey: 'app_name',
})
如果应用使用了ArkTS页面路由容器,需要确保前后台生命周期已经正确接入,保证RN页面在进入后台时能够收到对应的生命周期信号。这样enableBackgroundGC才能在预期时机生效。
对于后台内存比较敏感的业务,还可以将该能力与onMemoryLevel()配合使用:
- 前台内存紧张时,优先做分级释放;
- 进入后台时,再通过后台GC进一步清理JS内存。
3.3 注意事项
- 如果业务强依赖大量内存缓存,进入后台后再次回到前台时,部分对象可能需要重新建立。
- 该能力只能回收已经不可达的对象,仍被引用的缓存不会因为GC自动释放。
4. 使用虚拟机调参JSVM优化应用内存
4.1 原理介绍
RNOH在RNInstance配置中提供了jsvmInitOptions,用于向JSVM传递启动参数。框架还内置了以下预设值:
export const JSVM_INIT_OPTIONS_PRESET = {
DEFAULT: [],
LOW_MEMORY: [
'--incremental-marking-hard-trigger=40',
'--min-semi-space-size=1',
'--max-semi-space-size=4',
],
HIGH_PERFORMANCE: [
'--incremental-marking-hard-trigger=80',
'--min-semi-space-size=16',
'--max-semi-space-size=16',
],
}
这类参数主要用于调节JSVM的GC触发时机和年轻代空间大小,从而在“更低内存占用”和“更高运行性能”之间做平衡。
- DEFAULT:默认配置,适合一般场景。
- LOW_MEMORY:更适合低内存设备或内存敏感场景。
- HIGH_PERFORMANCE:更适合交互时延敏感、对象创建较频繁的场景。
开发者使用自有V8虚拟机时,可按实际修改以下三条配置参数
- --min-semi-space-size:新生代最小大小(MB)
- --max-semi-space-size:新生代最大大小(MB)
- --incremental-marking-hard-trigger:增量标记触发阈值(%)
说明:JSVM和V8引擎默认虚拟机参数设置未做精细化调整,可通过精细化调整减小内存,推荐使用LOW_MEMORY
4.2 适配方法
如果应用使用JSVM引擎,可以在创建RN实例时传入预设参数:
import {
JSVM_INIT_OPTIONS_PRESET,
RNApp,
} from '@rnoh/react-native-openharmony';
RNApp({
rnInstanceConfig: {
name: 'app_name',
createRNPackages: getRNOHPackages,
jsvmInitOptions: JSVM_INIT_OPTIONS_PRESET.LOW_MEMORY,
},
appKey: 'app_name',
})
4.3 注意事项
- jsvmInitOptions仅在使用JSVM引擎时生效,对Hermes无效。
- 如果没有明确调优需求,建议优先使用框架预设,而不是直接传入自定义参数。
- 自定义启动参数需要与当前JSVM版本保持兼容。
5. 使用图片Resize适配优化应用内存
5.1 原理介绍
图片通常是RN页面中最容易产生较大内存占用的一类资源。当图片原始尺寸远大于组件实际显示尺寸时,即使最终只显示在较小区域,解码过程仍可能占用较多内存。
在React Native社区文档中,Image提供了resizeMethod属性,用于控制当图片尺寸与组件尺寸不一致时的处理方式:
- auto:由框架自动选择resize或scale。
- resize:在解码前对图片进行缩放,更适合“图片明显大于显示区域”的场景。
- scale:直接对图片做缩放显示,通常更快,且更适合图片与显示区域尺寸接近的场景。
RNOH 已支持resizeMethod属性透传到底层图片节点。因此,在缩略图、列表图、头像图等场景中,开发者可以优先使用resize,减少大图解码带来的内存占用。
5.2 适配方法
对于尺寸明显大于显示区域的图片,建议显式指定resizeMethod="resize":
import { Image } from 'react-native';
<Image
source={{ uri: imageUrl }}
style={{ width: 120, height: 120 }}
resizeMethod="resize"
/>
建议优先在以下场景使用resize:
- 长列表缩略图
- 宫格图片
- 头像图
- 瀑布流、小卡片图片
- 原图尺寸明显大于显示尺寸的内容图
如果图片来自服务端,建议同时配合按尺寸下发图片资源,避免下载和解码超过实际显示需要的大图。
5.3 注意事项
- resize更适合缩略图场景;对于需要高质量显示或可放大预览的图片,建议根据实际效果评估是否继续使用。
- 建议优先通过组件属性显式配置,而不是依赖默认行为。
6. 其他优化方法
在日常开发中,还可以结合以下方法进一步优化应用内存占用:
- 长列表窗口裁剪与虚拟化渲染: 在 FlatList、SectionList、VirtualizedList等长列表场景中,可通过合理配置removeClippedSubviews、initialNumToRender、windowSize、getItemLayout等属性,减少屏幕外组件的常驻数量,降低Native视图和列表项渲染带来的内存驻留。对于数据量大、Item结构复杂的场景,可进一步评估使用FlashList等更高效的列表方案。
- React组件记忆化: 对于列表Item、复杂子组件和重复渲染频繁的页面,可通过React.memo、PureComponent、useMemo、useCallback等方式减少不必要的重复渲染和临时对象创建,降低渲染链路中的额外内存开销。开发时应尽量保持Props引用稳定,避免因为匿名函数、内联对象等写法导致记忆化失效。
- 生命周期中及时释放资源: 在页面隐藏、组件销毁或实例退出时,应及时释放不再使用的资源,例如事件订阅、定时器、缓存数据、大对象引用和临时状态等。对于RNOH页面,建议开发者结合页面生命周期和组件生命周期,在资源不再需要时尽快清理,避免无效资源长期驻留内存。
- 大数据跨线程传递时使用Sendable: 在Worker或并发任务场景下,如果需要在线程间传递较大的对象数据,建议优先使用 Sendable或ISendable支持的数据结构。相较于普通对象的拷贝传递,Sendable更适合大数据共享和跨线程传输场景,有助于减少额外拷贝带来的内存开销,提升大数据并发处理场景下的资源利用效率。