如何防止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) } } }
方案生效原理
- 强制停止服务后:进程被杀死,Worker终止但WorkManager会标记任务为
ENQUEUED待恢复。重启应用时,ViewModel会直接取消非运行状态的任务;若任务已恢复为RUNNING,Worker启动时会校验凭证——由于强制停止时进程被杀死,凭证未被正常清除,但重启后当前时间大概率已超过原结束时间,校验失败后Worker直接终止。 - 设备关机重启后:SharedPreferences中的凭证依然存在,但Worker启动时会校验当前时间是否小于结束时间,过期则直接终止任务;未过期但任务因关机中断的场景,也可通过凭证规则直接阻止恢复。
- 解决异步竞态:ViewModel改用协程异步处理状态,同时Worker自身的校验逻辑从根源上阻止无效任务执行,不再依赖ViewModel的状态判断时机。
内容的提问来源于stack exchange,提问作者philo
相关产品推荐
相关产品推荐

