
做Android开发这几年“保活”两个字几乎伴随了我每一个和后台能力沾边的项目。IM收不到离线消息、智能硬件App连不上设备、考勤打卡被系统掐断最后产品经理和测试同事都会把目光聚到一个问题上为什么App一锁屏就被杀这个问题的技术收敛点就是今天要聊的Android原生保活。先说清楚我不打算教你怎么做到“进程永不被杀”。在现在的系统机制和厂商定制ROM面前任何宣称“永不杀”的方案都是在挑战整个操作系统的资源管理底线。这篇文章要做的是把Android系统到底怎么决定进程生死这件事讲透再把几种常见保活方案按适用场景拆开点评给出一套能直接落地的“前台服务自动重启”实现最后整理我在国内主流ROM上适配时踩过的坑和排查命令。无论你是刚接触后台任务的初级开发者还是正在为保活效果焦头烂额的老手这篇都能给你一些可以直接抄走的经验。1. Android到底在杀什么进程回收机制与厂商定制1.1 从OOM_ADJ到LMKD系统怎么判定谁该活很多开发者一上来就搜“Android 保活方案”然后被各种黑科技名词绕晕。我建议你先放下方案回到最底层Android系统里进程不是被“应用自己主动退出”的而是由系统服务和Linux内核根据一套优先级机制动刀。这套机制的核心指标叫OOM_ADJ全称是Out Of Memory Adjustment取值大概在-17到15之间。数值越小代表进程越重要越不该被杀。前台Activity对应的进程一般是0用户正在交互可见但不在前台的进程是1有前台服务在跑的是2后台Activity通常是5到9空进程则是10以上。系统在内存吃紧时会从adj值最大的进程开始杀这就是Low Memory Killer的早期逻辑。从Android 10开始内核里的lowmemorykiller驱动被用户态守护进程lmkd替代。lmkd不再只盯着空闲内存还会结合PSI内存压力指标做更精确的判断但本质上仍然按进程的优先级排序内存压力越高、被杀阈值越紧后台进程越容易遭殃。你看到的现象是“App刚切走就被杀了”本质就是你的进程adj值被调得很高或者系统已经进入低内存状态lmkd按策略开始清理。明白了这一点你就知道所谓“保活”的第一步不是研究花活而是尽量让进程的adj值保持在低位。前台服务之所以是正统解法就是因为它能把进程拖到adj≤2的区间系统轻易不会动手。1.2 厂商ROM的“二次加工”AOSP和国产ROM完全是两个世界纯粹的原生AOSP系统保活其实没那么难。因为Google的后台管理策略相对宽松前台服务加合理的自启动逻辑能让App稳定在后台跑很久。但国内安卓生态最大的变数是每家厂商都对ActivityManager的重排逻辑做了深度定制。华为EMUI的“应用启动管理”、小米MIUI的“自启动和后台管理”、OPPO ColorOS的“纯净后台”、vivo OriginOS的“后台高耗电管理”、三星One UI的“深度休眠”——名字五花八门本质就一件事ROM在系统层维护了一张“白名单”不在白名单里的应用即使你用了前台服务也会被判定为不活跃应用轻则冻结重则杀掉。我实测过很多次一个正常实现了前台服务的App在AOSP环境或者海外版GMS设备上能老老实实挂一晚上但同一套代码装到国内MIUI上锁屏五分钟后再解屏通知栏的服务通知还在实际上进程早就被冻结了。这是因为厂商把进程的adj值强制改高或者直接对应用进程做freeze操作技术上的保活手段在下层就被拦截了。所以做保活方案之前先问自己三件事目标设备是纯AOSP还是国产ROMApp是否有可能进入厂商白名单用户是否愿意配合做设置引导这三个问题决定了方案的复杂度也决定了你后面要写多少页适配代码。2. 保活方案的选型逻辑正攻、偏师与高风险手段2.1 前台服务为什么是正统方案前台服务是目前唯一被Android官方承认的“长时后台执行”手段。系统为了保证服务不被轻易杀死会在通知栏展示一条常驻通知同时把进程adj拉低。Android 14之后官方对前台服务做了更细的类型管理要求应用必须声明foregroundServiceType这是后话。前台服务的本质是“牺牲一个永久通知换一个相对稳定的后台运行环境”。它的价值不是“杀不死”而是让系统在内存压力不高的情况下没有理由杀你。在实际项目中几乎所有需要长连接、数据同步、设备交互的App最终都会落到前台服务上。很多开发者把前台服务做成了“空壳”——通知文案乱写服务里什么正事都不干还常年挂在后台。这种做法在应用商店审核和用户感知上都很难看而且部分ROM会检测到这种“恶意后台”行为并强制清理。正确姿势是把保活服务和真实业务绑定比如确确实实在做消息心跳、数据同步、文件下载让服务的存在有说服力。2.2 JobScheduler和WorkManager延迟任务里的最佳队友有些场景不一定需要常驻进程只需要在满足条件时执行一次性任务比如用户充电时上传日志、连接WiFi时同步相册。这种需求就应该用JobScheduler或者它的AndroidX封装版WorkManager而不是强行保活。WorkManager的优势是系统会自行聚合任务、选择最佳执行时机并且能跨过Doze模式和App Standby的限制。它比AlarmManager那些定时器要温和得多不会因为频繁弹起而触发厂商的“频繁后台运行”检测。如果产品经理跟你说“我们每天凌晨两点同步一次数据就行”你就放心用WorkManager别去碰那些花里胡哨的保活方案。但WorkManager的缺陷也很明显它只保证“在某个合适的时间执行”不保证“精确到秒”。你要的是锁屏后仍然持续接收实时推送那它就是不合格的还是得回到前台服务的思路。2.3 双进程守护与Native层“黑科技”的真相早期Android保活圈流行过一种方案叫双进程守护。原理是App拆成两个进程互相监听A进程被杀了B进程通过onServiceDisconnected感知到立刻拉起AB被杀了A同理再拉起B。Android 4.4之前这个方案确实有效因为那时候系统不会同时批量处理两个进程。但现在的系统早就看穿了这个把戏。厂商可以在一次清理里同时干掉两个进程或者干脆只保留一个前台进程、把另一个空进程直接冻结。到了Android 8.0之后后台执行限制越来越严startService在后台直接被禁双进程守护基本只剩个理论意义。我的建议是别在双进程上浪费时间收益低、代码复杂、还容易触发系统的异常行为检测。还有一类黑科技是在Native层fork一个子进程让这个进程脱离Android应用框架自己维护一个循环主进程被杀了子进程来拉活。这个方案在部分低版本系统上有效果但到了Android 10以上的lmkd面前子进程的adj也会被标记而且会带来功耗问题、兼容性问题绑架起来极其痛苦。我见过一些做外挂、抢红包类的App用这种方案最后都是被系统按“恶意行为”处理的下场。正经产品我不建议碰。2.4 广播唤醒与AlarmManager先搞懂Doze的脾气AlarmManager的setRepeating和setExact是很多老项目的保活依赖。但Android 6.0引入Doze模式之后设备在长时间静止且未充电时会进入休眠状态系统会延迟甚至合并闹钟。setExactAndAllowWhileIdle虽然能穿越Doze但Google对每个应用的使用次数做了限制每9分钟最多触发一次而且App Standby状态下会更苛刻。厂商ROM还会在此基础上做更激进的闹钟管理。小米的“神隐模式”、华为的“纯净后台”会把没有被加白名单的应用的AlarmManager事件整体延后到用户点亮屏幕之后。这就导致你用AlarmManager做心跳检查锁屏状态下经常两三个小时不来一次自以为保活逻辑在跑其实早就停了。正确做法是把AlarmManager当成“兜底自检工具”而不是主要保活手段。即使系统延迟只要进程还在就不影响业务进程真被杀掉了延迟的闹钟总会在某个时刻把进程拉起这就达到了“自愈”的目的。2.5 保活方案矩阵先想清楚你要的是哪种“活”方案实现成本原生AOSP效果国产ROM效果风险与限制前台服务 前台类型低极好中等需引导用户加白必须常驻通知Android 14类型限制WorkManager延迟任务低好好不保证实时只适合任务型需求AlarmManager定时唤醒低中等差会被合并延迟Doze限制厂商限制频繁唤醒双进程守护高低版本有效极差系统批量清理容易异常Native fork子进程极高中等差功耗高兼容性差合规风险系统预装/厂商白名单取决于合作极好极好门槛高普通App无法使用选型时最怕的是“什么都想要”。我见过不少项目既想做功耗优化又想消息实时到达最后方案越做越重真机上一跑不是发热就是被系统标记。与其这样不如一开始就明确这个后台能力到底是不是用户高频需要的如果是就大胆上前台服务并做好用户引导如果不是就老老实实交给WorkManager。3. 实操一个能上线的原生前台服务保活实现3.1 权限声明与targetSdk 34的前台服务类型从Android 14开始Google强制要求应用在Manifest中声明前台服务类型并且在运行时申请对应的权限。保活场景最常用的类型是dataSync适合数据同步、上传下载类任务还有specialUse这种兜底类型但specialUse在应用商店审核时会被特别关注需要说明用途。在AndroidManifest.xml里需要这样写uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE_DATA_SYNC / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / service android:name.KeepAliveService android:enabledtrue android:exportedfalse android:foregroundServiceTypedataSync /注意POST_NOTIFICATIONS是Android 13开始的通知运行时权限。前台服务的通知如果不展示部分ROM会直接判定你没有合法的前台服务所以这个权限一定要在用户首次使用时引导授予。Android 14还要求App使用startForegroundService()启动后必须在5秒内调用startForeground()否则会抛ForegroundServiceDidNotStartInTimeException崩溃。3.2 一个可用的KeepAliveService完整代码下面这段代码是我在项目里用过的简化版本去掉了具体业务逻辑保留了保活核心骨架。public class KeepAliveService extends Service { private static final String CHANNEL_ID keep_alive_channel; private static final int NOTIFICATION_ID 1001; Override public void onCreate() { super.onCreate(); createNotificationChannel(); Notification notification buildNotification(); if (Build.VERSION.SDK_INT Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC); } else { startForeground(NOTIFICATION_ID, notification); } } Override public int onStartCommand(Intent intent, int flags, int startId) { // 在这里开始真实业务网络心跳、数据同步、设备巡检等 // 注意不要在这里做耗时阻塞操作长任务要放线程池或者子线程 return START_STICKY; } Nullable Override public IBinder onBind(Intent intent) { return null; } private void createNotificationChannel() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel( CHANNEL_ID, 后台数据同步, NotificationManager.IMPORTANCE_LOW ); channel.setShowBadge(false); getSystemService(NotificationManager.class).createNotificationChannel(channel); } } private Notification buildNotification() { return new NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) .setContentTitle(正在保持后台稳定运行) .setContentText(用于同步消息与检测设备状态) .setOngoing(true) .setPriority(NotificationCompat.PRIORITY_LOW) .build(); } }这段代码里最重要的是onStartCommand返回START_STICKY。它表示如果系统在内存不足时把这个服务进程杀了那么在系统空闲后会尝试重建服务并把空Intent传回onStartCommand。这个机制是“原生保活”里成本最低、收益最确定的一个开关很多开发者居然漏了导致服务被杀就不回来了。启动服务的调用建议这样写Intent intent new Intent(context, KeepAliveService.class); if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { context.startForegroundService(intent); } else { context.startService(intent); }3.3 用AlarmManager做兜底自检形成“自愈”闭环仅靠前台服务和START_STICKY还不够因为有些厂商清理后系统不会那么及时地重建服务。正确的做法是再加一个兜底机制定时检查服务和进程是否存活发现不在就重新拉起。我用AlarmManager实现过一个简化版自检器每15分钟执行一次public class KeepAliveReceiver extends BroadcastReceiver { private static final long INTERVAL 15 * 60 * 1000L; public static void schedule(Context context) { AlarmManager am (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent new Intent(context, KeepAliveReceiver.class); PendingIntent pi PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); long triggerAt System.currentTimeMillis() INTERVAL; if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAt, pi); } else { am.set(AlarmManager.RTC_WAKEUP, triggerAt, pi); } } Override public void onReceive(Context context, Intent intent) { if (!isServiceRunning(context, KeepAliveService.class)) { Intent service new Intent(context, KeepAliveService.class); if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { context.startForegroundService(service); } else { context.startService(service); } } schedule(context); } private boolean isServiceRunning(Context context, Class? serviceClass) { ActivityManager am (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo info : am.getRunningServices(Integer.MAX_VALUE)) { if (serviceClass.getName().equals(info.service.getClassName())) { return true; } } return false; } }这里要注意getRunningServices在Android 5.0以上只能拿到自己的服务信息但这恰好够用了——我们只需要判断自己的服务是否还活着。setAndAllowWhileIdle虽然受Doze限制但作为15分钟级别的自检强度不算高系统一般会放行。3.4 通知栏是“双刃剑”透明度决定存活率前台服务强制要求常驻通知这一点在国产ROM上被放大成了“原罪”。用户看到一条关不掉的、莫名其妙的通知第一反应就是去设置里关掉App的通知权限。一旦通知权限被关闭Android 13以上的系统会直接阻止前台服务启动或者在运行中降级为普通服务你的保活瞬间失效。所以通知文案不能写得太商业化或者太神秘尽量让用户明白这个服务在做什么。比如做运动类的App就写“正在记录你的运动数据”做智能家居的就写“保持与设备连接以便快速控制”。让用户觉得这条通知有价值比什么保活技巧都管用。另外通知渠道的IMPORTANCE建议用IMPORTANCE_LOW或者IMPORTANCE_MIN不要在高优先级的渠道发一个毫无意义的常驻通知那只会引起用户反感还会让厂商系统更容易判定为“骚扰通知”。4. 厂商ROM适配哪些补丁必须打4.1 处理“后台管理白名单”先从设置路径入手不管技术方案做得多漂亮国内ROM的“后台管理开关”都会是最大的变量。以我适配过的设备为例小米在“设置-应用设置-授权管理-自启动管理”里可以允许自启动华为在“手机管家-应用启动管理”里关闭“自动管理”手动开启“允许自启动”和“允许关联启动”OPPO在“设置-电池-耗电保护”里选择“允许后台运行”vivo在“i管家-应用管理-权限管理-自启动”和“后台高耗电”里设置白名单。针对这些路径App在首次启动时做一个引导页或者弹窗把“允许自启动”、“允许后台运行”、“忽略电池优化”三个核心开关的图文步骤展示出来是最有效也最笨的适配手段。很多大厂App都是这么做的用户虽然烦但也会习惯点一下。4.2 电池优化白名单能申请但别滥用Android提供了一个REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限申请后可以弹系统对话框引导用户把App加入“忽略电池优化”列表。加入之后App在Doze模式下的限制会放松很多这是原生层面最接近“永活”的正规通道。但在Google Play上架时这个权限属于“敏感权限”除非App的核心功能就是需要长时后台运行比如导航、健身记录否则审核很难通过。国内应用商店态度相对宽松可以申请。要注意的是即便申请到了权限厂商ROM还是会跑自己的后台清理逻辑所以它并不是万能药。代码里申请这个权限的写法也尽量在用户确实遇到后台问题后再弹出不要一进来就请求那样会触发厂商的异常检测。4.3 通知权限与自启动引导保活链路的最后一块拼图前面提到前台服务依赖通知展示。在Android 13及以上你必须申请POST_NOTIFICATIONS运行时权限国产ROM还会在这个基础上增加一层“通知管理”限制。如果你的App没有引导用户打开通知权限那么前台服务启动时可能不报错但通知不展示厂商系统会把你的服务视为非法后台行为过一会儿就清掉。另一个容易忽略的是“锁屏清理”。华为和小米都有一个“锁屏后清理内存”的选项默认在锁屏10分钟后清理后台进程。这个开关如果开着无论你怎么保活锁屏时间一到照样被杀。引导用户把App加入锁屏清理豁免列表或者干脆建议用户关闭锁屏清理是适配里必须做的事。很多开发者只写了代码忘了这些ROM层的开关最后线上保活率依然很低就是这个原因。4.4 系统预装与Persistent属性聊聊只有合作方才能活的方案如果你的App走系统预装渠道或者和厂商有深度合作那还有一条降维打击的路把App安装为系统级应用并在Manifest里声明android:persistenttrue。这个属性会让系统把当前进程视为核心进程LMK几乎不会杀它就算崩溃重启SystemServer也会在第一时间重新拉起。但普通从应用商店下载安装的App这个属性是不生效的。需要预置到系统分区或者利用Root权限把APK移动到/system/app后才能生效。我在热词里看到有人讨论通过adb shell sh /storage/emulated/0/android/data/.../up.sh这种脚本方式操作那其实就是在调试机上用Root/Shell权限写入系统配置安全性极差不适合正式产品只适合内部测试环境验证效果。开发机上临时测试保活效果时这种Shell脚本确实好用但千万别把这套东西打包进用户端。正常情况下我们能做的就是把上面的用户引导和系统设置适配做好效果已经能达到60分到80分之间剩下的事情属于商务和渠道层面的合作。5. 常见问题与排查技巧实录5.1 线上问题速查表症状可能原因排查思路解决方向锁屏一段时间后服务被杀厂商清理策略、未加白名单查看系统后台管理列表引导用户加白关闭锁屏清理前台服务通知突然消失用户关闭通知权限、服务被降级查看通知栏、设置页引导开启通知权限定时自检任务长时间不触发Doze模式、厂商闹钟合并抓AlarmManager日志改用WorkManager或者降低频率进程没死但业务不执行进程被冻结freeze查看进程状态、freezer信息加白名单、使用可见前台服务安装到Android 14崩溃未声明前台服务类型看崩溃堆栈声明foregroundServiceType和权限5.2 用adb命令把进程状态“看穿”排查保活问题时不要光靠肉眼观察要学会用命令看内核和系统服务的真实状态。# 查看本应用进程的oom_adj值越小越不容易被杀 adb shell cat /proc/pid/oom_score_adj # 查看系统对进程的裁剪和调度状态 adb shell dumpsys activity processes | grep -E myapp|adj # 查看应用是否处于冻结状态Android 11冻结标记 adb shell dumpsys activity processes | grep -iE frozen|freezer # 查看AlarmManager有哪些唤醒事件属于你的应用 adb shell dumpsys alarm | grep -E myapp|KeepAlive我特别建议盯着oom_score_adj这个值。在原生系统上前台服务进程的adj一般是2到5之间如果发现你的进程在后台时adj被改成10以上说明ROM做了额外处理单纯调代码已经没用了必须从用户引导层面解决。小米的部分设备还会在dumpsys activity processes里直接标注“frozen”看到这个字段就可以断定进程被冻结了。5.3 系统日志里“SERVICE killed by system”的潜台词通过logcat -b events或者logcat -b system可以看到类似这样的日志ActivityManager: Killing 12345:com.example.app/u0a123 (background, cached) LowMemoryKiller: Kill com.example.app (12345), uid10123, oom_adj10如果日志里出现“Kill ... (background, cached)”说明系统正常回收了缓存后台进程你的服务没有把自己的优先级提到前台。如果出现“Kill ... (empty)”说明进程连空进程级别都算不上系统觉得留着它没有价值。出现“Killed due to high memory pressure”才是真正的内存不足杀进程这种情况没有太好的办法只能把App自身内存占用降下来。还有一种隐蔽的情况部分ROM在“应用信息-省电策略”里把应用设为了“智能限制后台”日志里不一定有明显关键词但服务就是不触发。这种日志层面的排查往往无效还是要回到设置项去检查。5.4 聊聊Android新变化APEX、16KB页面大小与后台限制最近热词里出现了“android apex”和“支持16KB页面大小”这两个和保活也有隐性关联。Android 10之后系统组件开始用APEX模块方式升级意味着系统底层策略的更新不再依赖整个ROM升级厂商可以在系统模块级别调整进程管理策略。对我们开发者来说这套机制会放大不同ROM版本之间的差异同一套保活代码在Android 13上表现正常到了Android 14可能又被限制一层。另一个正在推进的变化是16KB内存页面大小。Google要求从Android 15开始新应用和更新应用的原生库必须支持16KB对齐否则安装或运行可能出问题。对保活方案的影响在于如果你用Native层做了任何黑科技级的进程守护或者依赖旧版SSO库、加固壳16KB对齐问题很可能直接导致App打不开到时候连保活都不用谈了。所以我的建议很直接能用纯Java/Kotlin实现的保活逻辑就不要下沉到Native层减少依赖、提高兼容性比追求“更强”的保活效果更实用。5.5 玩了两三年保活我最终沉淀下来的几点经验第一个经验是不要追求“永不杀”要追求“快速活”。任何App都做不到永远不被杀安卓系统的资源管理天生就是在动态平衡。与其和系统对抗不如把被杀后的自动拉起链路做完善用户感知上几乎没有中断保活就成功了。第二个经验是保活效果必须用真机多ROM长期测试。模拟器和原生AOSP上跑到飞起不代表什么我见过最夸张的案例App在Pixel设备上稳定挂了三天上了小米设备两小时就被判为“异常后台行为”清理了。每次改完保活逻辑我都会准备一台小米、一台华为、一台OPPO装相同版本连续跑三天看数据再决定要不要上线。第三个经验是关于产品和用户的。相比技术上的保活让用户主动把App放进系统白名单比任何代码都有效。很多开发者羞于引导用户觉得烦人但实际上只要解释清楚“开启后台运行权限才能保证消息及时送达”大多数用户是愿意配合的。引导时机也很有讲究最好在用户确实因为后台被杀而功能异常时再弹出成功率会高很多。最后说一句得罪人的话微信那种级别的后台存活靠的不是某个保活奇技淫巧而是IM场景天然的用户高回访率、长连接服务和厂商层面的合作适配。作为普通App开发者我们更应该把时间花在降低功耗、精简后台逻辑、优化拉起速度上。把这些基本功做到位保活这件事就已经成功了一大半。