Android 7.0+ 后台常驻实战:从“1像素”到前台服务的生存博弈
最近在维护一个运动健康类的项目时,后台服务被系统频繁回收的问题又一次摆在了面前。用户反馈在锁屏跑步一段时间后,应用就收不到实时配速和心率提醒了。这场景太熟悉了——后台保活,一个让无数Android开发者又爱又恨的“玄学”领域。尤其是在Android 6.0之后,Doze模式和App Standby的引入,以及各大厂商愈发激进的省电策略,让传统的保活手段纷纷失效。我们需要的不是“黑科技”,而是一套在合规框架下,理解系统规则并与之共舞的生存策略。
这篇文章,我想和你深入聊聊在Android 7.0及以上系统中,如何通过组合“1像素悬浮窗”与“前台服务”等技巧,实质性地提升应用进程的存活率。我们会绕过那些已经被系统封杀的老路,聚焦于当前依然有效且相对稳妥的思路。目标很明确:不是为了实现“永生”,而是在用户真正需要的时候(比如运动记录、即时通讯),我们的服务能“活”得更久一点,更稳定一点。
1. 理解Android的“内存杀场”:OOM_ADJ与Low Memory Killer
在动手写代码之前,我们必须先摸清游戏规则。Android系统管理进程生命周期的核心机制,绕不开两个关键概念:oom_adj(或更新系统中的oom_score_adj)和 Low Memory Killer (LMK)。
你可以把Android设备的内存想象成一个拥挤的舞台,每个应用进程都是一个演员。系统(舞台管理员)会根据演员的“重要性”和“活跃度”给他们贴上不同的标签,这个标签值就是oom_adj。数值越低,代表优先级越高,越不容易被请下台。
进程的常规优先级层次大致如下:
| 进程类型 | 描述 | oom_adj 范围 (典型值) | 被回收的可能性 |
|---|---|---|---|
| 前台进程 (Foreground) | 用户正在交互的Activity或绑定的Service。 | 0 | 极低,除非内存极度紧张。 |
| 可见进程 (Visible) | 不在前台但仍对用户可见(如弹窗后的Activity)。 | 1 | 较低。 |
| 服务进程 (Service) | 通过startService()启动的服务,如音乐播放。 |
5 | 中等,系统会根据内存压力决定。 |
| 后台进程 (Background) | 对用户不可见的Activity(已onStop)。 | 9 | 高,是LMK的主要清理目标。 |
| 空进程 (Empty) | 仅保留进程空壳,无任何活跃组件。 | 15 | 最高,最先被回收。 |
注意:
oom_adj的具体数值因Android版本和设备厂商定制而异,上表是一个通用参考。从Android 7.0开始,更常用的是oom_score_adj,其范围是-1000到1000,原理类似。
Low Memory Killer 就是这个舞台的清洁工。它预定义了多个内存阈值(/sys/module/lowmemorykiller/parameters/minfree)。当系统可用内存低于某个阈值时,LMK就会启动,从oom_adj最高的进程开始清理,直到释放足够的内存。
所以,保活的核心逻辑之一就变成了:想尽办法,让我们希望存活的进程(或服务)获得更低的oom_adj值,即更高的优先级。
一个前台服务(Foreground Service)的oom_adj值通常为2,远低于普通的后台服务。而一个拥有可见界面的Activity(即使只有1像素),其进程通常被视为“可见”或“前台”进程。这就是我们技术方案的底层逻辑出发点。
2. 前台服务:穿上“VIP”的外衣
让服务运行在前台,是提升其优先级最直接、最合规的方法。系统会将其视为一个用户知晓且正在进行的任务,从而降低被杀的几率。
2.1 基础实现与通知栏的博弈
从Android 8.0(API 26)开始,系统强制要求前台服务必须显示一个持续的通知。这对用户体验是个挑战。一个取巧的思路是:启动一个前台服务A并显示通知,随后立即启动另一个前台服务B,用B来移除A的通知。由于B也是前台服务,进程的整体优先级并未下降。
让我们看一个针对Android 7.0+的兼容性示例。我们先创建一个核心的前台守护服务:
/**
* 核心守护服务,以前台方式运行。
* 这是保活的第一道防线。
*/
public class CoreGuardService extends Service {
private static final String TAG = "CoreGuardService";
public static final int NOTIFICATION_ID = 1001;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "核心守护服务已创建");
// 适配不同API版本的前台服务启动方式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android 8.0+ 必须创建通知渠道
String channelId = createNotificationChannel();
Notification notification = new NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_stat_guard)
.setContentTitle("应用运行中")
.setContentText("正在保障核心功能持续工作")
.setPriority(NotificationCompat.PRIORITY_LOW) // 设置为低优先级,降低对用户干扰
.build();
startForeground(NOTIFICATION_ID, notification);
} else {
// Android 7.0-7.1 的旧方式
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_guard)
.setContentTitle("应用运行中")
.setPriority(NotificationCompat.PRIORITY_LOW)
.build();
startForeground(NOTIFICATION_ID, notification);
// 对于旧版本,可以尝试启动一个辅助服务来移除通知
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
startService(new Intent(this, HideNotificationService.class));
}
}
}
@TargetApi(Build.VERSION_CODES.O)
priva

239

被折叠的 条评论
为什么被折叠?



