在Android系统中,每一个运行中的进程都会被内核与框架赋予一个称作adj(adjustment)的优先级数值,它直接决定了系统在内存紧张时是否会优先回收该进程。理解adj的产生逻辑并掌握调整手段,是做后台保活、推送长连接以及音视频播放类应用的基础能力。很多崩溃与后台断线问题,追到底都是adj被系统算高之后触发lowmemorykiller清理。

Adj优先级背后的内核与框架原理
Android的进程回收并不是由Dalvik或ART虚拟机独自决定的,而是依托Linux内核中的lowmemorykiller驱动配合上层ActivityManagerService共同完成。内核维护了一组内存阈值,当可用内存低于某个级别时,就会遍历所有进程,选择oom_score_adj数值最大的进程杀掉以释放内存。这里的oom_score_adj就是我们在框架层常说的adj,取值范围一般从-1000到1000,其中负值代表系统进程或极其重要进程,正值越大越容易被回收。
在框架侧,ActivityManagerService会根据应用所处的前后台状态、是否含有前台服务、绑定关系以及最近使用时间,调用ProcessList中的计算方法更新每个进程的curAdj。例如一个正在和用户交互的Activity所在进程会被赋予FOREGROUND_APP_ADJ(-1000附近),而完全不可见的空进程可能落到900甚至1000。这个计算过程还会受到manifest中声明的重要性、bindService时的flag以及系统厂商定制策略影响,因此同样一段代码在不同 ROM 上表现出来的adj可能完全不同。
除了框架计算,开发者还可以通过写入/proc/<pid>/oom_score_adj节点来强制修改内核视角下的优先级。不过需要注意,系统每隔一段时间或状态切换时就会重新设置该值,单次写入若没有持续保活机制很容易被覆盖。理解这套双层控制逻辑,才能明白为什么单纯在Java层调用API有时不生效,必须配合native写入或提升组件层级。
常用的Adj调整手段与代码实现
最直观也最合规的方式是使用前台服务(foregroundService)。当调用Service的startForeground并传入持续展示的通知后,系统会将该进程标记为带有前台服务状态,adj通常会降到较低区间(例如0或更小),从而大幅降低被回收概率。这种方式适合导航、音乐播放、长连接保活等用户可感知场景,但滥用会导致通知栏污染而被商店拒审。
如果需要在native层做更精细控制,可以通过JNI获取当前pid并直接写oom_score_adj文件。下面示例展示了在C++中把自身进程adj设为-100(较低优先级)的实现,注意写文件需要root或system权限,在普通用户应用中往往只能写到正值区间:
#include <stdio.h>
#include <unistd.h>
void set_oom_score_adj(int adj) {
char path[64];
pid_t pid = getpid();
sprintf(path, "/proc/%d/oom_score_adj", pid);
FILE* f = fopen(path, "w");
if (f) {
fprintf(f, "%d", adj);
fclose(f);
}
}
// 调用示例:将adj设为-100,降低被杀概率
// set_oom_score_adj(-100);
在Java层也可以借助ActivityManager获取进程记录并辅助分析,但公开API并没有直接设置adj的接口。常见的做法是利用反射调用ProcessList相关方法,或者借助系统签名权限。以下代码演示如何读取当前进程的adj用于调试,帮助确认调整是否生效:
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> list = am.getRunningAppProcessInfo();
int pid = android.os.Process.myPid();
for (ActivityManager.RunningAppProcessInfo info : list) {
if (info.pid == pid) {
// info.adj 在部分系统可读,低版本可能为0
Log.d("AdjCheck", "current adj=" + info.importance);
}
}
此外,通过绑定系统高优先级服务、使用AccountSync等机制,也能间影响adj计算。但这类方式依赖系统版本,且过度使用会造成系统资源倾斜,在定制ROM上经常失效,因此生产环境应以foregroundService为主,native写入为辅。
调整时的避坑点与不同场景策略
不少团队在调整adj时容易陷入两个误区:一是认为把adj调到最低就万事大吉,结果后台耗电剧增,被系统 heuristics 判定为异常应用甚至加入黑名单;二是只在启动时写一次文件,后续状态切换后被系统重置却未感知。正确的做法是根据业务场景动态升降级,比如音视频播放时提权,暂停后主动释放前台服务降权。
对于IM长连接场景,可以结合foregroundService与心跳机制,在用户锁屏后维持较低adj保活,但同时监听屏幕点亮与退到前台事件,及时调用stopForeground让出资源。下表列出常见场景与推荐adj区间:
| 场景 | 推荐adj区间 | 实现方式 |
|---|---|---|
| 前台交互Activity | -1000至0 | 系统自动分配 |
| 音乐播放 | 0至-100 | 前台服务 |
| 后台定位 | 100至200 | 前台服务+定位类型通知 |
| 缓存空进程 | 900至1000 | 不干预,随系统回收 |
最后需要强调,Android从Q版本开始对后台启动服务和adj操纵限制越发严格,普通应用无法通过非系统签名手段长期霸占低adj。因此架构上应将关键任务尽量托管给WorkManager、FCM等系统调度通道,把adj调整作为体验优化补充而非保活唯一依赖。只有理清系统回收模型并顺应权限演进,才能既保住核心功能又不被系统惩罚。