App 需要适配和做的基本在 Android 17 内存管理将严格管控,App 要注意适配都已经聊过了,这次 Android 17 正式版发布也就说明这个能力正式落地了。
简单说,Android 17 这套和之前的不带一样,不再是针对 Dalvik/ART heap size 场景做一些大小调整,核心是在系统层给 App 进程挂 cgroup v2 内存约束。
简单说,它主要限制的是“进程总内存行为”,尤其是匿名内存 + swap,除了 Java heap,类似 native、Bitmap、WebView、GPU/图形相关缓存(大概率)等都可能受到影响。
也就是之前抖音“极客”适配 Android 5 ~ 9 等老机型技术在这种情况下大概率也没办法很好继续发挥,因为改 ART 堆结构、扩 Region Space这些操作,解决不了系统从 cgroup 层面对整个进程的外部上限。
另外,这次新增加的 Memory Limiter 是一个系统服务,用的是 Linux cgroup v2监控和限制应用进程内存,所以核心不再是 Runtime.maxMemory() 这中 Java heap 限制,实际用的是 cgroup v2 的两个内核接口:
-
memory.high:软上限,超过后内核会对该 cgroup 下的进程施加压力,尝试回收 file-backed memory,并把匿名内存换出到 swap -
memory.swap.max:swap 使用上限,防止一个进程无限把匿名页挤进 ZRAM/swap
说人话就是,
memory.high警戒线,memory.swap.max是最多能塞进去的上限,两个一起用就是防止一个 App 把真实内存和压缩内存都占满。
另外 Memory Limiter 会和 Activity Manager Service / AMS 集成,用来跟踪进程生命周期与状态变化,然后通过 Linux kernel cgroup v2 文件系统执行限制。
所以它的执行链大概是:
-
ActivityManagerService跟踪进程生命周期和状态 -
Memory Limiter 根据进程状态分配 visible / not visible / cached 对应限制,写入对应 cgroup v2 的
memory.high和memory.swap.max -
进程超过
memory.high后,内核先回收、换出、限速 -
如果继续分配匿名内存、swap 用尽,就会触发系统记录与终止
这个情况触发后,反馈在 App 侧看到的是一次没有 crash 堆栈的进程退出,而且这个场景下,最容易出现问题的就是存在内存泄漏的 App,可能出现的是内存泄漏,但是因为 Memory Limiter 被回收,以前的路径完全捕获不到问题。
当然,“天无绝人之路”,还是有一些情况可以豁免:
1、默认只监控 App 进程,Memory Limiter 默认监控 UID >= 10000 的应用进程,系统进程通常豁免,这个很好理解
2、部分进程状态是 Unrestricted 的也可以豁免,所以这个还是很“灵活”的,目前的情况主要如下表:
| Android 进程状态 | Memory Limiter 眼里的待遇 | 人话 |
|---|---|---|
| PERSISTENT / PERSISTENT_UI | Unrestricted | 系统核心常驻进程,不按普通 App 限制 |
| TOP | visible | 用户正在看、正在操作的 App |
| BOUND_TOP | visible | 被当前前台 App 绑定的重要进程 |
| IMPORTANT_FOREGROUND | visible | 虽然不是主 Activity,但和当前用户交互强相关 |
| TOP_SLEEPING | visible | 屏幕刚熄灭/锁屏,但刚才那个前台 App 还算高优先级 |
| FOREGROUND_SERVICE | not visible | 有前台服务通知,但用户不一定正在看它 |
| cached | cached | 用户已经离开,进程只是留在后台等下次打开更快 |
需要注意的是 foreground service 具体需要看真实的 process-state 映射表,现在 foreground service 不等于 visible 上了,很多后台常驻、上传、定位、音频、IM、同步类 App 过去靠 FGS 提升优先级,现在内存上限有可能会被按 not visible 处理。
说人话这几个状态可以这么理解:
-
Unrestricted:消防、电梯、安保控制室,这些是系统核心部门,不能被限制
-
Visible:正在面对面办理业务的窗口,用户正在看,正在用,优先给空间
-
Not visible:后勤办公室还在处理你的单子,比如你已经离开窗口,但文件还在上传、订单还在处理,重要但不能占空间
-
Cached:临时空位上放着你刚才用过的资料,留着是为了下次方便,不是必须留,空间紧张就先清掉
3、调试是可以通过 adb 设置 ignore 机制:
adb shell am memory-limiter ignore <uid>|none|all adb shell am memory-limiter manual <pid> <limit>|max|none adb shell am memory-limiter status
所以可以看到,这套机制主要还是针对没有“门路”的普通 App ,另外这套机制是 vendor 配置驱动,配置文件在 vendor 分区:
/vendor/etc/memory-limiter-config.xml
如果这个文件不存在、不可读或无效,Memory Limiter 会被禁用,也就是 Memory Limiter 是否启用、阈值多少都取决于设备配置。
配置文件可以定义多个 limitSet,系统会根据设备可用总内存选择最匹配的那一组,所有内存值单位是 MiB:
<MemoryLimiterConfig> <version>1</version> <configList> <limitSet> <!-- Limits for a phone with at least 14G of ram: 8G/4G/4G/4G --> <minimumRequiredMemTotal>14336</minimumRequiredMemTotal> <memVisible>8192</memVisible> <memNotVisible>4096</memNotVisible> <swapVisible>4096</swapVisible> <swapNotVisible>4096</swapNotVisible> </limitSet> </configList> </MemoryLimiterConfig>
| 字段 | 含义 |
|---|---|
| minimumRequiredMemTotal | 这个 limitSet 适用于“至少多少 MiB 可用系统内存”的设备 |
| memVisible | visible 进程的 memory.high |
| memNotVisible | not visible 进程的 memory.high |
| swapVisible | visible 进程的 memory.swap.max |
| swapNotVisible | not visible 进程的 memory.swap.max |
所以不同进程,大小配置也可能不一样,配置文件可以定义多个 limit sets,服务会根据设备可用 RAM 选择 best match。
比如 AOSP Demo 里 14 GiB RAM 的手机:
-
visible 进程内存上限示例是 8192 MiB
-
not visible 是 4096 MiB
-
visible / not visible 的 swap 上限是 4096 MiB
你可以在设备上直接看当前生效值:
adb shell am memory-limiter status
AOSP 文档给的示例输出是:
Memory limiter enabled monitoring=true ignored=none visibleMem=1948MB visibleSwap=974MB notVisibleMem=974MB notVisibleSwap=487MB started=36 watched=36 watch-failed=0 events=0 processes=36 process-hwm=36
这里 visibleMem / notVisibleMem 就是当前设备算出来同时生效的绝对上限。
另外 Android 17 还有一个信的叫 PMGD (Process Memory Guardian Daemon),它和 Memory Limiter 不是一回事 。
PMGD 更像是一个 vendor/system 进程级守护机制,可以按 /vendor/etc/pmgd/config.json 配置目标进程、memory.high 、 profile、匿名内存硬上限。
可以把 PMGD理解成厂商给某些“关键系统进程”单独装的内存保险丝,比如厂商对某个 App 的内存使用比较担心,但是又不能让他被 Memory Limiter 直接秒杀,所以让 PMGD 去盯着它,它超过某个内存线后,先给系统一点时间回收,如果回收不下来就记录日志,然后停掉。
技术上就是,PMGD 使用 inotify 监听 cgroup v2 的 memory.events,命中后检查匿名内存,必要时记录 statsd atom 并杀进程,它的配置目标可以是 system_server 这类指定进程。
所以简单来说:
-
Memory Limiter 是“按人群管理”:前台 App 一档、后台 App 一档、cached 一档
-
PMGD 是“点名管理”:我就盯 进程,发现它不正常就处理
可以粗暴点理解,PMGD 就是针对 Memory Limiter 豁免的那些进程的一个另外配置监管支持,相比 Memory Limiter 可以有一段豁免时间, 不会立刻扑街。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/ZuoYueLiang/article/details/162175868




