Android worker-在重试之间更新和保留状态

问题描述 投票:2回答:2

这里是Kotlin / Android新手:)。我正在使用CoroutineWorker进行分块上载,并且看不到内置的方法来为工作人员维护状态,以防发生重试,但是我很难相信这样的东西会丢失...

我的用例如下:

  1. 创建带有要上传为输入数据的文件的路径的辅助请求
  2. Worker遍历文件并分块执行上传。正在跟踪最新上传的chunkIndex
  3. 如果发生错误并随后出现Retry(),则工作器以某种方式检索当前块索引并继续执行,而不是从头开始重新进行。

所以基本上,我真的只需要保留那个chunkIndex标志。我研究了设置进度,但是重试似乎很成功(尝试一次,一次尝试都无法使用)。

override suspend fun doWork(): Result {
    try {
        // TODO check if we are resuming with a given chunk index
        chunkIndex = ...

        // do the work
        performUpload(...)

        return Result.success()

    } catch (e: Exception) {
        // TODO cache the chunk index


        return Result.retry()
    }
}

我是否忽略了某些内容,还是真的必须将该索引存储在工作人员之外?

android kotlin android-workmanager
2个回答
2
投票

您有一个很好的用例,但是不幸的是您不能在Worker类中缓存数据,也不能在重试时将数据传递给下一个Worker对象!您怀疑,您将必须存储在WorkManager提供的结构之外的索引!

答案很长,

Worker对象可以接收和返回数据。它可以通过getInputData()方法访问数据。如果您是chain tasks,则可以为下一个在线工人输入一个工人的输出。可以通过返回Result.success(output)(请参见下面的代码)

完成
public Result doWork() {
        int chunkIndex = upload();

        //...set the output, and we're done!
        Data output = new Data.Builder()
            .putInt(KEY_RESULT, result)
            .build();
        return Result.success(output);
}

因此,问题是我们不能为重试情况返回数据,而只能为失败和成功情况返回数据! (缺少Result.retry(Data data)方法!)

参考:官方documentationAPI


0
投票

如接受的答案中所述,似乎没有办法在工作线程中缓存数据或执行Result.retry(data)。我最终只是用SharedPreferences进行了快速修改。

下面的解决方案。把它和一粒盐一起吃,我的腰带总共大约有10个小时的Kotlin;)

var latestChunkIndex = -1

override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
    try {
        // get cached entry (simplified - no checking for fishy status or anything)
        val transferId = id.toString()
        var uploadInfo: UploadInfo = TransferCache.tryGetUpload(applicationContext, transferId) ?: TransferCache.registerUpload(applicationContext, transferId, TransferStatus.InProgress)

        if(uploadInfo.status != TransferStatus.InProgress) {
            TransferCache.setUploadStatus(applicationContext, transferId, TransferStatus.InProgress)
        }

        // resolve the current chunk - this will allow us to resume in case we're retrying
        latestChunkIndex = uploadInfo.latestChunkIndex

        // do the actual work
        upload()

        // update status and complete
        TransferCache.setUploadStatus(applicationContext, id.toString(), TransferStatus.Success)
        Result.success()
    } catch (e: Exception) {
        if (runAttemptCount > 20) {
            // give up
            TransferCache.setUploadStatus(applicationContext, id.toString(), TransferStatus.Error)
            Result.failure()
        }

        // update status and schedule retry
        TransferCache.setUploadStatus(applicationContext, id.toString(), TransferStatus.Paused)
        Result.retry()
    }
}

upload函数中,我只是跟踪我的缓存(我也可以在doWork方法的异常处理程序中进行此操作,但是我还将使用缓存项进行状态检查,而且很便宜):

private suspend fun upload() {
    while ((latestChunkIndex + 1) * defaultChunkSize < fileSize) {

        // doing the actual upload
        ...

        // increment chunk number and store as progress
        latestChunkIndex += 1
        TransferCache.cacheUploadProgress(applicationContext, id.toString(), latestChunkIndex)
    }
}

TransferCache看起来像这样(请注意,那里有no家务,因此如果不进行清理,它将继续增长!)]

class UploadInfo() {
    var transferId: String = ""
    var status: TransferStatus = TransferStatus.Undefined
    var latestChunkIndex: Int = -1

    constructor(transferId: String) : this() {
        this.transferId = transferId
    }
}


object TransferCache {

    private const val PREFERENCES_NAME = "${BuildConfig.APPLICATION_ID}.transfercache"
    private val gson = Gson()

    fun tryGetUpload(context: Context, transferId: String): UploadInfo? {
        return getPreferences(context).tryGetUpload(transferId);
    }


    fun cacheUploadProgress(context: Context, transferId: String, transferredChunkIndex: Int): UploadInfo {
        getPreferences(context).run {
            // get or create entry, update and save
            val uploadInfo = tryGetUpload(transferId)!!
            uploadInfo.latestChunkIndex = transferredChunkIndex
            return saveUpload(uploadInfo)
        }
    }


    fun setUploadStatus(context: Context, transferId: String, status: TransferStatus): UploadInfo {
        getPreferences(context).run {
            val upload = tryGetUpload(transferId) ?: registerUpload(context, transferId, status)
            if (upload.status != status) {
                upload.status = status
                saveUpload(upload)
            }

            return upload
        }
    }


    /**
     * Registers a new upload transfer. This would simply (and silently) override any
     * existing registration.
     */
    fun registerUpload(context: Context, transferId: String, status: TransferStatus): UploadInfo {
        getPreferences(context).run {
            val upload = UploadInfo(transferId).apply {
                this.status = status
            }
            return saveUpload(upload)
        }
    }


    private fun getPreferences(context: Context): SharedPreferences {
        return context.getSharedPreferences(
            PREFERENCES_NAME,
            Context.MODE_PRIVATE
        )
    }


    private fun SharedPreferences.tryGetUpload(transferId: String): UploadInfo? {
        val data: String? = getString(transferId, null)
        return if (data == null)
            null
        else
            gson.fromJson(data, UploadInfo::class.java)
    }


    private fun SharedPreferences.saveUpload(uploadInfo: UploadInfo): UploadInfo {
        val editor = edit()
        editor.putString(uploadInfo.transferId, gson.toJson(uploadInfo))
        editor.apply()
        return uploadInfo;
    }
 }
© www.soinside.com 2019 - 2024. All rights reserved.