更新记录
1.0.0(2026-11-04)
- Android:前台服务(dataSync 类型)+ Partial WakeLock(可选)+ 电池优化白名单授权,targetSdk ≥ 33 时自动申请通知权限
- iOS:后台音频模式静音保活,附
beginBackgroundTask兜底;电池优化相关接口降级处理(系统不支持) - 接口语义:
startKeepAlive的 success 在 Android 侧代表前台服务真正进入前台;requestIgnoreBatteryOptimization的 success 仅代表授权页拉起,结果需用isIgnoringBatteryOptimizations查询 - 启动失败(含服务 3 秒内未进入前台)通过 fail 回调返回具体原因
- 诊断接口:
startKeepAliveMonitor/stopKeepAliveMonitor(Android),实时推送网络变化、深度 Doze 进入/退出、省电模式切换事件,并附带当前状态快照 - 集成要求:服务组件须在项目根目录 AndroidManifest.xml 中声明,详见 README
平台兼容性
uni-app(4.75)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | × | × | √ | × | 5.0 | 12 | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
ll-keepalive
uni-app App 端后台保活插件(UTS)。保障 App 退后台后 进程存活、CPU 可运行、网络可访问,适用于需要长期保持 UDP/TCP 连接的场景(如局域网设备监听、心跳上报)。
原理:三重威胁与三层防御
后台断网靠的不是杀进程,而是三套独立机制,本插件分别对应处理:
| 威胁 | 触发条件 | 对网络的影响 | 插件的防御 |
|---|---|---|---|
| 进程被杀 | 内存压力 / 厂商激进省电 | socket 和线程直接消失 | Android 前台服务(dataSync)提升进程优先级;iOS 静音音频防挂起 |
| CPU 休眠 | 锁屏后无人持锁 | 线程仍在但无法执行 | Partial WakeLock(Android,可选) |
| Doze 断网 | 拔电 + 静止 + 锁屏 | 系统级挂起网络,前台服务也不能豁免 | 电池优化白名单(官方唯一手段,需用户授权) |
iOS 没有"系统禁网"机制,只有"App 被挂起"。防挂起(后台音频模式)基本等价于保住网络能力。
平台支持
| 平台 | 支持 | 方式 |
|---|---|---|
| Android 5.0+ (minSdk 21) | ✔ | 前台服务 + WakeLock + 电池优化白名单 |
| iOS 12+ | ✔ | 后台静音音频(UIBackgroundModes: audio)+ beginBackgroundTask 兜底 |
| Web / 小程序 | ✘ | — |
集成步骤(重要,缺一不可)
1. 项目根目录声明服务(Android 必须)
UTS 插件目录下的 AndroidManifest.xml 只会合并权限,<application> 子节点会被忽略。必须在项目根目录创建 AndroidManifest.xml(HBuilderX 3.6.0+ 支持,云端打包时合并):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="你的Android包名">
<application>
<service
android:name="uts.sdk.modules.llKeepalive.KeepAliveService"
android:exported="false"
android:foregroundServiceType="dataSync" />
</application>
</manifest>
package填应用的 Android 包名(打包时配置的那个)- 漏掉此步的典型症状:
startForegroundService调用成功但 3 秒后报「保活服务未能在 3 秒内进入前台」,且adb shell am start-foreground-service -n 包名/uts.sdk.modules.llKeepalive.KeepAliveService报Not found
2. manifest.json 配置
"app-plus" : {
"distribute" : {
"android" : {
"targetSdkVersion" : 33,
"permissions" : [
"<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"/>",
"<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_DATA_SYNC\"/>",
"<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\"/>"
]
},
"ios" : {
"UIBackgroundModes" : [ "audio" ]
}
}
}
targetSdkVersion 建议 ≥ 33:低于 33 时通知权限随安装自动授予(不影响功能),但不符合新规范。
3. 重新制作自定义调试基座
UTS 插件的原生代码与清单都打进基座 APK,修改插件后必须:HBuilderX → 运行 → 运行到手机 → 制作自定义调试基座,然后重新安装。页面热更新不生效于原生代码。
4. 国产 ROM 手动设置(无法纯代码解决)
- 小米/Redmi(HyperOS/MIUI):设置 → 应用管理 → 本应用 → 开启自启动,省电策略改为无限制。否则服务可能被静默拦截(表现为 startService 成功但服务不运行)
- 华为/OPPO/vivo:类似,需在启动管理/电池设置中放行
快速上手
import { startKeepAlive, stopKeepAlive, isKeepAliveRunning,
isIgnoringBatteryOptimizations, requestIgnoreBatteryOptimization } from '@/uni_modules/ll-keepalive'
// 启动保活(App 启动时或进入需要保活的页面前调用)
startKeepAlive({
title: '设备监控运行中',
content: '保持网络连接',
wakelock: true,
success: (res) => {
// Android:前台服务已真正进入前台;iOS:静音音频已开始
console.log(res.errMsg)
},
fail: (res) => {
// res.errCode: 1920001(Android) / 1930001(iOS)
console.error(res.errMsg)
}
})
// 电池优化白名单(强烈建议,是 Doze 下保住网络的关键)
if (!isIgnoringBatteryOptimizations()) {
requestIgnoreBatteryOptimization({
success: () => {
// 仅代表系统授权页已拉起,用户是否同意需再查询
console.log('当前白名单状态:', isIgnoringBatteryOptimizations())
}
})
}
// 停止保活
stopKeepAlive()
// 查询状态
console.log(isKeepAliveRunning())
API
startKeepAlive(options)
启动保活。Android 启动前台服务(通知 + dataSync 类型),内部附带尽力申请通知权限(申请失败不阻塞启动);iOS 播放后台静音音频。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
| title | string | 应用正在后台运行 | Android 通知标题(iOS 忽略) |
| content | string | 保持网络连接 | Android 通知内容(iOS 忽略) |
| wakelock | boolean | true | 是否持有 Partial WakeLock(iOS 忽略) |
| success | function | — | Android:服务真正进入前台后回调;iOS:音频开始后回调 |
| fail | function | — | errCode 1920001 / 1930001 |
| complete | function | — | 成功或失败都会回调 |
stopKeepAlive(options?)
停止保活:移除 WakeLock 并停止前台服务(Android)/ 停止静音音频(iOS)。未运行时调用返回成功(幂等)。
isKeepAliveRunning(): boolean
插件内部运行状态标记。注意:用户手动在系统设置里杀了应用后此状态自然消失;它不做系统级校验。
requestIgnoreBatteryOptimization(options?)
拉起「忽略电池优化」系统授权页。success 仅代表页面拉起成功,用户是否同意需用 isIgnoringBatteryOptimizations() 查询。iOS 固定 fail(errCode 1930001)。
isIgnoringBatteryOptimizations(): boolean
当前是否已在电池优化白名单中。iOS 固定返回 false。
startKeepAliveMonitor(options)
开启诊断监听(仅 Android;iOS 返回 fail),把系统事件实时推送到 onEvent,用于回答"后台到底发生了什么":
| 事件类型 | 含义 |
|---|---|
| monitor_started | 起始状态快照:深Doze / 省电模式 / 白名单 / 当前网络 |
| network_available / network_lost | 网络连接与断开(detail 为 Wi-Fi / 蜂窝数据 / VPN) |
| doze_deep_enter / doze_deep_exit | 深度 Doze 进入与退出 |
| power_save_enter / power_save_exit | 省电模式切换 |
light Doze 无公开广播,不做监听也不做猜测;发送失败时请对照事件流的时间线定位。
stopKeepAliveMonitor()
停止诊断监听并注销系统回调。
验证指南
验证必须区分三层,只验证"进程没死"是不够的:
1. 进程/服务层(没死):
adb shell dumpsys activity services | findstr KeepAlive
2. 组件注册层(服务真的进了 APK 清单):
adb shell am start-foreground-service -n 包名/uts.sdk.modules.llKeepalive.KeepAliveService
出现常驻通知 = 注册成功;报 Not found = 根清单漏配或基座未重打。
3. 端到端网络验证(发送式,最硬的证据)
原理:由调用方在 App 内以固定间隔(如每 10 秒)主动发送心跳——UDP 包或 HTTP GET 均可,内容带上序号与时间戳;由任意接收方(HTTP 服务、UDP 监听脚本、IoT 设备等)记录每次到达的间隔。接收方持续按设定间隔收到 = 进程存活 + CPU 可运行 + 网络可用三者同时成立,缺一就会观察到中断。
发送式验证比"被动收包"更宽松:主动流量会持续唤醒 CPU 并刷新 NAT 表项,因此发送侧正常不能直接推出被动接收场景也正常,结论外推时需注意。
对照矩阵:
| 组 | 配置 | 锁屏静止 30 分钟预期 |
|---|---|---|
| A | 什么都不开 | 短期正常,Doze 触发后心跳中断 |
| B | 只开保活(不加白名单) | 进程活着,但 deep Doze 下定时器/网络被挂起 → 心跳中断 |
| C | 保活 + 白名单 | 心跳按固定间隔持续到达 |
强制进入 Doze 做实验:
adb shell dumpsys deviceidle whitelist +包名 :: 或 -包名 做对照组
adb shell dumpsys battery unplug
adb shell dumpsys deviceidle force-idle
adb shell dumpsys deviceidle get deep :: 确认进入 IDLE
:: 此期间调用方的心跳发送保持运行,由接收方记录到达间隔
adb shell dumpsys deviceidle exit :: 恢复
adb shell dumpsys battery reset
判定标准:看接收方记录的心跳间隔——稳定等于设定间隔 = 全链路存活;出现成倍间隔或彻底中断 = 当前配置组缺失对应防御层。
故障排查
| 现象 | 原因与处理 |
|---|---|
fail:保活服务未能在 3 秒内进入前台 |
服务组件没注册(根清单漏配/基座未重打)或被 ROM 静默拦截。先跑上面第 2 步命令;再确认小米自启动 |
fail:服务进入前台失败: xxx |
startForeground 抛出真实异常,xxx 即原因(类型/权限问题) |
fail:Unable to start service Intent ... not found |
同第一条 |
| 启动报 POST_NOTIFICATIONS targetSdk 必须 ≥33 | 基座 targetSdk < 33,属可忽略的旧版本行为(权限已自动授予),插件已兼容;建议重打基座选 targetSdk 33+ |
| 调用方周期心跳偶发/持续失败 | 失败回调通常含异常类名、信息与耗时;对照 startKeepAliveMonitor 事件流的时间线定位:失败时刻若伴随 network_lost / doze_deep_enter / power_save_enter,即为对应原因;无伴随事件则多为 Wi-Fi 抖动或路由器问题 |
| 控制台报 onEvent 回调函数已释放,不能再次执行 | 长期持有的 JS 回调被 GC。@UTSJS.keepAlive 注解必须配合函数声明式导出(export function xxx(...))才生效,export const xxx : Type = function 形式上注解无效 |
| 启动成功但通知栏看不到常驻通知 | 三种可能,依次排查:① HyperOS/MIUI 把常驻通知折叠在通知栏底部「正在运行」区,展开查看;② 通知权限未授予(adb shell dumpsys package 包名 \| findstr POST_NOTIFICATIONS,未授予则 adb shell pm grant 包名 android.permission.POST_NOTIFICATIONS);③ 服务根本没起来(adb shell dumpsys activity services 包名 \| findstr KeepAlive)。通知显示与否不影响保活本身 |
| 编译报 uni.UNIApplication 相关错误 | 宿主框架类在 UTS 编译环境不可用,插件内已全部改用纯 Android SDK + Context 注入,勿在 .kt 中引用宿主类 |
已知限制
- Android 15+ 对
dataSync类型前台服务有每日约 6 小时的运行限制,超长保活场景可考虑改用specialUse类型 - 电池优化白名单在部分深度定制 ROM 上不保证完全生效(如 MIUI 神隐模式),必须配合系统设置放行
- 用户在最近任务里主动划掉应用:部分 ROM 会连带杀死前台服务,无法防御
- iOS 静音音频方案在 App Store 审核中可能被质疑(需说明用途);测试无影响
- 插件不防御用户主动强制停止(设置 → 强制停止后服务与白名单授权仍在但应用不会自启)

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 1
赞赏 0
下载 12739115
赞赏 1959
赞赏
京公网安备:11010802035340号