Android 的 Partial Wake Lock 限制,后台保活还能怎么做

Android 的 Partial Wake Lock 限制,后台保活还能怎么做

Android 的 Partial Wake Lock 限制,后台保活还能怎么做


Android 的 Partial Wake Lock 限制,后台保活还能怎么做


如果你最近把手上的项目 targetSdkVersion 升到 34(Android 14),同时又恰好有个需要在后台持续跑任务的需求,大概率已经见识过 Google 这两年在电池优化上的铁拳。最直观的感受就是:以前那个靠 PowerManager.WakeLock 加上一个前台服务就能苟住的后台任务,现在连十分钟都撑不过,直接带着 ForegroundServiceDidNotStartInTimeException 或者干脆被系统冻结,连遗言都来不及留。


Partial Wake Lock 这个在 Android 2.3 时代就被写进官方文档的老伙计,曾经是后台保活的万金油。它的逻辑很朴素:屏幕关了没关系,CPU 得给我转着。通过 PowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag") 拿一把锁,acquire() 之后你的线程就能在深夜继续干活,下载、上传、计步、蓝牙交互,全指望这把锁吊命。但吊了十几年,Google 终于觉得这东西太碍眼了。


Doze 模式这八年,从提醒到绞杀


Android 6.0(API 23)引入 Doze 模式的时候,很多人没当回事。当时的说法是:如果设备长时间静止且未充电,系统会进入低功耗状态,间歇性地放开一个小窗口让应用集中处理后台任务。那时候持有 PARTIAL_WAKE_LOCK 的应用虽然也会被按进 Doze,但好歹还有所谓的 maintenance window 可以喘气。到了 Android 7.0(API 24),Google 又补了一刀 LIGHT_DOZE,设备不用完全静止,屏幕关闭一段时间就能触发,这下 Wake Lock 的含金量开始直线下降。


真正致命的转折在 Android 12(API 31)。Google 要求所有前台服务必须在 AndroidManifest.xml 里声明 android:foregroundServiceType,而且每一种类型都对应着特定的权限和使用场景。那时候很多开发者还抱着侥幸心理:行,我声明一个 dataSync 类型,继续跑我的前台服务,Wake Lock 该拿还拿,最多多写几行 XML。但 Android 14(API 34)直接把这条路堵死了。dataSync 类型的前台服务在 Android 14 上被系统强制限定了运行时长,官方文档里写得明明白白:系统可能会在服务运行约 6 小时后将其终止。注意这个“约”字,非常暧昧,实际上很多设备上这个时间远不到 6 小时,尤其是 Pixel 在夜间充电时,系统杀起 dataSync 服务来毫不手软。


更狠的是,Android 14 对 SCHEDULE_EXACT_ALARM 权限下手了。以前用 AlarmManager.setExactAndAllowWhileIdle() 还能在 Doze 的缝隙里精准唤醒 CPU,现在如果你的应用 targetSdkVersion 34+,这个权限默认是拒绝的,用户得手动去设置里打开,而且随时可能关掉。这意味着 Wake Lock + 精确闹钟这套组合拳,从系统层面被拆得七零八落。


Wake Lock 不再是免死金牌


有个细节很多官方文档不会告诉你,但在 AOSP 的 PowerManagerService 源码里写得清清楚楚:当设备进入 Doze 的 DEEP 状态后,系统对 Wake Lock 的态度已经从“尊重”变成了“登记在册但暂不执行”。你的 acquire() 调用不会抛异常,甚至 isHeld() 还返回 true,但 CPU 已经被系统挂起,你的 Runnable 实际上卡在那儿不动。等到 maintenance window 到来,系统才会集中放行。


