You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止WorkManager在应用被强制停止后重启计时任务

解决方案:通过状态凭证阻止WorkManager任务在强制停止/重启后恢复

核心思路是给计时任务添加有效状态凭证,只有凭证验证通过时Worker才继续执行,同时优化ViewModel中的任务管理逻辑,避免异步状态判断的竞态问题。

步骤1:添加状态凭证管理(基于SharedPreferences)

用SharedPreferences存储当前有效计时任务的关键信息,包括任务ID、结束时间。Worker启动时先校验这些信息,无效则直接终止任务。

object TimerWorkCredential {
    private const val PREF_NAME = "TimerWorkPrefs"
    private const val KEY_WORK_ID = "current_work_id"
    private const val KEY_END_TIME = "current_end_time"

    fun saveCredential(context: Context, workId: UUID, endTime: Long) {
        context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
            .putString(KEY_WORK_ID, workId.toString())
            .putLong(KEY_END_TIME, endTime)
            .apply()
    }

    fun clearCredential(context: Context) {
        context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
            .remove(KEY_WORK_ID)
            .remove(KEY_END_TIME)
            .apply()
    }

    fun isValid(context: Context, workId: UUID): Boolean {
        val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        val savedWorkId = prefs.getString(KEY_WORK_ID, null)
        val savedEndTime = prefs.getLong(KEY_END_TIME, 0)
        // 验证规则:任务ID匹配,且当前时间未超过任务结束时间
        return savedWorkId == workId.toString() && System.currentTimeMillis() < savedEndTime
    }
}

步骤2:修改TimerWorker,添加凭证校验

在doWork方法开头先校验凭证,无效则直接返回Result.failure()终止任务;任务正常完成或中断时清除凭证。

class TimerWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        // 优先校验凭证,无效直接终止任务
        if (!TimerWorkCredential.isValid(applicationContext, id)) {
            return Result.failure()
        }

        val minutes = inputData.getInt("minutes", 0)
        val endTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(minutes.toLong())
        
        setForeground(createForegroundInfo(endTime - System.currentTimeMillis()))
        return try {
            while (System.currentTimeMillis() < endTime) {
                delay(500) // 用协程delay替代Thread.sleep,符合CoroutineWorker规范
                val leftTime = endTime - System.currentTimeMillis()
                setForeground(createForegroundInfo(leftTime))
                println(leftTime)
            }
            TimerWorkCredential.clearCredential(applicationContext) // 任务完成,清除凭证
            Result.success()
        } catch (e: InterruptedException) {
            TimerWorkCredential.clearCredential(applicationContext) // 任务中断,清除凭证
            Result.failure()
        }
    }

    private fun createForegroundInfo(leftTime: Long): ForegroundInfo {
        val notification = NotificationCompat
            .Builder(applicationContext, NotificationHelper.CHANNEL_ID_FOREGROUND)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Backgrounding... $leftTime")
            .setOngoing(true)
            .build()

        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            ForegroundInfo(999, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
        } else {
            ForegroundInfo(999, notification)
        }
    }
}

步骤3:优化ViewModel的任务管理逻辑

移除阻塞主线程的同步调用,改用协程异步处理任务状态;启动任务时保存凭证,初始化时直接清理无效任务。

class TimerViewModel(application: Application) : AndroidViewModel(application) {
    private val workManager = WorkManager.getInstance(application)
    private val context = application.applicationContext

    init {
        viewModelScope.launch {
            // 异步获取任务状态,避免主线程阻塞
            val workInfos = workManager.getWorkInfosForUniqueWork("timer").await()
            if (workInfos.isNotEmpty()) {
                val workInfo = workInfos.first()
                when (workInfo.state) {
                    WorkInfo.State.RUNNING -> {
                        // 运行中任务也需要校验凭证,无效则取消
                        if (!TimerWorkCredential.isValid(context, workInfo.id)) {
                            workManager.cancelUniqueWork("timer")
                        }
                    }
                    else -> {
                        // 非运行状态的任务直接取消
                        workManager.cancelUniqueWork("timer")
                    }
                }
            }
        }
    }

    fun startTimerAlarm() {
        viewModelScope.launch {
            val minutes = _state.value.hour * 60 + _state.value.minute
            val timerWorkRequest = OneTimeWorkRequestBuilder<TimerWorker>()
                .setInputData(workDataOf("minutes" to minutes))
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 0, TimeUnit.MILLISECONDS)
                .build()

            workManager.enqueueUniqueWork(
                "timer",
                ExistingWorkPolicy.KEEP,
                timerWorkRequest
            )
            // 保存任务凭证:任务ID和结束时间
            val endTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(minutes.toLong())
            TimerWorkCredential.saveCredential(context, timerWorkRequest.id, endTime)
        }
    }
}

方案生效原理

  1. 强制停止服务后:进程被杀死,Worker终止但WorkManager会标记任务为ENQUEUED待恢复。重启应用时,ViewModel会直接取消非运行状态的任务;若任务已恢复为RUNNING,Worker启动时会校验凭证——由于强制停止时进程被杀死,凭证未被正常清除,但重启后当前时间大概率已超过原结束时间,校验失败后Worker直接终止。
  2. 设备关机重启后:SharedPreferences中的凭证依然存在,但Worker启动时会校验当前时间是否小于结束时间,过期则直接终止任务;未过期但任务因关机中断的场景,也可通过凭证规则直接阻止恢复。
  3. 解决异步竞态:ViewModel改用协程异步处理状态,同时Worker自身的校验逻辑从根源上阻止无效任务执行,不再依赖ViewModel的状态判断时机。

内容的提问来源于stack exchange,提问作者philo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:28:14