不知道各位有没有听过下面这种类似的段子
- 90后生病:我发烧38.5度,头晕得厉害,但是工作没做完,没做完就请假太对不起老板了,我得坚持下去
- 00后生病:劳资今天精神状态不适合上班,灵魂出窍中,请假,已读不回勿扰
而当我今天打开许久没看的Android开发者文档后,吓了一跳,谷歌又整新活儿了,Android17出了一个应用内存限制的新特性,越看越觉得Android17的手机应用里面,也出了不少"00后"
背景
总体先说下怎么回事,Android 17 引入了基于设备总RAM的应用内存限制机制。系统会"保守地设置限制",在极端内存泄漏等异常情况引发系统范围不稳定(界面卡顿、耗电过快、应用被终止)之前主动采取行动,目的是为应用和用户"打造更稳定、更确定的环境"。 个人解读:谷歌为了用户考虑,当用户使用的应用出现异常的时候,就不让用户用了,就像是app说:我有点发烧(电池过热),四肢酸痛(界面卡顿),吃坏肚子了(内存泄漏),活干不动了,系统就说:好的,你走吧,可以下班了...是这个意思吧?
影响范围
值得注意的是,这个应用内存限制的新特性是针对所有平台的,也就是不管你的应用升不升级targetSdkVersion版本,只要运行在Android17的设备上,都必须考虑这件事,但是人家也标注了,不是所有设备都会存在这个新特性

那究竟部分设备是指的哪些设备,并没有明说,不过按照以往所有新特性的规律,不排除将来对所有设备都开放的可能性
行为表现
所以对于开发者来讲,目前能做的只能是知道应用是不是因内存达到上限而被强制退出的,以及如何预防这件事情的发生,当应用突破内存限制时,系统会终止该应用进程,但是不会crash,也不会抛出OOM,仅仅只会有一个退出原因记录REASON_OTHER,然后说明中包含字符串 MemoryLimiter:AnonSwap,所以如果我们不做任何事情的话,线上如果有用户说:我的应用无故退出了,你大概率会去犟嘴:胡说,我啥日志都没看到
问题捕获
既然不会crash,那么肯定try-catch的方式也不能用,只能通过上报日志的方式知道了,前面说到了,应用被强制退出会有个原因记录,我们可以上报这个记录,这个记录是通过ApplicationExitInfo来获取的,代码如下

建议在 Application.onCreate() 或首页 Activity 中注册此检查,并上报到日志/监控系统。
使用基于触发器的生产环境分析
Android 平台提供了高级可观测性 API,支持在生产环境中基于触发器自动捕获数据:
| 触发器 | 触发时机 | 用途 |
|---|---|---|
TRIGGER_TYPE_ANOMALY |
系统检测到异常行为(如内存用量过量)时触发,在系统采取措施之前 | 捕获内存达到限制前的堆转储 |
TRIGGER_TYPE_OOM |
应用抛出 OutOfMemoryError 后,下次启动时触发 |
分析 OOM 现场 |

结合 Android Vitals 监控 LMK 事件
通过 Android Vitals(Google Play Console)跟踪低内存终止 (LMK) 事件:

问题分析
当确认应用被内存限制器终止后,需要分析具体原因。
本地分析:Android Studio Memory Profiler
Memory Profiler 可以分析:
- 内存用量的实时图表和趋势
- 分配的 Java/Kotlin 对象数量
- GC 事件发生的时间与频率
- Java 堆的快照(Heap Dump),检查大对象与泄漏
- 所有分配对象的堆栈轨迹
命令行分析:dumpsys meminfo
bash
# 查看某个进程的内存详情
adb shell dumpsys meminfo <package_name>
# 示例输出关键字段说明
# Native Heap - 原生代码分配的内存
# Dalvik Heap - Java/Kotlin 堆
# .so mmap - 共享库映射
# .jar .apk - 代码资源映射
# Graphics - 图形缓冲区
# Stack - 线程栈
# Cursor - 数据库游标
# Ashmem - 匿名共享内存
重点关注 PSS(Proportional Set Size)------按比例分摊共享库后的实际物理内存,这是与系统限制最相关的指标。
生产环境分析:ProfilingManager
生产环境中,通过 ProfilingManager 的基于触发器的分析能力:
- TRIGGER_TYPE_ANOMALY:系统检测到内存异常(达到限制前触发器)时自动转储堆
- TRIGGER_TYPE_OOM:OOM 发生后下次启动时触发
查询系统可用内存

