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

如何在应用被杀死时保持WorkManager持续运行?

解决WorkManager在应用被杀后停止下载的问题

问题核心分析

你的代码存在两个关键问题:

  1. 空循环无实际下载逻辑:while (!isDownloadCompleted)只是空转,既没执行下载动作,也没机会将isDownloadCompleted设为true,属于无效代码。
  2. 未适配系统后台限制: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 17:15:36