这就带来一个非常恶心的调试体验。你以为后台线程卡死是因为代码逻辑问题,加了无数日志和重试机制,结果发现单纯是因为系统没给你 CPU 时间。更恶心的是,Android 12 以后引入了 App Hibernation(应用休眠),如果一个应用几个月没被用户主动打开,系统会直接把它放到休眠分区,这时候别说 Wake Lock,连你的静态广播接收器都会被撤销注册。去年有个 GitHub issue 讨论得很热闹,有人发现他们的健康类应用在用 PARTIAL_WAKE_LOCK 做夜间睡眠监测时,Android 14 设备上每隔几十分钟就被强制断连,日志里只有一行冰冷的 WAKE_LOCK_RELEASEDSTOP_FOREGROUND_SERVICE,没有任何可以捕获的异常。


很多开发者这时候才回过味来:Wake Lock 本身没有被废弃,API 还在,但它背后的执行保证已经被掏空了。它变成了一块遮羞布,让你以为自己在持有 CPU,实际上系统说了算。


Android 14 的前台服务分级制度


Android 14 在前台服务上的改动,本质上是在建立一套“后台任务种姓制度”。Google 把前台服务类型分成了三六九等,每一等都有隐形的时限和权限门槛。


mediaPlaybackphoneCall 属于贵族阶层,只要你在播放音频或者正在通话,系统基本不会动你,用户感知也强,所以这类服务的存活率最高。locationcamera 次之,但需要动态权限,而且用户每用一次都在潜意识里加深“这应用在偷跑”的印象。最惨的是 dataSyncshortService。前者被限死在 6 小时,后者干脆只能跑 3 分钟,超时直接 Service.startForeground() 抛异常,应用崩溃。


还有个叫 specialUse 的类型,看起来像是给特殊场景开后门,但实际上传应用商店时,Google Play 会要求你提交视频说明为什么不能用其他类型替代,审核不通过直接打回。这套机制导致中小开发者的生存空间被极度压缩。你有后台同步需求?用 WorkManager。要实时通信?接 FCM。想持续定位?行,但电量消耗黑锅你背。Google 的政策导向越来越明显:后台任务全部收归系统调度,开发者不准私自跑长连接。


但问题是,WorkManager 那套 setExpedited(true) 在 Android 14 上也不是免死金牌。 expedited job 虽然优先级高,但系统对整个设备的 expedited job 总量做了全局配额限制,你的应用在短时间内提交太多,后面的任务会被降级成普通 job,延迟执行。而且 expedited job 不能替代真正的实时性需求,它的保证是“尽快”,不是“立即”。


那些已死的保活邪术


既然正道被堵死,很多人开始翻旧黄历,试图复活那些上古保活邪术。作为一个从 Android 4.4 时代过来的老开发,我可以负责任地说:这些玩意在 Android 14 上全是尸体。


双进程守护,也就是 A 进程看 B 进程,B 进程看 A 进程,一个死了另一个通过 AlarmManager 拉起来。这套在 Android 5.0 前后就基本失效了,因为系统对 fork 出来的进程组进行统一管理,父进程被杀时子进程会被连带送走。Android 8.0(API 26)以后,Service.startForeground() 要求必须在 5 秒内发出通知,双进程守护连前台服务的启动时机都抓不住。


1 像素 Activity 更是一场闹剧。原理是在锁屏时弹出一个 1x1 像素的透明 Activity,让应用处于“前台”状态从而规避后台限制。Android 10(API 29)的 android.permission.SYSTEM_ALERT_WINDOW 权限就已经被收紧,Android 12 的 SplashScreen API 和任务栈管理进一步压缩了这种漏洞,何况现在各大厂商的定制 ROM 都有“后台弹窗权限”的独立开关,默认关闭,用户根本感知不到你的 1 像素窗口。


账号同步(Sync Adapter)曾经是黑科技,利用系统定期触发账号同步的机会拉起进程。Android 8.0 之后,未拥有前台应用或前台服务的后台应用,其 ContentResolver.requestSync() 调用会被延迟到系统认为合适的时间。Android 14 更是直接告诉你:用 WorkManager 的 PeriodicWorkRequest 替代,但周期最短只能 15 分钟,而且受 Doze 和 App Standby 约束。


