这里是Kotlin / Android新手:)。我正在使用CoroutineWorker进行分块上载,并且看不到内置的方法来为工作人员维护状态,以防发生重试,但是我很难相信这样的东西会丢失...
我的用例如下:
chunkIndex
。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()
}
}
我是否忽略了某些内容,还是真的必须将该索引存储在工作人员之外?
您有一个很好的用例,但是不幸的是您不能在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)
方法!)
参考:官方documentation和API。
如接受的答案中所述,似乎没有办法在工作线程中缓存数据或执行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;
}
}