更新记录

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 审核中可能被质疑(需说明用途);测试无影响
  • 插件不防御用户主动强制停止(设置 → 强制停止后服务与白名单授权仍在但应用不会自启)

更新日志

见 changelog.md

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android:FOREGROUND_SERVICE、FOREGROUND_SERVICE_DATA_SYNC、POST_NOTIFICATIONS、WAKE_LOCK、REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;iOS:后台音频模式(UIBackgroundModes: audio)

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无