Sticky Service 呢?START_STICKY 在系统资源充足时确实能自动重启服务,但 Android 14 上,如果你的服务类型不合法或者已经跑满时长,系统重启后只会给你一个非常短的窗口去调用 startForeground(),抓不住就 RemoteServiceException。很多崩溃日志里看到的 Context.startForegroundService() did not then call Service.startForeground(),有一半是因为系统在重启服务时给的时间片太短,应用还没来得及展示通知就被杀了。


WorkManager 与 JobScheduler 的现实困境


Google 官方的叙事一直是:别折腾 Wake Lock 和前台服务了,用 WorkManager,它是后台任务的最优解。这话听听就好,真在生产环境大规模用过 WorkManager 的人都知道它的局限性。


WorkManager 底层在 API 23+ 设备上走的是 JobScheduler,在旧设备上走 AlarmManager + BroadcastReceiver。对于需要保证最终执行的后台任务,比如上传日志、同步数据,WorkManager 确实靠谱。但它的“靠谱”建立在“不保证时效性”的基础上。一个 OneTimeWorkRequest 在设备空闲时可能秒执行,在 Doze 状态下可能拖半小时。你给它加上 setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST),意思是如果配额用完了就当普通任务跑,但这对于需要持续运行的后台场景毫无意义。


更现实的问题是网络约束。WorkManager 支持 setRequiredNetworkType(NetworkType.CONNECTED),但在 Android 7.0+ 的 Doze 模式下,系统会在 maintenance window 期间短暂放开网络,如果你的任务在这几十秒内没跑完,网络会被重新掐断。很多开发者误以为 WorkManager 能保证任务在后台完整执行,结果发现上传进度走到一半卡住,下次 window 到来时又得从头重试,因为系统中间杀掉了进程。


JobScheduler 的直接使用也好不到哪去。Android 14 对 JobInfo.BuildersetMinimumLatency()setOverrideDeadline() 做了更严格的限制,而且通过 JobScheduler 提交的 job 在应用进入缓存状态后,其 onStartJob() 回调的响应延迟被系统有意拉长。Google 在官方文档里加了这么一句:"Jobs scheduled by apps that are in the cached state may be deferred." 翻译成人话就是:你的应用在后台待久了,连 JobScheduler 都不一定准时。


推送通道的垄断与厂商白名单


当 AOSP 层面的后台通道被一条条焊死之后,存活下来的实际上只有两条路:要么接 Google FCM(Firebase Cloud Messaging),要么跪求各大国产手机厂商把你加进白名单。


FCM 在海外市场是事实上的标准,高优先级消息(FCM high priority)确实能唤醒应用并赋予其短暂的后台执行窗口。但 FCM 的到达率在国内网络环境下是个玄学,而且 Google 从 2019 年开始就在打击“滥用高优先级消息”的行为。如果你的应用频繁发送高优先级消息但实际没有用户可感知的即时通知,Google Play 会降低你账号的 FCM 配额,严重时直接封号。


国内环境更赤裸。小米的 MIUI、OPPO 的 ColorOS、vivo 的 OriginOS、华为的 HarmonyOS,都有自己的推送联盟(比如统一推送联盟,后来改名什么的,实际各玩各的)。你得逐一接入厂商的推送 SDK,小米用 MiPush,华为用 HMS Push,OPPO 用 OPPO Push。更关键的是,即便接入了推送,应用能不能在被杀后通过推送拉起,还取决于厂商的“省电策略”和“自启动管理”。用户不手动把你的应用加到自启动白名单,不给你后台运行权限,推送来了也拉不起来。


这就造成了一个非常畸形的市场格局:头部应用因为有商务合作,能进厂商的预装名单和默认白名单,后台保活根本不是技术问题,而是商务问题。中小开发者没有谈判筹码,只能在系统设置里卑微地引导用户:“请关闭电池优化”“请允许自启动”“请锁定后台卡片”。用户一看这么麻烦,直接卸载。最后的结果就是后台资源向大厂集中,创新应用的生存空间被压缩殆尽。


