Android 应使用哪种后台任务机制定期上传设备信息?
结论
普通第三方 Android 应用没有一种后台任务机制,能保证全天约每分钟执行一次、熄屏后继续执行、进程被杀后立即恢复,并在设备重启后自动恢复相同频率。
选择方案时,需要明确哪些条件可以放宽:
- 允许系统延迟采集和上传时,优先使用 WorkManager。
- 用户主动开启一段持续采集任务时,可以考虑前台服务,在服务内约每分钟采集一次,先将数据写入本地数据库,上传失败后再由 WorkManager 补传。
- 无人值守、全天分钟级采集的需求,需要评估受管设备或系统定制方案。普通应用即使组合使用多个调度 API,也无法获得这样的执行保证。
前台服务可以在屏幕关闭、Activity 不可见时运行,符合问题中对“后台”的定义。在 Android 16 上使用时,还需要考虑服务类型、运行时限和 Doze。
以下讨论适用于运行在 Android 16(API level 36)上、targetSdkVersion 为 36 或更高的普通应用。minSdkVersion 只决定最低支持版本,部分后台限制还取决于 targetSdkVersion。
这几种机制分别适合什么
| 机制 | 是否适合约每分钟持续执行 | 在这个需求中的用途 |
|---|---|---|
WorkManager 的 PeriodicWorkRequest | 不适合,最短周期为 15 分钟,执行还可能延迟 | 周期性补传、可延迟的采集 |
JobScheduler.setPeriodic() | 不适合,同样受最短周期和系统调度限制 | 直接使用平台任务调度,通常无需用它替代 WorkManager |
普通后台 Service | 不适合长期运行,受后台服务限制 | 不能作为常驻采集方案 |
| 前台服务 | 可用于符合条件的持续任务,但不保证全天运行或分钟级准时 | 用户主动开启的采集会话 |
AlarmManager | 不适合每分钟上传遥测数据 | 闹钟、日历提醒等与具体时间相关的任务 |
WorkManager 的“15 分钟”是允许配置的最短重复周期,不保证每 15 分钟执行一次。约束条件、Doze 和系统调度都可能推迟执行,改用 JobScheduler 也不会消除这些限制。WorkManager 周期任务说明、JobScheduler 周期设置
连续提交延迟一分钟的 OneTimeWorkRequest,同样无法把 WorkManager 变成可靠的分钟级定时器。一次性任务仍受系统调度和执行配额约束。
Android 16 上,前台服务有哪些限制
dataSync 不能作为全天常驻服务
设备向服务器上传数据通常对应 dataSync 前台服务类型。对于以 Android 15 或更高版本为目标的应用,应用处于后台时,这类前台服务在 24 小时内累计只能运行 6 小时。同一应用的多个 dataSync 服务共享这份额度。
额度按服务运行时间计算。即使每分钟只上传几秒钟,其余时间都在执行 delay(),等待时间也会计入额度。
达到时限后,系统会调用 Service.onTimeout(int, int),服务需要及时调用 stopSelf()。用户将应用带回前台会重置计时,但无人值守的程序不能依赖这一点。前台服务运行时限
以 Android 15 或更高版本为目标的应用,也不能从 BOOT_COMPLETED 接收器启动 dataSync 前台服务。因此,在这里的版本条件下,无法通过开机广播启动上传服务并让它一直运行。dataSync 类型说明
specialUse 适用于其他类型没有覆盖的有效前台服务用途,需要在 manifest 中说明用途,上架 Google Play 时还会接受审核。仅凭“需要一直采集”,无法判断应用是否符合 specialUse,不能把它作为规避 dataSync 时限的通用配置。specialUse 类型说明
熄屏运行还涉及 CPU 休眠和 Doze
前台服务不会自动让 CPU 一直保持唤醒。设备休眠时,协程中的 delay(60_000) 不会自行唤醒 CPU。
符合条件的持续任务如果确实需要熄屏执行,可能还需要使用 PARTIAL_WAKE_LOCK,在不点亮屏幕的情况下保持 CPU 运行。不过,唤醒锁本身不能解决 Doze 的限制:Doze 会限制网络访问,并忽略普通应用的唤醒锁。设备唤醒机制、Doze 限制
符合条件并获得电池优化豁免的应用,可以在 Doze 中使用网络和部分唤醒锁,其他限制仍然存在。开发者即使不考虑电池消耗,也不会因此自动获得系统豁免。电池优化豁免
推荐怎样组织采集和上传
将采集频率和上传时机分开处理:
- 采集后立即写入 Room 等持久化存储,记录采集时间和唯一记录 ID。
- 有执行机会且网络可用时,批量上传待发送记录。
- 收到服务器成功确认后,再将对应记录标记为已上传。
- 用带网络约束的 WorkManager 任务补传,处理断网、进程终止和设备重启等情况。
- 服务端按记录 ID 去重,处理服务器已收到数据、客户端却在记录成功状态前被终止而产生的重复请求。
如果只在用户主动开启的会话中进行分钟级采集,可以由前台服务负责采集和及时上传。服务应由用户在可见界面上的操作启动,提供前台服务通知和停止入口,并按实际用途声明服务类型及权限。普通应用通常不能在后台随意启动前台服务。前台服务启动限制
如果由 WorkManager 负责采集,就必须接受较低的采集频率和调度延迟。离线时仍需执行的采集任务不要添加网络约束,否则断网时采集也会被推迟。
Compose 只负责界面和开始、停止操作。持续采集循环不应依附于 Composable 的 LaunchedEffect 或页面生命周期。
Kotlin 代码示例
用 WorkManager 注册周期补传
这个函数注册一个需要网络连接、最短周期为 15 分钟的补传任务。workerClass 应传入项目中实现的 Worker,例如 UploadPendingWorker::class.java。
import android.content.Context
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
fun scheduleUploadRecovery(
context: Context,
workerClass: Class<out ListenableWorker>
) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = PeriodicWorkRequest.Builder(
workerClass,
15,
TimeUnit.MINUTES
)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
"telemetry-upload-recovery",
ExistingPeriodicWorkPolicy.KEEP,
request
)
}
Worker 每次只应处理有限的一批待上传数据,处理完就结束。暂时性网络故障可返回 Result.retry();认证失败、请求格式错误等情况应按业务规则处理,避免无限重试。
如果希望联网后尽快尝试上传,可以再提交一个带网络约束的一次性任务,由周期任务负责后续补传。两者都不保证网络刚恢复就立即执行。
在前台服务中执行约一分钟一次的循环
运行这个采集循环的前提是服务已经合法启动并进入前台状态。通知、数据库、HTTP 请求和电源管理需要分别实现。
import android.os.SystemClock
import java.io.IOException
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
suspend fun runSamplingSession(
collectAndPersist: suspend () -> Unit,
uploadPending: suspend () -> Unit
) {
while (currentCoroutineContext().isActive) {
val startedAt = SystemClock.elapsedRealtime()
collectAndPersist()
try {
uploadPending()
} catch (_: IOException) {
// 保留本地记录,后续再次上传。
}
val elapsed = SystemClock.elapsedRealtime() - startedAt
delay((60_000L - elapsed).coerceAtLeast(0L))
}
}
应在服务自己的协程作用域中运行这段代码,并在服务结束时取消该作用域。采集函数负责读取和持久化数据;上传函数需要配置请求超时,并处理 HTTP 状态码。
使用 SystemClock.elapsedRealtime() 计算间隔,可以避免用户修改系统时间影响周期。单轮耗时超过一分钟时,下一轮会延后;设备休眠、服务终止或系统限制也会造成间断。这个循环本身无法唤醒 CPU,也不会改变 dataSync 的运行时限。
重启、OOM 和数据读取还要注意什么
WorkManager 会持久化任务,并在应用进程重新创建或设备重启后恢复调度。恢复调度后,任务仍需等待约束满足和系统允许运行,也无法补采进程停止期间每一分钟的真实状态。WorkManager 持久化任务说明
对于 Service,START_STICKY 可以让系统在进程被杀后尝试重新创建服务,但不保证立即恢复,也不等于设备重启后会自动启动。进程内的变量和协程状态都会丢失,因此采集状态必须持久化。START_STICKY 说明
OOM 等系统终止与用户“强行停止”也需要区分。强行停止会让应用进入停止状态,无法保证自动恢复。
采集时还要明确各字段的含义:
- 电量可从
ACTION_BATTERY_CHANGED的 sticky intent 中读取EXTRA_LEVEL和EXTRA_SCALE。电量读取方式 BatteryManager.EXTRA_TEMPERATURE表示电池温度,不能标成 CPU 温度或整机温度。BatteryManager API- 网络状态可通过
ConnectivityManager和NetworkCapabilities读取。NET_CAPABILITY_VALIDATED表示系统检测到公网连通性,仍不保证你的服务器可达,上传请求需要处理失败和重试。网络状态说明
AlarmManager 同样无法保证分钟级执行。普通闹钟会被 Doze 推迟;setAndAllowWhileIdle() 和 setExactAndAllowWhileIdle() 在空闲状态下也有频率限制。官方文档给出的限制是,每个应用不能超过每九分钟一次,因此不适合每分钟上传。Doze 中的闹钟限制
Android 16 还调整了任务执行配额:即使应用同时运行前台服务,WorkManager 和 JobScheduler 调度的任务仍受相应配额限制。前台服务与 WorkManager 配合使用,可以改善持续采集和失败恢复,但执行仍可能间断。Android 16 任务配额变化
备注:内容仅供参考。
