android Workmanger的长期运行工作者问题:获取异常kotlinx.coroutines.作业取消异常:作业已在Kotlin的CoroutineWorker中取消

n6lpvg4x  于 2023-02-17  发布在  Android
关注(0)|答案(2)|浏览(190)

我创建了一个简单的CoroutineWorker,它运行1000次循环,延迟为1000毫秒。
此辅助进程是唯一的定期辅助进程,重复间隔为15分钟,并且ExistingPeriodicWorkPolicy为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
        )
    }

我在Activity中单击按钮时调用上述函数。
我不知道为什么我会在10分钟后的一段时间后自动得到这个异常。
谢谢提前。请帮助我在这方面确定原因,并请让我知道任何输入从我这边。

uajslkp6

uajslkp61#

您是否已检查是否满足Internet权限所赋予的downloadConstraints变量中设置的约束,尝试使用PeriodicWorkRequest.Builder中的setInitialDelay()检查是否满足约束

lb3vh1jj

lb3vh1jj2#

对我来说,我无法设法重现这个异常;尽管假设满足所有工作限制;在工作进程花费很长时间的情况下,存在由系统取消工作进程的可能性。
对于这种情况,文档提供了support for long-running workers,其关联前台服务,前台服务向系统/用户指示在后台存在长时间运行的处理;因此系统可以保持该进程,并且用户知道什么正在消耗他们的资源,并且这也可以允许他们停止该工作者。
尤其是当你提到它的长期与:
我不知道为什么我会在10分钟后的一段时间后自动得到这个异常。
文档中说了一些我认为可能是原因的东西:
WorkManager内置了对长时间运行的工作进程的支持。在这种情况下,WorkManager可以向操作系统提供一个信号,指示在执行此工作时应尽可能使进程保持活动状态。这些工作进程的运行时间可以超过10分钟。
要启用此功能,需要调用startForeground()来更新与前台服务关联的通知;通常随着工人的进步。
文档中提供了一个示例,展示了如何在worker中使用它;在这里,我已经为您的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>

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

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

相关问题