讽刺的是,很多厂商自己系统自带的应用却不受这些规则约束。它们可以持有系统级 Wake Lock,可以注册系统级前台服务,可以绕过 Doze。Android 的权限设计在 AOSP 层面看起来是公平的,一到定制 ROM 上就裂成了两块:一块是给亲儿子的特权,一块是给第三方开发者的牢笼。


那现在到底还能怎么做


聊了这么多限制,回到标题:后台保活还能怎么做?


如果你的需求是“后台持续执行任务”,比如即时通讯的在线状态、运动健康的数据采集、物联网设备的蓝牙连接,现在的 Android 生态基本上在逼你做以下几个选择,每一个都有明显的妥协。


第一,彻底放弃“持续运行”的执念,拥抱间断同步。把任务拆成碎片,用 WorkManager 的 PeriodicWorkRequest 或者 WorkRequest 配合 setExpedited(),每次执行时快速做完然后释放资源。这要求你的业务逻辑能容忍分钟级甚至小时级的延迟。对于非即时通讯类应用,这可能是目前最稳妥的方案,前提是你能接受用户在打开应用前数据是旧的。


第二,如果确实需要实时性,咬牙走厂商推送。国内接各家 Push SDK,国外接 FCM,消息到了之后利用系统给的几秒前台服务启动窗口,快速完成必要操作然后自杀。这里有个细节:Android 12+ 对 BOOT_COMPLETED 广播也做了限制,如果用户安装应用后从未手动打开过,你的 BOOT_COMPLETED 接收器是不会被触发的。所以别指望重启就能自动复活,必须至少有一次用户主动启动。


第三,利用一些系统漏洞或者边缘 API。比如 NotificationListenerService,这个服务在获得用户授权后,系统通常不会杀它,因为杀了它意味着通知栏状态可能不同步。有些应用利用这个特性来保活,代价是必须在设置里引导用户开启“通知使用权”,而且 Google Play 对这类权限的审核非常严格,一旦发现你滥用,直接下架。另一个边缘方案是辅助功能服务(AccessibilityService),但风险更大,属于高危权限,基本上是在雷区跳舞。


第四,引导用户手动改系统设置。这是最无奈也最实际的做法。在应用里检测厂商和系统版本,弹出教程教用户去电池设置里关掉你的应用的后台限制,去自启动管理里打开开关。用户体验极差,转化率极低,但在某些垂直领域(比如企业内应用、硬件配套 App),这是唯一能保证存活率的办法。


第五,如果条件允许,把后台逻辑挪到硬件层。比如用 Wear OS 手表、BLE 外设或者车载系统来承担部分持续连接的任务,手机端只负责偶尔同步。这显然不是所有产品都能走的路线。


我个人觉得,Google 在这件事上的逻辑是自相矛盾的。一方面它大力推行 Jetpack WorkManager、App Actions、Health Connect,鼓励开发者在后台做更多事情;另一方面又把后台执行的限制收得越来越紧,导致这些 API 的可用性大打折扣。最终结果是开发成本转嫁给了中小团队,而头部厂商凭借商业合作轻松绕过限制。Android 曾经引以为傲的开放性,在后台管理这个维度上,已经越来越像 iOS 了——只不过 iOS 好歹给了一条明确的 PushKit 和 Background Processing 通道,而 Android 是表面上给你一堆选择,实际上每条路都放着路障。


Wake Lock 不会消失,但它已经从一个“保证执行”的工具,退化成了一个“申请执行”的弱提示。系统答不答应,全看它的心情。在这个环境下讨论后台保活,技术方案其实已经没有太多灰色地带,剩下的全是工程上的妥协和商业上的博弈。你还在研究怎么在 AndroidManifest 里多写几个权限来苟活的时候,微信已经在系统白名单里躺着了。这才是最残酷的部分。

WebView 的 JsBridge 方案,现在还有必要吗 2026-07-29

评论区