美文网首页
Android Jetpack 之 WorkManger---入

Android Jetpack 之 WorkManger---入

作者: 花前月下的细说 | 来源:发表于2019-03-26 18:18 被阅读0次

    前言

    最近在学习 Google 推出的框架Jetpack,虽然目前网上的资料已经很多了,但为了加深印象和边动手练习跟着学习,所以站在了下面的巨人的肩膀上,并根据当前最新的API 和编写实际Demo,记录下一些学习笔记,大部分是参考巨人们的,整理和休整,加入自己理解和更新吧,学习领略了Android Jetpack组件的一点魅力

    目前学习笔记系列为:

    日常鸣谢巨人

    Google官方博文

    CSDN 博主Alex@W

    正题

    WorkManger 初识

    WorkManger是Android Jetpack提供执行后台任务管理的组件

    适用于需要保证系统即使应用程序退出也会运行的任务

    WorkManager API可以轻松指定可延迟的异步任务以及何时运行它们,这些API允许您创建任务并将其交给WorkManager立即运行或在适当的时间运行

    WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行任务。如果WorkManager在应用程序运行时执行您的任务之一,WorkManager可以在您应用程序进程的新线程中运行您的任务

    如果您的应用程序未运行,WorkManager会选择一种合适的方式来安排后台任务 - 具体取决于设备API级别和包含的依赖项,WorkManager可能会使用 JobScheduler,Firebase JobDispatcher或AlarmManager

    WorkManager 相关

    • Worker

    指定需要执行的任务,可以继承抽象的Worker类,在实现的 doWork 方法中编写执行的逻辑

    • WorkRequest

    执行一项单一任务

    1. WorkRequest对象必须指定Work执行的任务
    2. WorkRequest都有一个自动生成的唯一ID; 您可以使用ID执行取消排队任务或获取任务状态等操作
    3. WorkRequest是一个抽象的类;系统默认实现子类 OneTimeWorkRequest或PeriodicWorkRequest(循环执行)
    4. WorkRequest.Builder创建WorkRequest对象;相应的子类:OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder
    5. Constraints:指定对任务运行时间的限制(任务约束);使用Constraints.Builder创建Constraints对象 ,并传递给WorkRequest.Builder
    • WorkManager

    排队和管理工作请求、将WorkRequest 对象传递WorkManager的任务队列

    如果未指定任何约束, WorkManager立即运行任务

    • WorkStatus

    包含有关特定任务的信息;可以使用LiveData保存 WorkStatus对象,监听任务状态;如LiveData<WorkStatus>

    WorkManager 入门使用

    • 添加依赖
        def work_version = "1.0.0-alpha11"
    
        implementation "android.arch.work:work-runtime:$work_version"
    
        // optional - Firebase JobDispatcher support
        implementation "android.arch.work:work-firebase:$work_version"
    
        // optional - Test helpers
        androidTestImplementation "android.arch.work:work-testing:$work_version"
    

    但这里注意,如果项目做搭配了其他 jetpack 组件的依赖使用

    可能会在运行时,报错

    Program type already present: com.google.common.util.concurrent.ListenableFuture
    

    是因为重复依赖包冲突了,在导入上述依赖时,可忽略掉上面的 ListenableFuture

        def work_version = "1.0.0-alpha11"
    
        implementation("android.arch.work:work-runtime:$work_version"){
            exclude group:'com.google.guava', module:'listenablefuture'
        }
    
        // optional - Firebase JobDispatcher support
        implementation ("android.arch.work:work-firebase:$work_version"){
            exclude group:'com.google.guava', module:'listenablefuture'
        }
    
        // optional - Test helpers
        androidTestImplementation "android.arch.work:work-testing:$work_version"
    
    • 定义Worker类,并重写其 doWork()方法
        class MyWorkA(context: Context, workerParameters: WorkerParameters) : Worker(context,     workerParameters) {
        
            override fun doWork(): Result {
                Log.i(TAG,"doWork A !")
        
                Thread.sleep(1000)
                // 模拟任务执行成功
                return Result.SUCCESS
            }
        }
    
    • 使用 OneTimeWorkRequest.Builder 创建对象Worker,然后将任务排入WorkManager队列
        val workRequestA = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
        WorkManager.getInstance().enqueue(workRequestA)
    
    • 执行结果
        I MyWorkA : doWork A !
    
    • WorkStatus
    1. 调用WorkManger的getStatusByIdLiveData()返回任务执行的状态LiveData<WorkStatus>
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA.id)
                .observe(this, Observer {
                    Log.i(TAG, it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i(TAG, "Finish !")
                    }
                })
    
    1. 添加Observe()执行观察回调
        I MyWorkActivity: ENQUEUED
        I MyWorkA: doWork A !
        I MyWorkActivity: RUNNING
        I MyWorkActivity: SUCCEEDED
        I MyWorkActivity: Finish !
    

    可以看出,回调了Work的三个运行状态ENQUEUED 、RUNNING、SUCCESSESD

    但实际上,源码中定义的状态如下

        /**
         * The current state of a unit of work.
         */
        public enum State {
    
            /**
             * The state for work that is enqueued (hasn't completed and isn't running)
             */
            ENQUEUED,
    
            /**
             * The state for work that is currently being executed
             */
            RUNNING,
    
            /**
             * The state for work that has completed successfully
             */
            SUCCEEDED,
    
            /**
             * The state for work that has completed in a failure state
             */
            FAILED,
    
            /**
             * The state for work that is currently blocked because its prerequisites haven't finished
             * successfully
             */
            BLOCKED,
    
            /**
             * The state for work that has been cancelled and will not execute
             */
            CANCELLED;
    
            /**
             * Returns {@code true} if this State is considered finished.
             *
             * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and
             * {@link #CANCELLED} states
             */
            public boolean isFinished() {
                return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
            }
        }
    

    清晰一目了然...

    • 任务约束
    1. 使用Constraints.Builder()创建并配置Constraints对象
        val myConstrains = Constraints.Builder()
                .setRequiresCharging(true)
                .setRequiresDeviceIdle(true)
                .build()
    
    1. 可以指定任务运行时间的约束,例如,可以指定该任务仅在设备空闲并连接到电源时运行,具体源码中目前支持的条件为
    • setRequiredNetworkType:网络连接设置
    • setRequiresBatteryNotLow:是否为低电量时运行 默认false
    • setRequiresCharging:是否要插入设备(接入电源),默认false
    • setRequiresDeviceIdle:设备是否为空闲,默认false
    • setRequiresStorageNotLow:设备可用存储是否不低于临界阈值
    1. 配置好Constraints后,利用它创建Work对象,并安排进队列
        val compressionWork = OneTimeWorkRequest.Builder(MyWorkA::class.java)
                .setConstraints(myConstrains)
                .build()
                
        WorkManager.getInstance().enqueue(compressionWork)
    
    • 取消任务

    根据ID取消任务,从WorkRequest()获取Worker的ID

        WorkManager.getInstance().cancelWorkById(compressionWork.id)
    
    • 添加TAG
    1. 通过为WorkRequest对象分配标记字符串来对任务进行分组
        val workRequestWithTag = OneTimeWorkRequest.Builder(MyWorkA::class.java)
                            .addTag("MyTAG")
                            .build()
    
    1. WorkManager.getWorkInfosByTag() 返回WorkStatus具有该标记的所有任务的所有任务的列表
        val list = WorkManager.getInstance().getWorkInfosByTag("MyTAG")
    
    1. WorkManager.cancelAllWorkByTag() 取消具有特定标记的所有任务
        WorkManager.getInstance().cancelAllWorkByTag("MyTAG")
    
    • 重复的任务

    创建单次任务使用 OneTimeWorkRequest

    创建重复任务使用 PeriodicWorkRequest.Builder创建PeriodicWorkRequest对象,然后将任务添加到WorkManager的任务队列

        val repeatRequest = PeriodicWorkRequest.Builder(MyWorkA::class.java,10,TimeUnit.SECONDS).build()
    

    然后其他的设置条件,Tag 都喝单次任务一致

    Workmanger的高级用法

    • 链式任务
    1. WorkManager允许您创建和排队指定多个任务的工作序列,以及它们应运行的顺序
    2. 使用该WorkManager.beginWith() 方法创建一个序列 ,并传递第一个OneTimeWorkRequest对象; 该方法返回一个WorkContinuation对象
    3. 使用 WorkContinuation.then()添加剩余的对象
    4. 最后调用WorkContinuation.enqueue()将整个序列排入队列
    5. 如果任何任务返回 Worker.Result.FAILURE,则整个序列结束
        val workRequestA = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
        val workRequestB = OneTimeWorkRequest.Builder(MyWorkB::class.java).build()
        val workRequestC = OneTimeWorkRequest.Builder(MyWorkC::class.java).build()
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA.id)
                .observe(this, Observer {
                    Log.i("MyWorkA", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkA", "Finish !")
                    }
                })
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB.id)
                .observe(this, Observer {
                    Log.i("MyWorkB", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkB", "Finish !")
                    }
                })
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestC.id)
                .observe(this, Observer {
                    Log.i("MyWorkC", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkC", "Finish !")
                    }
                })
        WorkManager.getInstance()
                .beginWith(workRequestA)
                .then(workRequestB)
                .then(workRequestC)
                .enqueue()
    

    执行结果 任务会按照设置的顺序依次执行A、B、C

        MyWorkA: doWork A !
        MyWorkA: ENQUEUED
        MyWorkB: BLOCKED
        MyWorkC: BLOCKED
        MyWorkA: RUNNING
        MyWorkA: SUCCEEDED
        MyWorkA: Finish !
        MyWorkB: ENQUEUED
        MyWorkB: doWork B !
        MyWorkB: RUNNING
        MyWorkB: SUCCEEDED
        MyWorkB: Finish !
        MyWorkC: doWork C !
        MyWorkC: ENQUEUED
        MyWorkC: RUNNING
        MyWorkC: SUCCEEDED
        MyWorkC: Finish !
    

    一个任务执行时,从Log 已经可以清晰看到其他顺序任务的状态...

    WorkManger在执行过程中,当遇到一个WOrk不成功,则会停止执行

    修改WorkB返回FAILURE状态

        override fun doWork(): Result {
            Log.i(TAG,"doWork B !")
            Thread.sleep(1000)
            // 模拟任务执行失败
            return Result.FAILURE
        }
    

    执行结果

        MyWorkB: BLOCKED
        MyWorkC: BLOCKED
        MyWorkA: doWork A !
        MyWorkA: ENQUEUED
        MyWorkA: RUNNING
        MyWorkA: SUCCEEDED
        MyWorkA: Finish !
        MyWorkB: ENQUEUED
        MyWorkB: ENQUEUED
        MyWorkB: doWork B !
        MyWorkB: RUNNING
        MyWorkB: FAILED
        MyWorkB: Finish !
        MyWorkC: FAILED
        MyWorkC: Finish !
    

    代码执行到WorkB就已经结束了,WorkC并未执行

    但,细心点可以发现

    虽然 workC 未执行,也就是说 C 的 doWork 方法未被执行,但是这里最后判断 workC 的 isFinished ,也是通过的,所以才会出现 MyWorkC: Finish ! 打印 . 所以要注意 isFinished 方法只是判断这个work 是否已经结束了,并不关心执行的结果

    • beginWith() 和 then()传递请求数组 List<OneTimeWorkRequest>, 同一方法传递的对象将会并行执行
        val requestList = arrayListOf<OneTimeWorkRequest>(workRequestA, workRequestB)
        WorkManager.getInstance()
                .beginWith(requestList)
                .then(workRequestC)
                .enqueue()
    

    直观点,直接看Log ...

        MyWorkA: ENQUEUED
        MyWorkB: ENQUEUED
        MyWorkA: doWork A !
        MyWorkC: BLOCKED
        MyWorkA: RUNNING
        MyWorkC: BLOCKED
        MyWorkB: doWork B !
        MyWorkB: RUNNING
        MyWorkA: SUCCEEDED
        MyWorkA: Finish !
        MyWorkC: doWork C !
        MyWorkB: SUCCEEDED
        MyWorkB: Finish !
        MyWorkC: ENQUEUED
        MyWorkC: RUNNING
        MyWorkC: SUCCEEDED
        MyWorkC: Finish !
    
    • WorkContinuation.combine
    1. 使用 WorkContinuation.combine() 方法连接多个链来创建更复杂的序列
    graph TB
    A1-->B1
    A2-->B2
    
    B1-->C
    B2-->C
    

    预测执行流程应该是

        开始
        执行A1/A2、其余为阻塞状态
        A1/A2执行结束后执行B1/B2
        最后执行C
    

    编写代码

        val workRequestA1 = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
        val workRequestA2 = OneTimeWorkRequest.Builder(MyWorkA2::class.java).build()
        val workRequestB1 = OneTimeWorkRequest.Builder(MyWorkB::class.java).build()
        val workRequestB2 = OneTimeWorkRequest.Builder(MyWorkB2::class.java).build()
        val workRequestC = OneTimeWorkRequest.Builder(MyWorkC::class.java).build()
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA1.id)
                .observe(this, Observer {
                    Log.i("MyWorkA1", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkA1", "Finish !")
                    }
                })
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA2.id)
                .observe(this, Observer {
                    Log.i("MyWorkA2", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkA2", "Finish !")
                    }
                })
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB1.id)
                .observe(this, Observer {
                    Log.i("MyWorkB1", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkB1", "Finish !")
                    }
                })
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB2.id)
                .observe(this, Observer {
                    Log.i("MyWorkB2", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkB2", "Finish !")
                    }
                })
        WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestC.id)
                .observe(this, Observer {
                    Log.i("MyWorkC", it?.state?.name)
                    if (it?.state!!.isFinished) {
                        Log.i("MyWorkC", "Finish !")
                    }
                })
    
        val requestA1_B1 = WorkManager.getInstance().beginWith(workRequestA1)
                .then(workRequestB1)
        val requestA2_B2 = WorkManager.getInstance().beginWith(workRequestA2)
                .then(workRequestB2)
        WorkContinuation.combine(requestA1_B1, requestA2_B2)
                .then(workRequestC)
                .enqueue()
    

    实际运行结果,一目了然...

        MyWorkB2: BLOCKED
        MyWorkC: BLOCKED
        MyWorkA1: doWork A1 !
        MyWorkA1: ENQUEUED
        MyWorkA2: doWork A2 !
        MyWorkA2: ENQUEUED
        MyWorkB1: BLOCKED
        MyWorkA1: RUNNING
        MyWorkA2: RUNNING
        MyWorkA1: SUCCEEDED
        MyWorkA1: Finish !
        MyWorkB1: ENQUEUED
        MyWorkA1: SUCCEEDED
        MyWorkA1: Finish !
        MyWorkA2: SUCCEEDED
        MyWorkA2: Finish !
        MyWorkB1: ENQUEUED
        MyWorkB2: ENQUEUED
        MyWorkB1: doWork B1 !
        MyWorkB2: doWork B2 !
        MyWorkB1: RUNNING
        MyWorkB2: RUNNING
        MyWorkB1: SUCCEEDED
        MyWorkB1: Finish !
        MyWorkB1: SUCCEEDED
        MyWorkB1: Finish !
        MyWorkB2: SUCCEEDED
        MyWorkB2: Finish !
        MyWorkC: ENQUEUED
        MyWorkC: doWork C !
        MyWorkC: RUNNING
        MyWorkC: SUCCEEDED
        MyWorkC: Finish !
    

    同上,中途任意一环的 work 执行失败,返回FAILURE,后续的emm.....

    • 特定的工作方式
    1. 通过调用 beginUniqueWork() 来创建唯一的工作序列,被标记的Work在执行过程中只能出现一次
        WorkManager.getInstance().beginUniqueWork("MyUniqueWork", 
                            ExistingWorkPolicy.APPEND, workRequestA1).enqueue()
    
    1. 每个独特的工作序列都有一个名称
    2. 同一时间只允许执行一个使用该名称工作序列
    3. beginUniqueWork 的参数二 ExistingWorkPolicy 的策略有:
    /**
     * An enum that determines what to do with existing {@link OneTimeWorkRequest}s with the same unique
     * name in case of a collision.
     */
    public enum ExistingWorkPolicy {
    
        /**
         * If there is existing pending work with the same unique name, cancel and delete it.  Then,
         * insert the newly-specified work.
         */
        REPLACE,
    
        /**
         * If there is existing pending work with the same unique name, do nothing.  Otherwise, insert
         * the newly-specified work.
         */
        KEEP,
    
        /**
         * If there is existing pending work with the same unique name, append the newly-specified work
         * as a child of all the leaves of that work sequence.  Otherwise, insert the newly-specified
         * work as the start of a new sequence.
         */
        APPEND
    }
    

    注释写的很详细了

    ExistingWorkPolicy.REPLACE:取消现有序列并将其替换为新序列

    ExistingWorkPolicy.KEEP:保留现有序列并忽略您的新请求

    ExistingWorkPolicy.APPEND:将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务

    • 输入参数和返回值
    1. 将参数传递给任务并让任务返回结果。传递和返回的值是键值对
    2. 使用 Data.Builder创建 Data 对象,保存参数的键值对
    3. 在创建WorkQuest之前调用WorkRequest.Builder.setInputData()传递Data的实例
    4. 调用 Worker.getInputData()获取参数值
    5. 调用Worker.setOutputData()设置返回数据

    修改WorkerA

        const val MIN_NUMBER = "minNumber"
        const val MAX_NUMBER = "maxNumber"
        const val RESULT_CODE = "Result"
         
        class WorkerA(context: Context, workerParameters: WorkerParameters) : Worker(context,     workerParameters) {
         
            private var minNumber = 0
            private var maxNumber = 0
         
            override fun doWork(): Result {
                // 使用InputData获取传入的参数
                minNumber = inputData.getInt(MIN_NUMBER, 0) 
                maxNumber = inputData.getInt(MAX_NUMBER, 0)
         
                val result = maxNumber - minNumber
                 // 创建返回的数据Data
                val outData: Data = Data.Builder().putAll(mapOf(RESULT_CODE to result)).build()    
                // 设置返回的数据Data
                outputData = outData
         
                return Result.SUCCESS
            }
        }
    

    创建Worker并传递参数

        val map = mapOf(MIN_NUMBER to 5, MAX_NUMBER to 15)   
        // 创建输入参数Data
        val data = Data.Builder().putAll(map).build()   
         
        // 传递参数
        val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
                .setInputData(data)   
                .build()
    

    观察任务WorkStatus获取返回结果

    
        WorkManager.getInstance().getStatusByIdLiveData(workRequest.id)
            .observe(this, Observer {
                if (it?.state!!.isFinished) {
                    // 获取执行结果
                    Log.e("WorkerA", "${it.outputData.getInt(RESULT_CODE, 0)}")   
                }
            })
    

    相关文章

      网友评论

          本文标题:Android Jetpack 之 WorkManger---入

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