应该如何适配
知道了问题,那么接下来就要做的是我们如何去适配这个新特性,才能让应用在内存限制内运行
建立内存基线
针对不同的设备配置(RAM 大小),建立应用的内存使用基线:
- 收集各场景峰值内存:启动、主页、列表滑动、详情页、图片浏览、后台切换
- 按 RAM 分档:4GB、6GB、8GB、12GB+ 设备分别记录
- 设置报警阈值:设定为系统限制的 80%,接近时触发告警
优化图片加载
图片是内存占用的最大来源之一:

实现 onTrimMemory 及时释放资源

使用经过优化的数据容器
避免 Java 标准库中带有自动装箱开销的容器:
| 推荐使用 | 替代 | 节省 |
|---|---|---|
SparseArray |
HashMap<Integer, Object> |
每条目减少 1-2 个对象 |
SparseBooleanArray |
HashMap<Integer, Boolean> |
同上 |
LongSparseArray |
HashMap<Long, Object> |
同上 |
| 原始数组 | ArrayList<Integer> |
无装箱开销 |
精简代码与依赖

通过启用 R8 缩减应用总大小
-
应避免的全局规则:
-dontoptimize:完全停用整个应用的优化,导致可执行文件更大、速度更慢。-dontshrink:防止移除未使用的代码和资源。-dontobfuscate:防止名称缩减,从而错失宝贵的内存节省机会(尤其是在大型应用中)。
-
避免使用软件包级通配符:
-keep class com.example.package.** { *; }等宽泛的规则会强制 R8 保留相应软件包中的每个类、字段和方法。这会完全阻止 R8 移除、优化或缩小相应软件包中的代码 -
使用默认 R8 配置文件:始终使用
proguard-android-optimize.txt -
精简 Protobuf:始终使用 protobuf-lite(精简版)而非完整版
谨慎管理 Service
启动 Service 后,系统会倾向于保持其进程存活,这将减少 LRU 缓存中的可用进程数,影响应用切换效率:

避免内存抖动
内存抖动指短时间内大量分配临时对象,导致频繁 GC,消耗电量和增加帧绘制时间:

测试与调试工具
adb memory-limiter 命令
Android 17 提供了一组新的 adb 调试工具,用于模拟和调试内存限制:
bash
# 查看当前内存限制状态
adb shell am memory-limiter status
# 对指定 UID 忽略限制(用于白名单调试)
adb shell am memory-limiter ignore <uid>
# 忽略所有进程的限制
adb shell am memory-limiter ignore all
# 取消所有忽略
adb shell am memory-limiter ignore none
# 对指定 PID 设置手动限制(如 30MB)
adb shell am memory-limiter manual <pid> 30
# 移除手动限制
adb shell am memory-limiter manual <pid> max
# 恢复系统默认限制
adb shell am memory-limiter manual <pid> none
模拟低内存场景
bash
# 模拟低内存触发 onTrimMemory
adb shell am send-trim-memory <package> <level>
# level: MODERATE, BACKGROUND, COMPLETE, UI_HIDDEN
# 压力测试 - 持续分配内存直到被限制
# 建议结合 instrumentation 测试使用
持续监控脚本
bash
#!/bin/bash
# monitor_memory.sh - 监控应用内存使用变化
PACKAGE="com.example.app"
while true; do
TIMESTAMP=$(date +"%T")
PSS=$(adb shell dumpsys meminfo $PACKAGE | grep "TOTAL PSS" | awk '{print $3}')
echo "[$TIMESTAMP] PSS: ${PSS}kB"
sleep 5
done
总结
其实说白了也没啥大不了的,做的还是平时我们做的一些性能优化的事情,对于大部分Android老法师来讲,优化个内存每个人都有自己的手段,就算谷歌不弄出这个新特性,谁家kpi里面没几个性能优化的指标呢?所以,比起前面几个版本的某些特性,这个咱还真不用放心上