Workmanger 的长时间运行 Worker 问题:获取异常 kotlinx.coroutines.JobCancellationException: Job was cancelled in CoroutineWorker in Kotlin

2024-01-03

我创建了一个简单的CoroutineWorker运行循环 1000 次,延迟 1000 毫秒。

该工人是独特的周期性工人,重复间隔为 15 分钟,并且ExistingPeriodicWorkPolicy as KEEP

但是当我启动工作程序并在执行一段时间后,工作程序会因异常而被取消JobCancellationException

完整的例外:

Exception kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@ba57765
12:55:47.088 WM-Wor...rapper  I  Work [ id=4c44c3da-3c57-4cac-a40a-82c948125807, tags={ com.sk.workmanagerdemo1.DownloadingWorker } ] was cancelled
                                 java.util.concurrent.CancellationException: Task was cancelled.
                                    at androidx.work.impl.utils.futures.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1184)
                                    at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:514)
                                    at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
                                    at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:311)
                                    at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
                                    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
                                    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
                                    at java.lang.Thread.run(Thread.java:923)

工人代码:

import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.*

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

    override suspend fun doWork(): Result {
        return withContext(Dispatchers.IO) {
            Log.i("MYTAG", "Started ${getCurrentDateTime()}")
            return@withContext try {
                for (i in 0..1000) {
                    delay(1000)
                    Log.i("MYTAG", "Downloading $i")
                }
                Log.i("MYTAG", "Completed ${getCurrentDateTime()}")
                Result.success()
            } catch (e: Exception) {
                Log.i("MYTAG", "Exception $e")
                Result.failure()
            }
        }
    }

    private fun getCurrentDateTime(): String {
        val time = SimpleDateFormat("dd/M/yyyy hh:mm:ss")
        return time.format(Date())
    }
}

以及工人的启动

private fun setPeriodicWorkRequest() {
        val downloadConstraints = Constraints.Builder()
            .setRequiresCharging(true)
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
        val periodicWorkRequest = PeriodicWorkRequest
            .Builder(DownloadingWorker::class.java, 15, TimeUnit.MINUTES)
            .setConstraints(downloadConstraints)
            .build()
        WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork(
            "DownloadWorker",
            ExistingPeriodicWorkPolicy.KEEP,
            periodicWorkRequest
        )
    }

我在活动中单击按钮时调用上述函数。

我不确定为什么 10 分钟后我会自动收到此异常。

提前致谢。请帮助我找出原因,并请让我知道我这边的任何意见。


对我来说,我无法重现该异常;尽管假设满足所有工作限制;如果需要很长时间,系统可能会取消工作进程。

对于这种情况,文档提供了对长期工作的员工的支持 https://developer.android.com/guide/background/persistent/how-to/long-running它关联一个前台服务,向系统/用户指示后台有一个长时间运行的处理;因此系统可以保留进程,并且用户知道什么正在消耗他们的资源,这也可以让他们停止该工作人员。

尤其是当你提到它很长时:

我不确定为什么 10 分钟后我会自动收到此异常。

文档中说了一些我认为可能是原因的内容:

WorkManager 为长时间运行的工作人员提供内置支持。在这样的 在这种情况下,WorkManager 可以向操作系统提供一个信号,表明该进程 如果可能的话,在执行这项工作时应该保持活动状态。这些 工人可以运行超过10分钟。

要启用它,您需要调用startForeground() https://developer.android.com/reference/kotlin/androidx/work/CoroutineWorker#setforeground更新与前台服务相关的通知;通常随着工人的进步。

一个例子 https://developer.android.com/guide/background/persistent/how-to/long-running#long-running由文档提供,展示如何在工作人员中使用它;我在这里为你定制了它DownloadingWorker:


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

    private val notificationId: Int = 1

    override suspend fun doWork(): Result {
        return withContext(Dispatchers.IO) {
            Log.i("MYTAG", "Started ${getCurrentDateTime()}")
            return@withContext try {
                for (i in 0..1000) {
                    delay(1000)
                    val progress = "Starting Download"
                    setForeground(createForegroundInfo(progress))
                    Log.i("MYTAG", "Downloading $i")
                    setForeground(createForegroundInfo("Downloading $i"))
                }
                Log.i("MYTAG", "Completed ${getCurrentDateTime()}")
                setForeground(createForegroundInfo("Completed"))
                Result.success()
            } catch (e: Exception) {
                Log.i("MYTAG", "Exception $e")
                Result.failure()
            }
        }
    }

    // Creates an instance of ForegroundInfo which can be used to update the
    // ongoing notification.
    private fun createForegroundInfo(progress: String): ForegroundInfo {
        val title = "notification title)"
        val cancel = "cancel download"
        val channelId = "notification id"
        // This PendingIntent can be used to cancel the worker
        val intent = WorkManager.getInstance(applicationContext)
            .createCancelPendingIntent(getId())

        // Create a Notification channel if necessary
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createChannel(channelId)
        }

        val notification = NotificationCompat.Builder(applicationContext, channelId)
            .setContentTitle(title)
            .setTicker(title)
            .setContentText(progress)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setOngoing(true)
            // Add the cancel action to the notification which can
            // be used to cancel the worker
            .addAction(android.R.drawable.ic_delete, cancel, intent)
            .build()

        return ForegroundInfo(notificationId, notification)
    }


    @RequiresApi(Build.VERSION_CODES.O)
    private fun createChannel(channelId: String) {
        // Create a Notification channel
        val serviceChannel = NotificationChannel(
            channelId,
            "Download Channel",
            NotificationManager.IMPORTANCE_DEFAULT
        )

        val notificationManager =
            applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as
                    NotificationManager
        notificationManager.createNotificationChannel(serviceChannel)

    }


    private fun getCurrentDateTime(): String {
        val time = SimpleDateFormat("dd/M/yyyy hh:mm:ss")
        return time.format(Date())
    }

}

确保将前台服务添加到清单中<application>:

<application
    ....
    <service
       android:name="androidx.work.impl.foreground.SystemForegroundService"
       android:foregroundServiceType="dataSync" />

</application>

您还需要请求Manifest.permission.POST_NOTIFICATIONS在 API 33+ 和清单中以编程方式:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Workmanger 的长时间运行 Worker 问题:获取异常 kotlinx.coroutines.JobCancellationException: Job was cancelled in CoroutineWorker in Kotlin 的相关文章

随机推荐