Jetpack系列(九) — WorkManager
WorkManager简单介绍
初步印象
WorkManager
是一个 API,可供您轻松调度那些即使在退出应用或重启设备后仍应运行的可延期异步任务
WorkManager 适用于可延期工作,即不需要立即运行但需要可靠运行的工作,即使退出应用或重启设备也不影响工作的执行。例如:
- 向后端服务发送日志或分析数据
- 定期将应用数据与服务器同步
- 对图像应用过滤器并保存图像
基本概念
Worker
:在后台执行的实际工作的代码,重写doWor()
方法
WorkRequest
:执行某些工作请求,为创建工作请求的一部分传入Worker
WorkManager
: 管理工作请求并使其运行,分散系统资源负载的方式调度工作请求,同时遵守指定的约束
WorkManager基本使用
基本使用
-
这里借用官网给图片做模糊效果的例子练习一下
WorkManager
的使用,首先创建Worker
- 如果需要实例化可用
Hilt
注入Worker
,我这里用的androidx.hilt:hilt-work
的版本是1.0.0
,所以直接使用@HiltWorker
, 版本不同注解可能也不同,一般不需要 doWork()
里面执行具体的迷糊操作,这里和官网保持一致
class BlurWorker constructor( private val ctx: Context, params: WorkerParameters ) : Worker(ctx, params) { override fun doWork(): Result { // 通知,记得加通道 makeStatusNotification("Blurring image", ctx) return try { val picture = BitmapFactory.decodeResource( ctx.resources, R.drawable.test ) val output = blurBitmap(picture, ctx) // 模糊 val outputUri = writeBitmapToFile(ctx, output) // 保存图片 makeStatusNotification("Output is $outputUri", ctx) Result.success() } catch (throwable: Throwable) { Result.failure() } } } 复制代码
- 如果需要实例化可用
-
在
ViewModel
当中通过WorkManager
触发Worker
,“WorkRequest`有三个子类,前两个PeriodicWorkRequest
周期性执行多次,直至取消OneTimeWorkRequest
仅仅执行一次WorkRequestHolder
包含WorkRequest
信息,不用于任务执行
@HiltViewModel class MainViewModel @Inject constructor( private val repository: WordRepository, private val workManager: WorkManager ) : ViewModel() { fun applyBlur() { workManager.enqueue(OneTimeWorkRequest.from(BlurWorker::class.java)) } } 复制代码
-
实例化
WorkManager
,这里因为我用了Hilt
,所以单独写一个Module
创建WorkManager
@InstallIn(SingletonComponent::class) @Module object WorkModule { @Provides @Singleton fun providesWorkManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context) } 复制代码
Worker 值传入
-
Worker
的继承类是通过Class
传入的,因此为了给Worker
传值可以借助WorkerParameters
,传入可以使用WorkRequest
的setInputData
方法@HiltViewModel class MainViewModel @Inject constructor( private val repository: WordRepository, private val workManager: WorkManager ) : ViewModel() { private var _imageUri = MutableLiveData<Uri>() val imageUri: LiveData<Uri?> get() = _imageUri fun applyBlur() { val workRequest = OneTimeWorkRequestBuilder<BlurWorker>() .setInputData(createInputDataForUri()) .build() workManager.enqueue(workRequest) } private fun createInputDataForUri(): Data { val builder = Data.Builder() imageUri.let { builder.putString(KEY_IMAGE_URI, imageUri.value.toString()) } return builder.build() } fun setImageUri(uri: Uri) { _imageUri.postValue(uri) } } 复制代码
- 接收值使用
WorkerParameters
的mInputData
熟悉
@HiltWorker class BlurWorker @AssistedInject constructor( @Assisted private val ctx: Context, @Assisted params: WorkerParameters ) : Worker(ctx, params) { override fun doWork(): Result { val resourceUri = inputData.getString(KEY_IMAGE_URI) makeStatusNotification("Blurring image", ctx) return try { val picture = BitmapFactory.decodeStream( ctx.contentResolver.openInputStream(Uri.parse(resourceUri))) val output = blurBitmap(picture, ctx) val outputUri = writeBitmapToFile(ctx, output) makeStatusNotification("Output is $outputUri", ctx) Result.success() } catch (throwable: Throwable) { Result.failure() } } } 复制代码
- 接收值使用
Work 链
-
对于复杂的相关工作,可以使用流畅自然的接口将各个工作任务串联起来,这样便可以控制哪些部分依序运行,哪些部分并行运行。使用用
WorkManager
的beginWith()
或beginUniqueWork()
,这会返回WorkContinuation
实例,WorkContinuation
通过then
添加OneTimeWorkRequest
依赖实例。案例中依次CleanupWorker
、BlurWorker
、SaveImageToFileWorker
// 清空文件夹 class CleanupWorker( ctx: Context, params: WorkerParameters ) : Worker(ctx, params) { override fun doWork(): Result { makeStatusNotification("Cleaning up old temporary files", applicationContext) return try { val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH) if (outputDirectory.exists()) { val entries = outputDirectory.listFiles() if (entries != null) { for (entry in entries) { val name = entry.name if (name.isNotEmpty() && name.endsWith(".png")) { val deleted = entry.delete() } } } } Result.success() } catch (exception: Exception) { Result.failure() } } } // 模糊图片 class BlurWorker( ctx: Context, params: WorkerParameters ) : Worker(ctx, params) { override fun doWork(): Result { val appContext = applicationContext val resourceUri = inputData.getString(KEY_IMAGE_URI) makeStatusNotification("Blurring image", appContext) return try { if (TextUtils.isEmpty(resourceUri)) { throw IllegalArgumentException("Invalid input uri") } val resolver = appContext.contentResolver val picture = BitmapFactory.decodeStream( resolver.openInputStream(Uri.parse(resourceUri))) val output = blurBitmap(picture, appContext) val outputUri = writeBitmapToFile(appContext, output) val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString()) Result.success(outputData) } catch (throwable: Throwable) { Result.failure() } } } // 存入图库(别忘了加动态权限) class SaveImageToFileWorker( ctx: Context, params: WorkerParameters ) : Worker(ctx, params) { private val Title = "Blurred Image" private val dateFormatter = SimpleDateFormat( "yyyy.MM.dd 'at' HH:mm:ss z", Locale.getDefault() ) override fun doWork(): Result { makeStatusNotification("Saving image", applicationContext) val resolver = applicationContext.contentResolver return try { val resourceUri = inputData.getString(KEY_IMAGE_URI) val bitmap = BitmapFactory.decodeStream( resolver.openInputStream(Uri.parse(resourceUri)) ) val imageUrl = MediaStore.Images.Media.insertImage( resolver, bitmap, Title, dateFormatter.format(Date()) ) if (!imageUrl.isNullOrEmpty()) { val output = workDataOf(KEY_IMAGE_URI to imageUrl) Result.success(output) } else { Result.failure() } } catch (exception: Exception) { Result.failure() } } } 复制代码
-
创建执行链
fun applyBlur() { var continuation = workManager .beginUniqueWork( IMAGE_MANIPULATION_WORK_NAME, ExistingWorkPolicy.REPLACE, OneTimeWorkRequest .from(CleanupWorker::class.java) ) // 反复模糊,这几我就循环三次 for (i in 0 until 3) { val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>() if (i == 0) { blurBuilder.setInputData(createInputDataForUri()) } continuation = continuation.then(blurBuilder.build()) } val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>() .addTag(TAG_OUTPUT) .build() continuation = continuation.then(save) continuation.enqueue() } 复制代码
相关知识点
知识点一:
WorkManager
根据不同的API使用底层作业来调度服务
知识点二: 工作状态监听
-
WorkManage
有getWorkInfosXxx
方法获取WorkInfo
,根据WorkInfo
判断工作状态// MainViewModel @HiltViewModel class MainViewModel @Inject constructor( private val repository: WordRepository, private val workManager: WorkManager ) : ViewModel() { private var _imageUri = MutableLiveData<Uri>() //传入 private var _imageUriOut = MutableLiveData<Uri>() //传出 internal var outputWorkInfos: LiveData<List<WorkInfo>> = MutableLiveData() init { outputWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT) } val imageUri: LiveData<Uri?> get() = _imageUri val imageUriOut: LiveData<Uri?> get() = _imageUriOut @SuppressLint("EnqueueWork") fun applyBlur() { var continuation = workManager .beginUniqueWork( IMAGE_MANIPULATION_WORK_NAME, ExistingWorkPolicy.REPLACE, OneTimeWorkRequest .from(CleanupWorker::class.java) ) // 反复模糊,这几我就循环三次 for (i in 0 until 3) { val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>() if (i == 0) { blurBuilder.setInputData(createInputDataForUri()) } continuation = continuation.then(blurBuilder.build()) } val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>() .addTag(TAG_OUTPUT) .build() continuation = continuation.then(save) continuation.enqueue() } private fun createInputDataForUri(): Data { val builder = Data.Builder() imageUri.let { builder.putString(KEY_IMAGE_URI, imageUri.value.toString()) } return builder.build() } fun setImageUri(uri: Uri) { _imageUri.postValue(uri) } fun setOutputUri(uri: Uri) { _imageUriOut.postValue(uri) } } // FlowStepTwoFragment.kt observe(viewModel.outputWorkInfos, ::workInfosUpdate) private fun workInfosUpdate(list: List<WorkInfo>) { if (list.isNullOrEmpty()) { return } val workInfo = list[0] if (workInfo.state.isFinished) { showWorkFinished() val outputImageUri = workInfo.outputData.getString(KEY_IMAGE_URI) if (!outputImageUri.isNullOrEmpty()) { viewModel.setOutputUri(Uri.parse(outputImageUri)) bindingTwo.btnCheck.visibility = View.VISIBLE } } else { showWorkInProgress() } } 复制代码