PAYATHON 2026

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 中使用网络和部分唤醒锁,其他限制仍然存在。开发者即使不考虑电池消耗,也不会因此自动获得系统豁免。电池优化豁免

推荐怎样组织采集和上传

将采集频率和上传时机分开处理:

  1. 采集后立即写入 Room 等持久化存储,记录采集时间和唯一记录 ID。
  2. 有执行机会且网络可用时,批量上传待发送记录。
  3. 收到服务器成功确认后,再将对应记录标记为已上传。
  4. 用带网络约束的 WorkManager 任务补传,处理断网、进程终止和设备重启等情况。
  5. 服务端按记录 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 任务配额变化

备注:内容仅供参考。