做Uniapp开发的朋友应该都有过这种经历:辛苦做好的App,业务逻辑都跑通了,结果用户锁屏几分钟,功能就“躺平”了。后台下载停了、运动轨迹断断续续、IM消息收不到、定位一直不肯更新。去各大应用市场的评论区和用户群里一看,“后台被杀”“消息收不到”“动不动就掉线”几乎是安卓类App的标配差评。更头疼的是,Uniapp本身就是跨平台框架,H5和小程序端的生命周期模型跟原生安卓完全不搭边,很多开发者一听说“保活”两个字就头皮发麻,不知道从哪儿下手。
这篇文章就专门解决这个问题。我基于Uniapp的安卓离线打包和原生插件机制,完整拆解一个“安卓后台保活插件”的设计思路、实现原理、适配要点和踩坑记录。涉及的技术核心包括前台服务(ForegroundService)、Notification渠道、权限适配、主流国产ROM的白名单引导,以及Uniapp层和原生层的数据通信。无论你是准备给自己的App接入后台保活能力,还是只是想去插件市场挑一个保活插件但看不懂它的实现原理,这篇都可以拿来当参考。
1. 后台保活的本质:先弄懂安卓为什么会杀后台
1.1 系统不是针对你,它在管理资源
很多开发者一遇到后台被杀,第一反应是“系统变态”。但换个角度想,安卓是一个多任务系统,后台进程多了,内存、CPU、网络、电量全都会被拖垮。谷歌在系统层面有一套完整的管理机制,从早期的Low Memory Killer(LMK)到后来引入的Doze模式、App Standby、后台执行限制和后台定位限制,一层比一层狠。
简单说,安卓判断要不要杀后台进程,主要看几个因素:
- 进程的优先级:前台进程 > 可见进程 > 服务进程 > 后台进程 > 空进程。这个优先级决定了系统在内存吃紧时先杀谁。
- 缓存进程数量:很多国产ROM会设置一个缓存进程阈值,超过就批量清理。
- 用户行为特征:在Doze模式下,系统会把所有网络访问和同步任务集中到指定的维护窗口(Maintenance Window),避免频繁唤醒。
- 厂商自定义策略:小米、华为、OPPO、vivo、荣耀这些厂商,都在原生安卓之上加了各种“省电精灵”“智能清理”,它们对后台进程的判定比原生系统更激进,这就是为什么同一款App在三星、Pixel上能活着,到了国产ROM上几分钟就没了。
所以我们要做的“后台保活”,本质上不是黑科技,而是“尽最大可能提高App在后台的进程优先级、降低被系统判为可清理对象的概率”。
1.2 保活解决的业务场景
后台保活不是所有App都需要。如果只是一个工具类应用,用户用完就关,完全没有保活必要。真正的刚需集中在下面几类:
- 运动健康类:记录轨迹、统计步数、锁屏后需要持续采集传感器数据。
- 音频/有声内容类:后台播放音乐、有声书,必须保持服务存活。
- 即时通讯类:接收消息推送,尤其是没有厂商推送渠道或者被用户关闭推送通知的场景。
- 考勤打卡/外勤管理类:后台定位上报、围栏触发。
- 下载传输类:大文件上传下载,切到后台不能断。
这些业务都有一个共同特点:用户不要求App在后台看得见、摸得着,但要求App继续干活。而这正是安卓后台机制的“重灾区”——一旦App进入后台,系统会默认降低它的权限,限制CPU、限制网络、限制广播,甚至直接冻结整个进程。
1.3 保活方案的技术面面观
做了这么多年安卓开发,我大概把市面上的保活方案分成这么几类,各有各的适用场景,也各有各的代价:
| 方案 | 原理 | 适用场景 | 风险 |
|---|---|---|---|
| 前台服务(ForegroundService) | 通过常驻通知让服务处于“前台”可见状态,提高进程优先级 | 定位、音乐、下载、上传等所有需要持续运行的业务 | 必须显示通知,部分用户反感 |
| WorkManager / AlarmManager | 系统级调度,定时唤醒执行任务后退出 | 周期性的短任务,不需要持续在后台 | 唤醒周期受Doze影响,不是真正保活 |
| 双进程守护 | 两个进程互相拉起 | 被系统允许的数据同步场景 | 大部分ROM已进行限制,且应用市场审核不过 |
| 厂商白名单 | 引导用户在系统设置中把App加入电池优化白名单、自启动白名单 | 国内ROM长期存活必须做的配套动作 | 依赖用户手动操作,需要引导页面 |
| 隐藏通知/1像素Activity | 偷偷生存在后台,逃避用户感知 | 几乎没有合规场景 | 违反应用商店规则,系统直接限制,极不建议使用 |
我把话说在前面:没有绝对100%不被杀的保活方案。系统权限是用户的,用户想把App杀了,谁也拦不住。我们做的所有事情,都是在合规的前提下,把存活概率尽量拉高。那些宣称“永久保活”“绝对杀不死”的插件或方案,要么是刚出的时候能跑,后来系统更新被堵死;要么本身就不合规,应用市场直接拒审。
2. 方案选型:为什么最终定在前台服务这条路上
2.1 从桌面端到安卓端,Uniapp的天然短板
Uniapp是跨端框架,大部分逻辑跑在封装好的webview和JS引擎里。在H5和微信小程序环境下,根本不存在“后台保活”这个概念,页面不可见时,应用本身就该被休眠。到了移动App端,Uniapp做了很多适配,但核心的进程管理、Service生命周期这些事情,它还是没法直接通过JS API来操作。
比如你需要在后台持续定位,Uniapp虽然提供了uni.startLocation这样的接口,但它只是调了系统定位能力,没有绑定一个长期存活的Service。屏幕熄灭后,系统功耗管理和定位策略一变,回调就可能中断。
所以在Uniapp里做后台保活,正确的思路是:保活这件事本身用原生安卓实现,做成插件,暴露给JS调用;Uniapp层只负责触发、配置和跟用户交互。
2.2 前台服务:最稳的合规保活手段
选前台服务作为核心方案,理由很直接:
- 系统资源管理机制里,前台服务的进程优先级只低于系统核心进程和用户当前正在交互的进程。系统在内存极度紧张时才会把它列入清理候补。
- 前台服务有持续存在的通知,用户能够明确知道App正在后台运行,这符合谷歌和各大应用商店的规范。
- Uniapp原生插件机制里,Service是合法的系统组件,可以稳定驻留,跨页面、跨生命周期存活。
- 国内厂商再怎么激进,也不敢在用户没任何感知的情况下随意杀掉一个带前台通知的服务——顶多做“清理后台应用”时统一处理。
当然也有代价:前台服务必须显示通知。如果你的App做的是“偷偷干活”且用户毫无感知的那种业务,对不起,这条路走不通。但正常的业务场景里,一个“正在后台定位”的通知反而是提升透明度的好事情,用户看了也能理解为什么App还活着。
2.3 原生插件是唯一靠谱的接入方式
Uniapp做原生功能,常见的有三种方式:
- 通过
plus.android运行时反射调用原生API,不需要离线打包,灵活但能力有限,很多系统级API拿不到。 - 编写原生插件(Module/Component),集成到离线打包的Android工程中,需要自定义基座,能力强,适合复杂的原生逻辑。
- 使用DCloud官方插件市场封装的现成插件,按文档配置,最快,但灵活性和可控性差,出了问题不好排查。
后台保活这件事,我强烈建议走原生插件方案。原因很简单:你要给Service配置权限、处理Android不同版本的兼容、适配厂商ROM,还要管理通知渠道和上下文长连接。这些逻辑用plus.android反射做,代码会变得极其臃肿,而且遇到任何内部API变动,你根本调不到具体的错误信息。原生插件虽然前期要配置离线打包,但把所有原生逻辑封闭在一个类里,后面维护起来非常清晰。
3. 核心实现:手写一个Uniapp安卓后台保活插件
3.1 前置准备与工程结构
开始之前,你本地需要有一套能跑通的Uniapp安卓离线打包环境。大致包括:
- Android Studio(建议Arctic Fox以上版本,至少API 30+的SDK)
- 从DCloud官网下载对应的离线打包SDK(uniapp-release-aar、lib.5plus.base等)
- 把离线打包的Android工程跑起来,确认你的Uniapp应用能出包
建议先做一个空的离线打包工程,能正常打开一个Hello uniapp页面之后再开始搞插件,否则环境问题会跟代码问题混在一起,排查起来相当痛苦。
插件的工程结构大概是这样的:
android ├── app │ ├── src/main │ │ ├── java/com/example/keepalive │ │ │ ├── KeepAliveModule.java │ │ │ └── KeepAliveService.java │ │ ├── res │ │ └── AndroidManifest.xml │ └── build.gradle └── project/build.gradleKeepAliveModule继承io.dcloud.feature.uniapp.common.UniModule,负责跟JS交互;KeepAliveService继承Service,是真正跑后台逻辑的宿主。
3.2 原生侧:创建前台服务
先写Service本体。一个最简可用的前台服务长这样:
public class KeepAliveService extends Service { private static final String CHANNEL_ID = "keepalive_channel"; private static final int NOTIFICATION_ID = 1001; @Override public void onCreate() { super.onCreate(); createNotificationChannel(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 让服务进入前台状态,必须在服务启动后立刻调用 startForeground(NOTIFICATION_ID, buildNotification()); // 这里放你的业务逻辑,比如持续定位、心跳、数据同步 return START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } private Notification buildNotification() { Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity( this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("正在运行") .setContentText("应用后台服务运行中") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(pendingIntent) .setPriority(NotificationCompat.PRIORITY_LOW) .setOngoing(true) .build(); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "后台服务", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = getSystemService(NotificationManager.class); if (manager != null) { manager.createNotificationChannel(channel); } } } @Override public void onDestroy() { super.onDestroy(); // 避免用户手动清除任务时服务直接被带死,可以做重启兜底 // 但不建议在这里强行无条件重启,要判断业务是否真的需要继续 } }几个关键点我得单独说明。
一是startForeground必须在Service启动后5秒内调用。从Android 8.0开始,系统规定后台应用不能随便启动后台Service,如果确实需要启动,必须用ContextCompat.startForegroundService()方法调起来,然后在Service内部的onStartCommand里立刻执行startForeground。如果你在5秒内没做这件事,系统会直接抛异常崩溃,异常名叫ForegroundServiceDidNotStartInTimeException,这是前台服务最容易踩的坑之一。
二是START_STICKY的作用。当Service因为系统资源不足被强杀时,系统会尝试在之后重建这个Service,重建时传进来的Intent为null。这个机制能提供一定程度的“死后复活”能力,说是兜底也好、保险也好,在天气、背单词这类低频率业务上够了,在定位、IM这类高实时性业务上还是不够的,必须配合主进程外层的机制来搞。
三是android:stopWithTask属性。如果Manifest里Service没有配置这个属性,系统默认情况下,当用户把App从最近任务列表(Recent Tasks)里划掉时,Service会跟Task一起被销毁。这个属性原本的作用是让Service随任务的清除而结束,但对保活来说是致命的。我们可以在Manifest里显式设为false:
<service android:name=".KeepAliveService" android:exported="false" android:stopWithTask="false" />3.3 原生侧:把能力暴露给Uniapp
Service写好了,接下来就要让JS层调得起它。Uniapp原生插件的模块类写法是固定的:
public class KeepAliveModule extends UniModule { @RunOnUIThread @JSMethod(uiThread = false) public void start(JSONObject options, UniJSCallback callback) { try { Context context = mUniSDKInstance.getContext(); Intent intent = new Intent(context, KeepAliveService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent); } else { context.startService(intent); } if (callback != null) { JSONObject result = new JSONObject(); result.put("code", 0); result.put("message", "success"); callback.invoke(result); } } catch (Exception e) { if (callback != null) { JSONObject result = new JSONObject(); result.put("code", -1); result.put("message", e.getMessage()); callback.invoke(result); } } } @RunOnUIThread @JSMethod(uiThread = false) public void stop(JSONObject options, UniJSCallback callback) { try { Context context = mUniSDKInstance.getContext(); context.stopService(new Intent(context, KeepAliveService.class)); if (callback != null) { JSONObject result = new JSONObject(); result.put("code", 0); result.put("message", "success"); callback.invoke(result); } } catch (Exception e) { if (callback != null) { JSONObject result = new JSONObject(); result.put("code", -1); result.put("message", e.getMessage()); callback.invoke(result); } } } }这里有一处非常容易翻车:直接在startForegroundService里启动Service,但没做权限判断。Android 12(API 31)之后,如果你没有动态申请通知权限,前台服务运行起来时系统只弹一个“该App正在后台运行”的提示,但通知不显示,用户感知错乱。Android 13(API 33)更进一步,以Android 13为目标平台的App,必须在运行时先申请POST_NOTIFICATIONS权限,才能创建通知渠道和显示通知。忘了这一步的话,前台服务能起来,但通知栏一片空白,用户找不到进入App的入口,产品那边就会被各种“App消失了”的反馈淹没。
所以插件里启动Service前后,应该有一个完整的权限检查流程,下面统一在JS侧处理。
3.4 JS侧代码:权限处理与插件调用
先导入插件模块。如果用的是DCloud的uni原生插件方式,JS端一般是这样:
const keepAliveModule = uni.requireNativePlugin('KeepAliveModule');判断系统版本并动态申请通知权限的逻辑,可以写在App启动时或者进入首页时。
async function ensureNotificationPermission() { // Android 13及以上才需要动态申请通知权限 const platform = uni.getSystemInfoSync().platform; if (platform !== 'android') return; const systemInfo = uni.getSystemInfoSync(); const androidVersion = Number(systemInfo.osVersion.split('.')[0]); if (androidVersion < 33) return; // 这里需要用到原生插件能力,直接调用plus.android动态申请权限 const mainActivity = plus.android.runtimeMainActivity(); const requestPermissions = plus.android.importClass('android.content.pm.PackageManager'); const permission = 'android.permission.POST_NOTIFICATIONS'; plus.android.requestPermissions( [permission], function(result) { if (result.granted && result.granted.length > 0) { console.log('通知权限已授权'); } else { // 跳转到系统设置页引导用户开启 plus.runtime.openAppSettings(); } }, function(error) { console.error('申请通知权限失败', error); } ); }申请完权限之后,再调用插件的启动方法:
function startKeepAlive() { ensureNotificationPermission().then(() => { keepAliveModule.start( { type: 'location', interval: 5000 }, (result) => { console.log('保活服务启动结果', result); if (result.code !== 0) { uni.showToast({ title: result.message, icon: 'none' }); } } ); }); }这里有几个接口设计上的心得:
start方法的第一个参数拿到的是JSON对象,可以直接传给原生层做配置。比如你的业务需要指定定位间隔、上传地址、白名单开关,这些都可以通过这个JSON对象传过去,不要在JS层硬写死。- 回调函数
UniJSCallback在原生线程和JS线程之间是自动切换的,你甚至不需要手动切线程,直接回调就能跑回JS side,非常方便。 - 如果你的保活服务只是要“活着”,纯粹给别的原生模块提供宿主进程,那JS层调一次
start之后就不用管了,后面全靠原生Service内部自行运转。
3.5 Manifest配置与混淆规则
AndroidManifest.xml里的完整配置长这样:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />FOREGROUND_SERVICE是Android 9(API 28)之后必须加的,不加就崩。FOREGROUND_SERVICE_LOCATION是Android 14(API 34)对前台服务细分类型的要求,如果你的服务类型包含了定位,这个权限必须一起声明。WAKE_LOCK用于保持CPU唤醒,做定位、下载时建议加上。
Service节点的配置也要写全:
<service android:name=".KeepAliveService" android:exported="false" android:foregroundServiceType="location|dataSync" android:stopWithTask="false" />foregroundServiceType这个字段是从Android 10(API 29)开始出现的,Android 14之后变成强制要求。如果你的App目标平台是34以上,但Service没声明类型,那么startForeground调用时会直接抛MissingForegroundServiceTypeException,而且是运行时崩溃,不是编译期报错,很多人上线后才从崩溃平台看到这个问题。
另外,如果你的工程开了混淆(minifyEnabled true),一定要留意keep规则:
-keep class com.example.keepalive.** { *; }原生插件类如果被混淆了,Uniapp插件调用时可能会报“Module not found”,排查起来非常费劲,所以直接全部keep掉。
4. 版本适配与厂商兼容:保活真正的分水岭
4.1 系统版本的演进是个大坎
OK,代码跑通了,服务也能起来了,但你会发现事情远没有这么简单:安卓的版本适配,才是一道真正的坎。从Android 6.0到Android 14,几乎每个大版本都在收紧后台权限。我直接给你整理一张实战排查对照表,你按表来配就少走弯路:
| 系统版本 | 限制内容 | 对应处理 |
|---|---|---|
| Android 6.0+ | Doze模式、App Standby | 申请电池优化白名单(REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) |
| Android 8.0+ | 后台Service限制、通知渠道 | 使用startForegroundService,创建NotificationChannel |
| Android 9.0+ | 前台服务权限 | 在Manifest加入FOREGROUND_SERVICE权限 |
| Android 10+ | 后台定位限制 | 声明ACCESS_BACKGROUND_LOCATION权限,引导用户授予后台定位 |
| Android 11+ | 包可见性变化 | 若需查询其他应用,在Manifest加QUERY_ALL_PACKAGES(非必须不加) |
| Android 12+ | 通知权限、前台服务启动限制 | 动态申请POST_NOTIFICATIONS,判断是否允许从后台启动Service |
| Android 13+ | 通知权限必须动态申请 | 引导用户授权,并处理拒绝逻辑 |
| Android 14+ | 前台服务类型强制、部分类型BOOT_COMPLETED受限 | 声明foregroundServiceType,避免在开机广播里拉起受限的前台服务类型 |
这里单独提一下Android 14的前台服务类型。官方把前台服务分成camera、microphone、location、mediaPlayback、dataSync等若干类型,不同业务必须声明对应类型。保活里最常用的是dataSync和location。如果你的业务是地理位置上报,就声明location,需要同时具备定位权限;如果是普通的后台数据同步,就声明dataSync。类型不对、权限不全,startForeground调用就会失败。
4.2 国产ROM白名单引导是核心中的核心
系统版本的适配只是基础,真正让保活效果天差地别的,是国产ROM的“省电策略”。小米的MIUI有“神隐模式”,华为EMUI/HarmonyOS有“应用启动管理”,OPPO ColorOS有“睡眠待机优化”,vivo OriginOS有“后台高耗电限制”,荣耀Magic UI有“智能启动管理”。这些机制不归谷歌管,每个厂商都有自己的逻辑,而且很多是黑盒,你不知道它内部怎么判定,只能按官方文档、社区经验去适配。
最有效的做法是做一个“保活引导页”,在用户打开App后提示:“为了确保后台功能正常运行,请允许本应用自启动、忽略电池优化,并加入后台运行白名单。”引导页里给出每个ROM的跳转路径。常用的跳转代码是:
Intent intent = new Intent(); intent.setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")); startActivity(intent);不同ROM对应的包名和Activity名各不相同,而且经常随着系统版本变化而失效。所以与其在原生层写死一堆跳转,我建议在引导页里做“扫码查教程”或“动态隐藏/显示入口”的方式,用户点击后先在页面上展示图文说明,再给尝试跳转的按钮。跳转失败就引导用户手动去设置里找,至少用户有个操作依据。
4.3 网络状态与电量的平衡
保活服务里通常还要做网络请求,比如心跳上报、数据同步。这里有两个大坑:
第一个是网络切换时连接断开。安卓Wifi和移动网络切换时,TCP连接会直接失效,如果你用的是长连接,必须重连。我一般在Service里注册CONNECTIVITY_ACTION广播监听,网络变化时把长连接断开重建。
第二个是Doze模式下网络访问受限。Doze模式下Wifi网络会被临时挂起,你在后台发起的网络请求可能延迟很久。这个没有完美的解法,要么申请电池优化白名单,让App跳出Doze限制;要么用系统的JobScheduler或WorkManager调度,等系统维护窗口时再统一上报。前者对定位类业务更有效,后者对消息类、数据同步类业务更合适。
5. 常见问题与排查:从崩溃日志到最终细节
5.1 高频问题速查表
我把平时群里和技术社区看到的高频问题整理成了一张表,你遇到了直接对号入座:
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| App切后台几分钟就没了 | 未引导用户加白 | 检查是否进入厂商省电白名单,补引导页 |
| Service启动瞬间崩溃 | 未在5秒内startForeground | 检查onStartCommand里startForeground是否在首行 |
| Android 14上启动崩溃 | 缺少foregroundServiceType | 在Manifest里补上对应类型,并声明对应权限 |
| 前台服务通知不显示 | 未申请POST_NOTIFICATIONS | Android 13+动态申请通知权限 |
| 开机后App不自动启动 | 厂商自启动白名单未开启 | 引导用户加入自启动白名单,申请BOOT_COMPLETED权限 |
| 定位在锁屏后失效 | 未申请后台定位权限 | 申请ACCESS_BACKGROUND_LOCATION,引导用户设置中开启 |
| 通知渠道不显示 | 渠道重要性设置为IMPORTANCE_NONE | 设为IMPORTANCE_LOW或DEFAULT |
| 用户手动划掉任务后服务被杀 | stopWithTask未设置 | 在Manifest中设置android:stopWithTask="false" |
5.2 排查实战:学会用Logcat和adb说话
代码遇到问题,先在控制台里看日志。Service崩没崩、崩在哪、哪个权限缺失,往往一眼就能看出来。
比较典型的几个日志关键字:
ForegroundServiceDidNotStartInTimeException MissingForegroundServiceTypeException SecurityException: startForeground requires android.permission.FOREGROUND_SERVICE Not allowed to start service Intent ... app is in background前三个都好理解,最后一个“Not allowed to start service”是后台启动限制。通常发生在App被划掉、进程还在、你又尝试用字符串方式重新拉起Service的场景。这时候日志会告诉你系统拒绝了你,不用再怀疑是不是代码写错了,去检查启动时机和启动方式就行。
另外推荐用adb命令辅助验证保活效果:
# 查看App进程是否存活 adb shell ps | grep com.example.app # 查看前台服务是否在运行 adb shell dumpsys activity services | grep KeepAliveService # 查看前台服务优先级 adb shell dumpsys activity processes | grep com.example.app # 模拟低内存,观察进程是否被杀 adb shell am send-trim-memory com.example.app SHIMMER重点是dumpsys activity processes里输出的oom相关的值,如果显示foreground或PERCEPTIBLE,说明优先级已经被拉高,存活概率很大。
5.3 经验之谈:几个长期维护中的小细节
写保活插件不是写完就完事了,维护周期往往比开发周期长得多。分享几个我感觉比较重要的细节。
通知文案别写得太“可怕”。有的开发者把通知写成“后台服务已开启,请勿清理”,用户看到觉得莫名其妙,反而给了差评。更好的做法是把通知做成一个状态展示位,比如“运动轨迹记录中”“正在同步数据 12:00”,让用户意识到这是有意义的功能,不是App偷偷占资源。
onDestroy里不要无条件重启Service。很多人为了实现“杀不死”,在onDestroy里无限循环拉起自己。这在老版本安卓上也许有效,但在现在的系统版本下,只会被当成恶意应用限制,甚至直接加入“后台高耗电”黑名单。我建议只在特定业务场景下做重启兜底,比如GPS轨迹服务要求整段轨迹不能断,哪怕Service被系统杀了也得尽量续上。普通的数据同步类业务用WorkManager就够,没必要做这一层。
适配新系统要有预判。安卓每年一个大版本,每次对后台能力都有收紧,厂商ROM也会更新自家省电策略。我现在的习惯是每年安卓大版本发布后,第一时间在开发者预览版上跑一遍保活服务,看看有没有新的限制。等到用户量爆发、崩溃率飙升了再去修,就太晚了。
最后,在应用市场审核上也要多加留意。前台服务是否违规,主要看通知描述和实际业务是否匹配。如果你做的是一个词典App,却在前台服务里跑定位,审核人员一眼就能看出问题。业务和保活类型保持一致,通知文案诚实,基本都能过审。
这个时代做安卓App,保活已经越来越像一门平衡的艺术——既要在系统限制和业务需求之间走钢丝,又得兼顾用户体验、应用市场审核和长期维护成本。你花大功夫搞出来的插件,可能在某个厂商ROM的新版本里一夜之间失效,这很正常。对我来说,与其追求“绝对不杀”,不如把底层的保活能力做成模块化:前台服务管存活,白名单引导管体验,业务层管实时性,三层解耦,哪个版本适配出了问题就修哪层。这套思路跑下来,至少能帮你在后台存活率这件事上,不再那么被动。