内存预算与 CPU 占用控制
ARCore 应用的内存消耗结构
| 组件 | 典型内存占用 | 说明 |
|---|---|---|
| Google Play Services for AR | ~50~100 MB | 系统进程,无法控制 |
| ARCore Session | ~30~50 MB | SLAM 地图、Frame 缓冲、Texture 等 |
| 摄像头缓冲 | ~20~40 MB | RGB + 深度图(如果启用 Depth) |
| 3D 渲染资源 | 因内容而异 | 纹理、Mesh、Shader |
| Unity/Sceneform 运行时 | ~100~300 MB | 如果用了游戏引擎 |
| Cloud Anchors 缓冲 | ~10~30 MB | 托管/解析时临时加载特征点地图 |
一个典型的 Unity AR 应用,启动后总内存 400~600 MB 是正常的。如果超过 800 MB,低端 Android 设备很容易被系统杀掉。
内存泄漏的常见来源
泄漏一:Anchor 不释放
// 每次 createAnchor 都增加一个 Anchor
// 如果用户每次点击屏幕都创建新 Anchor 而不清理旧的
// 几百次点击后就有几百个 Anchor,每个都持续占用内存
private val anchors = mutableListOf<Anchor>()
fun placeObject(pose: Pose) {
// 正确:清理旧 Anchor
anchors.forEach { it.detach() }
anchors.clear()
val newAnchor = session.createAnchor(pose)
anchors.add(newAnchor)
}
泄漏二:Bitmap / 纹理资源不释放
// Augmented Images 动态加载的 Bitmap
val bitmap = BitmapFactory.decodeFile("target_image.png")
database.addImage("name", bitmap)
// bitmap 用完后不能自动释放,需要手动 recycle
bitmap.recycle()
泄漏三:Frame / Image 对象不关闭
// Depth API 的 Image 必须显式关闭
val depthImage = frame.acquireDepthImage()
// 使用 depthImage...
depthImage.close() // 忘记这行 = 内存泄漏
// 还有 CPU Image(Unity)
cpuImage.Dispose()
泄漏四:Session 重复创建
// 错误:每次进入 AR 页面都 new Session()
fun enterAR() {
session = Session(context) // 旧的 Session 没被 destroy!
}
// 正确:复用同一个 Session
fun enterAR() {
if (session == null) {
session = Session(context)
}
session.resume()
}
// 退出时
fun exitAR() {
session?.pause()
// session?.destroy() 某些版本需要 destroy
}
泄漏五:Unity 的 Object.Destroy 时机
// Unity 中手动 Instantiate 的物体需要手动 Destroy
void OnDestroy()
{
foreach (var anchor in anchors)
{
if (anchor != null)
Destroy(anchor.gameObject);
}
anchors.Clear();
}
CPU 峰值控制
ARCore 应用中哪些操作会突然吃掉所有 CPU 核心?
| 操作 | 影响 | 控制手段 |
|---|---|---|
| Session 创建 | 首次创建 Session 会做大量初始化 | 提前在后台线程预创建 |
| Cloud Anchors 托管 | 特征点提取和压缩消耗大量 CPU | 在后台线程做,完成前给 Loading |
| Session.update() | 每一帧都在做 SLAM、平面检测、光照估算 | 不要在 update 之后做额外重计算 |
| Augmented Image 数据库编译 | addImage() 会同步做特征点提取 | 大量图片预先离线编译(arcoreimg 工具) |
| Depth API 深度图生成 | 被动深度模式下 CPU 计算量很大 | 低端设备自动关闭或只用硬件深度 |
| Unity 的 LateUpdate | 把重计算放在 LateUpdate 里会抢时间片 | 用协程或 Job System 做异步处理 |
Unity Job System 优化
把耗时计算移到后台线程:
// 用 C# Job System 做深度图处理
[BurstCompile]
public struct DepthProcessingJob : IJob
{
public NativeArray<float> depthData;
public NativeArray<float> result;
public void Execute()
{
// 在后台线程处理深度图
// 不阻塞主线程的帧渲染
}
}
void Update()
{
var job = new DepthProcessingJob { ... };
job.Schedule().Complete(); // 或用 IJobHandle 异步等待
}
后台处理:Activity 暂停后的清理
当用户切换到其他应用时(Activity.onPause),必须做以下清理:
override fun onPause() {
super.onPause()
// 1. 暂停 Session(停止 SLAM 计算)
arSession?.pause()
// 2. 停止 GL 渲染(释放 GPU 资源)
glSurfaceView?.onPause()
// 3. 移除所有运行时任务
mainHandler.removeCallbacksAndMessages(null)
// 4. 释放可释放的资源(Bitmap、非持久化 Anchor 等)
tempAnchors.forEach { it.detach() }
tempBitmaps.forEach { it.recycle() }
// 5. 保留持久性资源(Session 本身不需要 destroy,pause 足够)
}
override fun onResume() {
super.onResume()
// 恢复 Session 和渲染
glSurfaceView?.onResume()
arSession?.resume()
}
OOM(内存溢出)崩溃预防
| 预防措施 | 说明 |
|---|---|
| 监测内存使用 | 定期检查 Debug.getMemoryInfo(),超过阈值时主动降级 |
| 纹理压缩 | 用 ASTC 格式(Android 通用压缩格式),比 PNG/JPG 省 50~70% |
| 按需加载 | 不要启动时加载所有 3D 模型和纹理,只加载当前场景需要的 |
| 控制纹理尺寸 | UI 纹理 ≤ 1024×1024,3D 纹理 ≤ 2048×2048 |
| Unity Build Settings | 关闭 "Development Build"(Debug Build 会占很多额外内存) |
| Texture Streaming | Unity 的 Texture Streaming 功能根据相机距离动态加载/卸载纹理 |
iOS 上的内存管理
iOS 对后台运行的应用更严格:
- Session 暂停后 iOS 会自动释放 GPU 资源
- 收到内存警告时(
didReceiveMemoryWarning)需要清理缓存 - Cloud Anchors 的
NSURLSession会被 iOS 管理,不需要手动管理连接
监测工具
| 工具 | 平台 | 能力 |
|---|---|---|
| Android Studio Memory Profiler | Android | 实时内存分配、泄漏检测、Heap Dump 分析 |
| Unity Memory Profiler | Unity | 更详细的内存快照比较 |
| adb shell dumpsys meminfo | Android | 命令行查看进程内存 |
| Xcode Memory Graph | iOS | 对象引用图、泄漏检测 |
| LeakCanary | Android | 自动检测 Activity/Fragment 泄漏 |