4、前台服务的CPU影响原理:Android进程优先级、oom_adj值与CPU时间片分配
聊到前台服务对CPU的影响,很多人第一反应就是“优先级高”。但优先级到底是怎么工作的?为什么前台服务能抢到更多CPU时间?
我个人习惯把这件事拆成三层来看:进程优先级 → oom_adj值 → CPU时间片分配。这三层环环相扣,理解了它们,你也就明白了为什么前台服务能稳住帧率。
4.1 Android进程优先级——谁在调度器那儿更有“面子”
Android里没有一个全局的“进程优先级”数字,但它通过 oom_adj 间接决定了进程在内存和CPU两个维度的待遇。你想想看,系统在杀进程时要选“最不重要的”,在分CPU时间片时也要给“最重要的”多分一点——这两件事用的其实是同一套等级。
oom_adj 的范围是 -1000 到 +1000,值越小越重要。前台进程的 oom_adj 通常是 0(ADJ_FOREGROUND),而空进程可能高达 1000。那前台服务呢?它让进程的 oom_adj 变成 -1 到 +200 之间,具体取决于是否可见、是否被绑定等。
4.2 oom_adj值到底是谁在管?
嗯,这事其实由 ActivityManagerService(AMS)和 Linux kernel 的 lowmemorykiller / lmkd 协同完成。AMS 根据进程当前状态动态调整 oom_adj,而 lmkd 在内存紧张时按 oom_adj 从大到小依次杀进程。
我曾经排查过一个线上问题:某后台服务启动后频繁被 kill,log 里看到 oom_adj 一直在 +200 以上。后来发现是因为没有正确绑定前台服务通知,导致系统认为它只是一个“后台服务”甚至“空进程”…… 那自然优先被杀。
| 进程类型 | oom_adj 典型值 | CPU时间片分配 |
|---|---|---|
| 前台进程(当前 Activity) | 0 | 高 |
| 可见进程(无 Activity 但有可见服务) | 100 | 中 |
| 前台服务进程 | -1 ~ +200 | 中~高 |
| 后台服务进程 | +200 ~ +300 | 低 |
| 已缓存/空进程 | +900 ~ +1000 | 极低 |
4.3 CPU时间片分配——kernel 的 CFS 调度器怎么“看人下菜碟”
Linux 内核的完全公平调度器(CFS)给每个进程分配的 CPU 时间片比例,直接和进程的 nice 值以及 cgroup 权重挂钩。Android 在 cgroup 中给前台进程组分配了更高的 cpu.shares,比如 cpu.shares = 1024,而后台进程组可能只有 128。
前台服务启动后,AMS 会把当前进程移入“前台进程组”(top-app 或 foreground),于是该进程的 cgroup 权重就大了。你想想看,当系统发生 CPU 争抢时,前台进程能拿到的 CPU 时间片比例可能是后台进程的 8 倍。这就是为什么前台服务能保证你的 Compose 重组动画不卡顿。
4.4 梳理一张图:从优先级到时间片
为了方便你理解整个链路,我画了一张图。它把进程状态、oom_adj 变化、cgroup 移入、CPU 时间片分配串在一起。
4.5 避坑指南——我曾经踩过的坑
- 误区:前台服务 = 不卡顿。我曾经接手过一个音视频项目,开发者在 Server 的 onCreate 里直接做了大量 bit 运算,结果前台服务主线程阻塞,UI 线程虽然优先级高但等不到锁,帧率掉到 15fps。记住:优先级高不等于不卡,它只是让你在资源争抢中有优势。
- 误区:通知不重要。 有一次我测试发现前台服务的 oom_adj 莫名其妙变成 +300,排查半天发现是通知渠道被系统判定为“低优先级”,AMS 直接把进程归入后台。嗯,这里要注意:通知的
setPriority要设成IMPORTANCE_HIGH或至少IMPORTANCE_DEFAULT。 - 误区:前台服务可以无限后台运行。 Android 12 之后,系统对前台服务的限制更严格了。即便你正确调用了 startForeground,如果服务运行时间过长且没有与用户明显交互,系统也可能把 oom_adj 慢慢调高。我建议定时做
startForeground的重新绑定或者适时降级为后台服务。
4.6 拓展思考:Compose 重组的帧率到底和这个有什么关系?
简单说,Compose 的重组发生在主线程(或指定线程),而主线程的 CPU 时间片分配就受上述 cgroup 权重影响。如果你的 App 是纯前台 Activity,它就是“前台进程”,oom_adj = 0,cpu.shares=1024,很稳。
但如果你这个 Activity 里启动了前台服务来做后台计算(比如蓝牙扫描),而前台服务的进程和 Activity 是同一个进程吗?通常是的。所以只要进程本身是前台进程,后台计算线程也不会拖垮主线程?不对,如果后台线程大量占用 CPU 导致调度器让主线程等待,帧率还是会受影响。这时候你可能需要把计算任务放到另一个进程的服务里去——但那又是另一节课的内容了。