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 之间,具体取决于是否可见、是否被绑定等。

关键结论: 前台服务能直接将进程的 oom_adj 拉低到“几乎不会被杀且能抢到较多CPU时间片”的区间。

4.2 oom_adj值到底是谁在管?

嗯,这事其实由 ActivityManagerService(AMS)和 Linux kernel 的 lowmemorykiller / lmkd 协同完成。AMS 根据进程当前状态动态调整 oom_adj,而 lmkd 在内存紧张时按 oom_adj 从大到小依次杀进程。

我曾经排查过一个线上问题:某后台服务启动后频繁被 kill,log 里看到 oom_adj 一直在 +200 以上。后来发现是因为没有正确绑定前台服务通知,导致系统认为它只是一个“后台服务”甚至“空进程”…… 那自然优先被杀。

避坑提示: 别以为调了 startForeground() 就万事大吉。必须确保通知渠道有效、通知不被用户手动关闭,否则系统可能把你的 oom_adj 降级。
进程类型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 重组动画不卡顿。

但是! 并不是说前台服务就能无限吃 CPU。如果前台线程里做了大量阻塞操作(比如极端复杂的布局计算),即使优先级再高,帧率也会掉。我见过不少项目,以为加个前台服务就能无视性能优化……结果还是卡。

4.4 梳理一张图:从优先级到时间片

为了方便你理解整个链路,我画了一张图。它把进程状态、oom_adj 变化、cgroup 移入、CPU 时间片分配串在一起。

前台服务CPU时间片分配链路 启动前台服务 startForeground() AMS 调整 oom_adj 变为 -1 ~ +200 cgroup top-app组 CFS 调度器 cpu.shares = 1024 (对比后台组 128) CPU时间片分配 前台线程可获 5~8 倍时间片 注意 如果通知失效 oom_adj 会被降级

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 导致调度器让主线程等待,帧率还是会受影响。这时候你可能需要把计算任务放到另一个进程的服务里去——但那又是另一节课的内容了。

一句话总结: 前台服务通过降低 oom_adj 和提升 cgroup 权重,让你的进程在 CPU 争抢中获得更多时间片,但它不能解决代码本身的性能问题。优化好你的重组逻辑,前台服务才能发挥最大作用。