如何在应用被杀死时保持WorkManager持续运行?
解决WorkManager在应用被杀后停止下载的问题
问题核心分析
你的代码存在两个关键问题:
- 空循环无实际下载逻辑:
while (!isDownloadCompleted)只是空转,既没执行下载动作,也没机会将isDownloadCompleted设为true,属于无效代码。 - 未适配系统后台限制:Android(尤其是国内OEM厂商)对后台进程有严格的回收策略,单纯的WorkManager任务在应用被杀后容易被系统终止,同时你的Worker没有做断点续存和优先级提升处理。
解决方案
一、重构Worker逻辑:实现可恢复的断点下载
替换空循环,编写真正的协程下载逻辑,同时保存下载进度,确保进程重启后能从断点继续:
class UploadWorker( private val appContext: Context, workerParams: WorkerParameters ) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { val fileName = inputData.getString("filName") ?: return Result.failure() val url = inputData.getString("URL") ?: return Result.failure() // 启动前台通知,提升任务优先级(避免被系统回收) val notification = createDownloadNotification(fileName) setForeground(ForegroundInfo(1001, notification)) // 读取之前保存的断点进度 val savedProgress = getSavedDownloadProgress(fileName) val targetFile = getTargetFile(appContext, fileName) return try { // 执行断点下载 downloadFileWithProgress(url, targetFile, savedProgress) { currentProgress, total -> // 保存进度到本地 saveDownloadProgress(fileName, currentProgress) // 更新通知进度 updateNotificationProgress(fileName, currentProgress, total) // 更新WorkManager任务进度(供UI监听) setProgress(workDataOf("CURRENT" to currentProgress, "TOTAL" to total)) } // 下载完成,清理进度记录 clearDownloadProgress(fileName) updateNotificationCompleted(fileName) Result.success() } catch (e: Exception) { // 根据错误类型决定是否重试 if (e is IOException || e is CancellationException) { Result.retry() } else { updateNotificationFailed(fileName) Result.failure(workDataOf("ERROR" to e.message)) } } } // 从SharedPreferences读取断点进度 private fun getSavedDownloadProgress(fileName: String): Long { val prefs = appContext.getSharedPreferences("DownloadProgress", Context.MODE_PRIVATE) return prefs.getLong(fileName, 0) } // 保存下载进度 private fun saveDownloadProgress(fileName: String, progress: Long) { val prefs = appContext.getSharedPreferences("DownloadProgress", Context.MODE_PRIVATE) prefs.edit().putLong(fileName, progress).apply() } // 清理进度记录 private fun clearDownloadProgress(fileName: String) { val prefs = appContext.getSharedPreferences("DownloadProgress", Context.MODE_PRIVATE) prefs.edit().remove(fileName).apply() } // 获取下载目标文件 private fun getTargetFile(context: Context, fileName: String): File { val downloadDir = context.getExternalFilesDir(android.os.Environment.DIRECTORY_DOWNLOADS) ?: context.filesDir return File(downloadDir, fileName) } // 断点下载核心逻辑(基于OkHttp实现) private suspend fun downloadFileWithProgress( url: String, targetFile: File, startOffset: Long, onProgress: suspend (current: Long, total: Long) -> Unit ) { val client = OkHttpClient() val request = Request.Builder() .url(url) .header("Range", "bytes=$startOffset-") .build() client.newCall(request).await().use { response -> if (!response.isSuccessful) throw IOException("请求失败: ${response.code}") val totalBytes = response.body?.contentLength() ?: 0 val outputStream = RandomAccessFile(targetFile, "rw").apply { seek(startOffset) }.outputStream() response.body?.source()?.use { source -> var bytesRead: Long val buffer = ByteArray(8192) var currentProgress = startOffset while (source.read(buffer).also { bytesRead = it.toLong() } != -1L) { // 检查Worker是否被系统终止 if (isStopped) throw CancellationException("任务被系统终止") outputStream.write(buffer, 0, bytesRead.toInt()) currentProgress += bytesRead onProgress(currentProgress, startOffset + totalBytes) } } } } // 创建下载通知 private fun createDownloadNotification(fileName: String): Notification { val channelId = "DOWNLOAD_CHANNEL" // Android 8.0+ 需要创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( channelId, "下载通知", NotificationManager.IMPORTANCE_LOW ) val notificationManager = appContext.getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) } return NotificationCompat.Builder(appContext, channelId) .setContentTitle("正在下载") .setContentText(fileName) .setSmallIcon(R.drawable.ic_download) .setProgress(100, 0, true) .setOngoing(true) .build() } // 更新通知进度 private fun updateNotificationProgress(fileName: String, current: Long, total: Long) { val progress = if (total > 0) ((current * 100) / total).toInt() else 0 val notification = NotificationCompat.Builder(appContext, "DOWNLOAD_CHANNEL") .setContentTitle("正在下载") .setContentText(fileName) .setSmallIcon(R.drawable.ic_download) .setProgress(100, progress, total == 0L) .setOngoing(true) .build() val notificationManager = appContext.getSystemService(NotificationManager::class.java) notificationManager.notify(1001, notification) } // 更新通知为下载完成状态 private fun updateNotificationCompleted(fileName: String) { val notification = NotificationCompat.Builder(appContext, "DOWNLOAD_CHANNEL") .setContentTitle("下载完成") .setContentText(fileName) .setSmallIcon(R.drawable.ic_download_done) .setOngoing(false) .build() val notificationManager = appContext.getSystemService(NotificationManager::class.java) notificationManager.notify(1001, notification) } // 更新通知为下载失败状态 private fun updateNotificationFailed(fileName: String) { val notification = NotificationCompat.Builder(appContext, "DOWNLOAD_CHANNEL") .setContentTitle("下载失败") .setContentText(fileName) .setSmallIcon(R.drawable.ic_download_error) .setOngoing(false) .build() val notificationManager = appContext.getSystemService(NotificationManager::class.java) notificationManager.notify(1001, notification) } }
二、优化WorkRequest配置
保持原有约束的基础上,确保任务持久化:
val imageData = workDataOf("URL" to url, "filName" to filName) val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(false) .setRequiresStorageNotLow(false) .build() val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>() .setInputData(imageData) .setConstraints(constraints) .setBackoffCriteria( BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS ) .build() // 将任务提交给WorkManager WorkManager.getInstance(appContext).enqueue(uploadWorkRequest)
三、应对OEM后台限制
国内多数厂商(小米、华为、OPPO等)有严格的后台杀进程策略,需引导用户手动开启应用的后台权限:
- 小米:设置 → 应用设置 → 应用管理 → 你的应用 → 省电策略 → 无限制
- 华为:设置 → 电池 → 应用启动管理 → 你的应用 → 关闭自动管理,允许后台活动
- OPPO:设置 → 电池 → 应用耗电管理 → 你的应用 → 允许后台耗电
内容的提问来源于stack exchange,提问作者Ahmad Idrees
相关产品推荐
相关产品推荐

