1. ADJ算法在Android资源调度中的核心地位
当你打开手机上的音乐App听歌时,有没有想过为什么切换到微信视频通话后,音乐会自动暂停?这背后其实是Android的ADJ(Adjustment)算法在默默协调系统资源。就像交通指挥中心会根据车流量动态调整红绿灯时长一样,ADJ算法就是Android系统的"资源调度指挥官"。
ADJ值本质上是一个动态优先级评分机制,范围从-1000到1001。这个数字越小,表示进程越重要。举个例子:
- 你正在使用的微信视频通话进程ADJ=0(最高优先级)
- 后台播放的音乐App进程ADJ=100
- 三天没打开的购物App进程ADJ=900
ADJ与系统模块的联动关系就像多米诺骨牌:
- 当你在抖音滑动视频时,ActivityManagerService会检测到前台变化
- 触发updateOomAdjLocked()重新计算ADJ值
- 新的ADJ值会同时影响:
- LMKD(Low Memory Killer Daemon)的内存回收策略
- CFS调度器的CPU时间片分配
- 内核OOM Killer的进程终止顺序
我曾在调试一个视频会议应用时发现,当ADJ值计算异常时,即使在前台也会被系统误杀。通过adb命令cat /proc/<pid>/oom_score_adj可以实时观察进程ADJ值变化,这是定位优先级问题的利器。
2. ADJ与进程调度组(SchedGroup)的联动机制
Android的CPU调度不是"大锅饭"平均分配,而是通过SchedGroup实现分级调度。这就好比机场的VIP通道和普通通道——不同优先级的进程会进入不同的调度通道。
SchedGroup的四种类型:
| 调度组 | 对应线程组 | 典型场景 | CPU时间占比 |
|---|---|---|---|
| TOP_APP | THREAD_GROUP_TOP_APP | 前台应用 | 最高 |
| DEFAULT | THREAD_GROUP_DEFAULT | 可见应用 | 中等 |
| BACKGROUND | THREAD_GROUP_BG_NONINTERACTIVE | 后台服务 | 较低 |
| RESTRICTED | THREAD_GROUP_RESTRICTED | 严控后台 | 最低 |
当ADJ值变化时,applyOomAdjLocked()会同步调整SchedGroup。例如:
// 代码段来自ProcessList.java switch (adj) { case FOREGROUND_APP_ADJ: setProcessGroup(pid, SCHED_GROUP_TOP_APP); break; case VISIBLE_APP_ADJ: setProcessGroup(pid, SCHED_GROUP_DEFAULT); break; case SERVICE_ADJ: setProcessGroup(pid, SCHED_GROUP_BACKGROUND); }实测发现,从BACKGROUND组提升到TOP_APP组后,应用的帧率稳定性能提升40%以上。但要注意过度使用TOP_APP会导致系统整体响应延迟增加,就像高速公路全是应急车道反而会降低通行效率。
3. ADJ如何影响内存回收策略
内存紧张时,系统就像面临沉船危机的船长,必须决定哪些"乘客"(进程)要被请下船。ADJ值就是这里的逃生优先级标签,而Linux内核的OOM Killer就是执行者。
ADJ与内存回收的联动流程:
- LMKD监控内存水位线(如critical是100MB)
- 触发AMS的updateOomAdjLocked()重新计算所有进程ADJ
- 通过socket将ADJ值写入
/proc/<pid>/oom_score_adj - 内核根据oom_score选择ADJ值高的进程优先回收
关键内存阈值:
// 来自ProcessList.java static final long EMPTY_APP_MEM = 1024; // 1GB空闲时保留空进程 static final long BACKGROUND_APP_MEM = 512; // 低于512MB开始杀后台 static final long SERVICE_MEM = 256; // 低于256MB连服务进程也不保一个常见的误区是认为ADJ只影响内存回收。实际上在Android 10之后,ADJ≤200的进程会启用内存压缩(AppCompactor),而ADJ≥900的进程会被完全冻结(Cached状态)。这就像对进程实行"节能减排"分级管理。
4. ADJ与cgroups的深度配合
cgroups(控制组)是Linux内核的资源隔离机制,Android通过它实现"资源集装箱化"管理。ADJ值就像集装箱的VIP等级标签,决定了资源分配权重。
ADJ与cgroups的映射关系:
- ADJ≤0的进程进入
top-appcgroup - ADJ≤200的进程进入
foregroundcgroup - 其他进程根据类型分配到
background或system-background
通过cat /dev/cpuset/<group>/tasks可以查看各组包含的进程。在优化应用启动速度时,我们会主动将启动阶段进程绑定到top-app组:
echo <pid> > /dev/cpuset/top-app/tasks但要注意滥用cgroups会导致系统调度开销增大。曾经有个游戏应用错误地将所有线程都绑定到TOP_APP组,结果导致手机发热严重。正确的做法是只提升渲染线程的优先级。
5. 实战:优化应用保活能力的ADJ策略
要让应用在后台存活更久,关键是理解ADJ提升的触发条件。以下是几个有效策略:
服务保活技巧:
- 使用
startForeground()并显示通知(ADJ≤200) - 绑定到前台Activity(ADJ≤100)
- 实现
onTrimMemory()回调及时释放资源 - 避免在
onDestroy()中执行耗时操作
通过dumpsys activity processes命令可以查看详细ADJ信息:
Proc #12: adj=svc /B Svc com.example/.MyService oom: max=800 curRaw=800 setRaw=800 cur=800 set=800 curSched:1 setSched:1需要特别注意,从Android 8开始后台服务限制越来越严格。更好的做法是改用WorkManager安排后台任务,让系统在合适时机自动调度。
6. 调试ADJ问题的工具链
当出现优先级异常时,这套工具组合能帮你快速定位问题:
实时监控:
adb shell watch -n 1 "cat /proc/*/oom_score_adj | grep -v 0"历史记录:
adb shell dumpsys activity lru > lru.txt内存事件追踪:
adb shell am trace-ipc --start --dump调度策略检查:
adb shell ps -eo pid,cls,name | grep <package>
最近处理过一个案例:某金融应用在锁屏后ADJ异常升高。通过dumpsys activity processes发现是因为没有正确处理FLAG_KEEP_SCREEN_ON标志,导致系统误判为可回收进程。
7. ADJ算法的演进与未来趋势
从Android 7.0到13,ADJ机制有几个重要变化:
- 分层细化:VISIBLE_APP_ADJ从固定值变为100-199范围
- 绑定关系强化:Service与客户端的ADJ关联更紧密
- 功耗优先:新增RESTRICTED调度组限制后台活动
在Android 14的预览版中,我们发现ADJ与新的ProcessState枚举结合更紧密:
// Android 14新增状态 PROCESS_STATE_TOP_SLEEPING // 息屏显示场景 PROCESS_STATE_BOUND_FOREGROUND_SERVICE // 绑定到前台的服务这些变化反映出Android正在向"场景化调度"发展。就像智能交通系统能根据实时路况动态调整信号灯,未来的ADJ算法可能会结合AI预测来预调整进程优先级。