Jetpack系列(九) — WorkManager

Jetpack系列(九) — WorkManager

WorkManager简单介绍

初步印象

WorkManager是一个 API,可供您轻松调度那些即使在退出应用或重启设备后仍应运行的可延期异步任务

WorkManager 适用于可延期工作,即不需要立即运行但需要可靠运行的工作,即使退出应用或重启设备也不影响工作的执行。例如:

  • 向后端服务发送日志或分析数据
  • 定期将应用数据与服务器同步
  • 对图像应用过滤器并保存图像

基本概念

Worker:在后台执行的实际工作的代码,重写doWor()方法

WorkRequest:执行某些工作请求,为创建工作请求的一部分传入Worker

WorkManager: 管理工作请求并使其运行,分散系统资源负载的方式调度工作请求,同时遵守指定的约束

WorkManager基本使用

基本使用

  1. 这里借用官网给图片做模糊效果的例子练习一下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()
            }
        }
    }
    复制代码
  2. 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))
        }
    }
    复制代码
  3. 实例化WorkManager,这里因为我用了Hilt,所以单独写一个Module创建WorkManager

    @InstallIn(SingletonComponent::class)
    @Module
    object WorkModule {
    
        @Provides
        @Singleton
        fun providesWorkManager(@ApplicationContext context: Context): WorkManager =
            WorkManager.getInstance(context)
    }
    复制代码

Worker 值传入

  1. Worker的继承类是通过Class 传入的,因此为了给Worker 传值可以借助WorkerParameters,传入可以使用WorkRequestsetInputData方法

    @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)
        }
    
    }
    复制代码
    1. 接收值使用WorkerParametersmInputData熟悉
    @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 链

  1. 对于复杂的相关工作,可以使用流畅自然的接口将各个工作任务串联起来,这样便可以控制哪些部分依序运行,哪些部分并行运行。使用用 WorkManagerbeginWith()beginUniqueWork(),这会返回 WorkContinuation 实例, WorkContinuation 通过then添加 OneTimeWorkRequest 依赖实例。案例中依次CleanupWorkerBlurWorkerSaveImageToFileWorker

    // 清空文件夹
    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()
            }
        }
    }
    复制代码
  2. 创建执行链

    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使用底层作业来调度服务

overview-criteria.png

知识点二: 工作状态监听

  • WorkManagegetWorkInfosXxx 方法获取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()
        }
    }
    复制代码

相关链接

Jetpack系列(一) — Navigation

Jetpack系列(二) — Lifecycle

Jetpack系列(三) — LiveData

Jetpack系列(四) — ViewModel

Jetpack系列(五) — Room

Jetpack系列(六) — Paging3

Jetpack系列(七) — Hilt

Jetpack系列(八) — Data Bidnding && View Binding

参考资料

官网

codelabs

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享