08 Jetpack-WorkManger

作者: 凤邪摩羯 | 来源:发表于2021-12-07 09:17 被阅读0次

    目录

    一、介绍

    友情提示
    官方文档:WorkManager
    谷歌实验室:官方教程
    官方案例:android-workmanager
    以及强力安利:
    WorkManger介绍视频:中文官方介绍视频(主要是小姐姐好看~)

    1. 定义

    通过一开始粗略的介绍,我们已经了解到,WorkManager是用来执行后台任务的,正如官方介绍:

    WorkManager, a compatible, flexible and simple library for deferrable background work.
    WorkManger是一个可兼容、灵活和简单的延迟后台任务。

    2. 选择WorkManager的理由

    Android中处理后台任务的选择挺多的,比如ServiceDownloadManagerAlarmManagerJobScheduler等,那么选择WorkManager的理由是什么呢?

    1. 版本兼容性强,向后兼容至API 14。
    2. 可以指定约束条件,比如可以选择必须在有网络的条件下执行。
    3. 可定时执行也可单次执行。
    4. 监听和管理任务状态。
    5. 多个任务可使用任务链。
    6. 保证任务执行,如当前执行条件不满足或者App进程被杀死,它会等到下次条件满足或者App进程打开后执行。
    7. 支持省电模式。

    3. 多线程任务如何选择?

    后台任务会消耗设备的系统资源,如若处理不当,可能会造成设备电量的急剧消耗,给用户带来糟糕的体验。所以,选择正确的后台处理方式是每个开发者应当注意的,如下是官方给的选择方式:

    image

    图片来自:官方文档
    关于一些后台任务的知识,我推荐你阅读:[译] 从Service到WorkManager,很好的一篇文章。

    二、实战

    本次的实战来自于我上面的介绍的官方例子,最终我将它添加进我的Demo里面:

    image

    如图所见,我们要做的就是选取一张图片,将图片做模糊处理,之后显示在我们的头像上。

    第一步 添加依赖

    ext.workVersion = "2.0.1"
    dependencies {
        // ...省略
    
        implementation "androidx.work:work-runtime-ktx:$rootProject.workVersion"
    }
    
    

    第二步 自定义Worker

    构建Worker之前,我们有必要了解一下WorkManger中重要的几个类:

    作用
    Worker 需要继承Worker,并复写doWork()方法,在doWork()方法中放入你需要在后台执行的代码。
    WorkRequest 指后台工作的请求,你可以在后台工作的请求中添加约束条件
    WorkManager 真正让Worker在后台执行的类

    除了这几个重要的类,我们仍需了解WorkManger的执行流程,以便于我们能够更好的使用:

    image

    图片来自:谷歌工程师的博客
    主要分为三步:

    1. WorkRequest生成以后,Internal TaskExecutor将它存入WorkManger的数据库中,这也是为什么即使在程序退出之后,WorkManger也能保证后台任务在下次启动后条件满足的情况下执行。
    2. 当约束条件满足的情况下,Internal TaskExecutor告诉WorkFactory生成Worker
    3. 后台任务Worker执行。

    下面开始我们的构建Worker,为了生成一张模糊图片,我们需要:清除之前的缓存路径、图片模糊的处理和图片的生成。我们可以将这三个步骤分为三个后台任务,三个后台任务又分别涉及到无变量情况、往外传参和读取参数这三种情况:

    通常情况

    /**
     * 清理临时文件的Worker
     */
    class CleanUpWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
        private val TAG by lazy {
            this::class.java.simpleName
        }
    
        override fun doWork(): Result {
            // ... 省略
    
            return try {
                // 删除逻辑
                // ...代码省略
                // 成功时返回
                Result.success()
            } catch (exception: Exception) {
                // 失败时返回
                Result.failure()
            }
        }
    }
    
    

    输出参数

    /**
     * 模糊处理的Worker
     */
    class BlurWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    
        override fun doWork(): Result {
            //...
            return try {
                // 图片处理逻辑
                // 图片处理逻辑省略...
    
                // 将路径输出
                val outPutData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
                makeStatusNotification("Output is $outputUri", context)
                Result.success(outPutData)
            }catch (throwable: Throwable){
                Result.failure()
            }
        }
    }
    
    

    读取参数

    /**
     * 存储照片的Worker
     */
    class SaveImageToFileWorker(ctx:Context,parameters: WorkerParameters):Worker(ctx,parameters) {
        //...
    
        override fun doWork(): Result {
            //...
            return try {
                // 获取从外部传入的参数
                val resourceUri = inputData.getString(KEY_IMAGE_URI)
                //... 存储逻辑
                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()
            }
        }
    }
    
    

    第三步 创建WorkManger

    这一步还是挺简单的,MeModel中单例获取:

    class MeModel(val userRepository: UserRepository) : ViewModel() {
        //...
        private val workManager = WorkManager.getInstance()
        // ...
    }
    
    

    第四步 构建WorkRequest

    WorkRequest可以分为两类:

    名称 作用
    PeriodicWorkRequest 多次、定时执行的任务请求,不支持任务链
    OneTimeWorkRequest 只执行一次的任务请求,支持任务链
    1. 执行一个任务

    我们以OneTimeWorkRequest为例,如果我们只有一个任务请求,这样写就行:

            val request = OneTimeWorkRequest.from(CleanUpWorker::class.java)
            workManager.enqueue(request)
    
    
    2. 执行多个任务

    但是,这样写显然不适合我们当前的业务需求,因为我们有三个Worker,并且三个Worker有先后顺序,因此我们可以使用任务链:

            // 多任务按顺序执行
            workManager.beginWith(
                mutableListOf(
                    OneTimeWorkRequest.from(CleanUpWorker::class.java)
                ))
                .then(OneTimeWorkRequestBuilder<BlurWorker>().setInputData(createInputDataForUri()).build())
                .then(OneTimeWorkRequestBuilder<SaveImageToFileWorker>().build())
                .enqueue()
    
    

    等等,假设我多次点击图片更换头像,提交多次请求,由于网络等原因(虽然我们的Demo没有网络数据请求部分),最后返回的很有可能不是我们最后一次请求的图片,这显然是糟糕的,不过,WorkManger能够满足你的需求,保证任务的唯一性:

            // 多任务按顺序执行
            workManager.beginUniqueWork(
                IMAGE_MANIPULATION_WORK_NAME, // 任务名称
                ExistingWorkPolicy.REPLACE, // 任务相同的执行策略 分为REPLACE,KEEP,APPEND
                mutableListOf(
                    OneTimeWorkRequest.from(CleanUpWorker::class.java) 
                ))
                .then(OneTimeWorkRequestBuilder<BlurWorker>().setInputData(createInputDataForUri()).build())
                .then(OneTimeWorkRequestBuilder<SaveImageToFileWorker>().build())
                .enqueue()
    
    

    无顺序多任务
    这里有必要提一下,如果并行执行没有顺序的多个任务,无论是beginUniqueWork还是beginWith方法都可以接受一个List<OneTimeWorkRequest>

    3. 使用约束

    假设我们需要将生成的图片上传到服务端,并且需要将图片存储到本地,显然,我们需要设备网络条件良好并且有存储空间,这时候,我们可以给WorkRequest指明约束条件:

            // 构建约束条件
            val constraints = Constraints.Builder()
                .setRequiresBatteryNotLow(true) // 非电池低电量
                .setRequiredNetworkType(NetworkType.CONNECTED) // 网络连接的情况
                .setRequiresStorageNotLow(true) // 存储空间足
                .build()
    
            // 储存照片
            val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
                .setConstraints(constraints)
                .addTag(TAG_OUTPUT)
                .build()
            continuation = continuation.then(save)
    
    

    可以指明的约束条件有:电池电量充电网络条件存储延迟等,具体的可以使用的时候查看接口。

    以下则是我们Demo中的具体使用:

    class MeModel(val userRepository: UserRepository) : ViewModel() {
        //... 
        private val workManager = WorkManager.getInstance()
        val user = userRepository.findUserById(AppPrefsUtils.getLong(BaseConstant.SP_USER_ID))
    
        internal fun applyBlur(blurLevel: Int) {
           //... 创建任务链
    
            var continuation = workManager
                .beginUniqueWork(
                    IMAGE_MANIPULATION_WORK_NAME,
                    ExistingWorkPolicy.REPLACE,
                    OneTimeWorkRequest.from(CleanUpWorker::class.java)
                )
    
            for (i in 0 until blurLevel) {
                val builder = OneTimeWorkRequestBuilder<BlurWorker>()
                if (i == 0) {
                    builder.setInputData(createInputDataForUri())
                }
                continuation = continuation.then(builder.build())
            }
    
            // 构建约束条件
            val constraints = Constraints.Builder()
                .setRequiresBatteryNotLow(true) // 非电池低电量
                .setRequiredNetworkType(NetworkType.CONNECTED) // 网络连接的情况
                .setRequiresStorageNotLow(true) // 存储空间足
                .build()
    
            // 储存照片
            val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
                .setConstraints(constraints)
                .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.toString())
            }
            return builder.build()
        }
    
        //... 省略
    }
    
    

    第四步 取消任务

    如果想取消所有的任务workManager.cancelAllWork(),当然如果想取消我们上面执行的唯一任务,需要我们上面的唯一任务名:

    class MeModel(val userRepository: UserRepository) : ViewModel() {
        fun cancelWork() {
            workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
        }
    }
    
    

    第五步 观察任务状态

    任务状态的变化过程:

    image

    图片来自于:How to use WorkManager with RxJava
    其中,SUCCEEDEDFAILEDCANCELLED都属于任务已经完成。观察任务状态需要使用到LiveData

    class MeModel(val userRepository: UserRepository) : ViewModel() {
        //... 省略
        private val workManager = WorkManager.getInstance()
        val user = userRepository.findUserById(AppPrefsUtils.getLong(BaseConstant.SP_USER_ID))
    
        init {
            outPutWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
        }
    
        // ...省略
    }
    
    

    当图片处理的时候,程序弹出加载框,图片处理完成,程序会将图片路径保存到User里的headImage并存储到数据库中,任务状态观测参见MeFragment中的onSubscribeUi方法:

    class MeFragment : Fragment() {
        private val TAG by lazy { MeFragment::class.java.simpleName }
        // 选择图片的标识
        private val REQUEST_CODE_IMAGE = 100
        // 加载框
        private val sweetAlertDialog: SweetAlertDialog by lazy {
            SweetAlertDialog(requireContext(), SweetAlertDialog.PROGRESS_TYPE)
                .setTitleText("头像")
                .setContentText("更新中...")
                /*
                .setCancelButton("取消") {
                    model.cancelWork()
                    sweetAlertDialog.dismiss()
                }*/
        }
    
        // MeModel懒加载
        private val model: MeModel by viewModels {
            CustomViewModelProvider.providerMeModel(requireContext())
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Data Binding
            val binding: FragmentMeBinding = FragmentMeBinding.inflate(inflater, container, false)
            initListener(binding)
            onSubscribeUi(binding)
            return binding.root
        }
    
        /**
         * 初始化监听器
         */
        private fun initListener(binding: FragmentMeBinding) {
            binding.ivHead.setOnClickListener {
                // 选择处理的图片
                val chooseIntent = Intent(
                    Intent.ACTION_PICK,
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                )
                startActivityForResult(chooseIntent, REQUEST_CODE_IMAGE)
            }
        }
    
        /**
         * Binding绑定
         */
        private fun onSubscribeUi(binding: FragmentMeBinding) {
            model.user.observe(this, Observer {
                binding.user = it
            })
    
            // 任务状态的观测
            model.outPutWorkInfos.observe(this, Observer {
                if (it.isNullOrEmpty())
                    return@Observer
    
                val state = it[0]
                if (state.state.isFinished) {
                    // 更新头像
                    val outputImageUri = state.outputData.getString(KEY_IMAGE_URI)
                    if (!outputImageUri.isNullOrEmpty()) {
                        model.setOutputUri(outputImageUri)
                    }
                    sweetAlertDialog.dismiss()
                }
            })
        }
    
        /**
         * 图片选择完成的回调
         */
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            if (resultCode == Activity.RESULT_OK) {
                when (requestCode) {
                    REQUEST_CODE_IMAGE -> data?.let { handleImageRequestResult(data) }
                    else -> Log.d(TAG, "Unknown request code.")
                }
            } else {
                Log.e(TAG, String.format("Unexpected Result code %s", resultCode))
            }
        }
    
        /**
         * 图片选择完成的处理
         */
        private fun handleImageRequestResult(intent: Intent) {
            // If clipdata is available, we use it, otherwise we use data
            val imageUri: Uri? = intent.clipData?.let {
                it.getItemAt(0).uri
            } ?: intent.data
    
            if (imageUri == null) {
                Log.e(TAG, "Invalid input image Uri.")
                return
            }
    
            sweetAlertDialog.show()
            // 图片模糊处理
            model.setImageUri(imageUri.toString())
            model.applyBlur(3)
        }
    }
    
    

    写完以后,动图的效果就会出现了。

    三、更多

    选择适合自己的Worker

    谷歌提供了四种Worker给我们使用,分别为:自动运行在后台线程的Worker、结合协程的CoroutineWorker、结合RxJava2RxWorker和以上三个类的基类的ListenableWorker

    由于本文使用的Kotlin,故打算简单的介绍CoroutineWorker,其他的可以自行探索。

    我们使用ShoeWorker来从文件中读取鞋子的数据并完成数据库的插入工作,使用方式基本与Worker一致:

    class ShoeWorker(
        context: Context,
        workerParams: WorkerParameters
    ) : CoroutineWorker(context, workerParams) {
    
        private val TAG by lazy {
            ShoeWorker::class.java.simpleName
        }
    
        // 指定Dispatchers
        override val coroutineContext: CoroutineDispatcher
            get() = Dispatchers.IO
    
        override suspend fun doWork(): Result = coroutineScope {
            try {
                applicationContext.assets.open("shoes.json").use {
                    JsonReader(it.reader()).use {
                        val shoeType = object : TypeToken<List<Shoe>>() {}.type
                        val shoeList: List<Shoe> = Gson().fromJson(it, shoeType)
    
                        val shoeDao = RepositoryProvider.providerShoeRepository(applicationContext)
                        shoeDao.insertShoes(shoeList)
                        for (i in 0..2) {
                            for (shoe in shoeList) {
                                shoe.id += shoeList.size
                            }
                            shoeDao.insertShoes(shoeList)
                        }
                        Result.success()
                    }
    
                }
            } catch (ex: Exception) {
                Log.e(TAG, "Error seeding database", ex)
                Result.failure()
            }
        }
    }
    
    

    四、总结

    image

    可以发现,大部分的后台任务处理,WorkManager都可以胜任,这也是我们需要学习WorkManger的原因。本次WorkManger学习完毕,本人水平有限,难免有误,欢迎指正。
    Over~

    相关文章

      网友评论

        本文标题:08 Jetpack-WorkManger

        本文链接:https://www.haomeiwen.com/subject/ivwbyltx.html