亲宝软件园·资讯

展开

Android Jetpack WorkManager

Android技术栈 人气:0

前言

WorkManager是Jetpack很重要的一个组件; 本篇我们就先来讲讲它是如何使用的,在讲解之前我们先了解关于后台处理的一些痛点

后台处理指南

我们知道每个 Android 应用都有一个主线程,它负责处理界面(包括测量和绘制视图)、协调用户互动以及接收生命周期事件; 如果有太多工作在主线程中进行,则应用可能会挂起或运行速度变慢,从而导致用户体验不佳。任何长时间运行的计算和操作(例如解码位图、访问磁盘或执行网络请求)都应在单独的后台线程上完成

一般来说,任何所需时间超过几毫秒的任务都应该分派到后台线程; 在用户与应用积极互动时,可能需要执行几项这样的任务;即使在用户没有积极使用应用时,应用可能也需要运行一些任务(例如,定期与后端服务器同步或定期从应用内提取新内容)

后台处理面临的挑战

后台任务会使用设备的有限资源,例如 RAM 和电池电量; 如果处理不当,可能会导致用户体验不佳;为了最大限度地延长电池续航时间并强制推行良好的应用行为,Android 会在应用(或前台服务通知)对用户不可见时,限制后台工作;为此Google在不同平台上逐步的改进

Android 6.0(API 级别 23)引入了低电耗模式和应用待机模式

低电耗模式会在未插接设备的电源,在屏幕关闭的情况下,让设备在一段时间内保持不活动状态,那么设备就会进入低电耗模式; 在低电耗模式下,系统会尝试通过限制应用访问占用大量网络和 CPU 资源的服务来节省电量。它还会阻止应用访问网络,并延迟其作业、同步和标准闹钟

系统会定期退出低电耗模式一小段时间,让应用完成其延迟的活动; 在此维护期内,系统会运行所有待处理的同步、作业和闹钟,并允许应用访问网络。在每个维护期结束时,系统会再次进入低电耗模式,暂停网络访问并推迟作业、同步和闹钟

随着时间的推移,系统安排维护期的次数越来越少,这有助于在设备未连接至充电器的情况下长期处于不活动状态时降低耗电量; 一旦用户通过移动设备、打开屏幕或连接至充电器唤醒设备,系统就会立即退出低电耗模式,并且所有应用都会恢复正常活动

应用待机模式允许系统判定应用在用户未主动使用它时是否处于闲置状态; 当用户有一段时间未触摸应用时,系统便会作出此判定;当用户将设备插入电源时,系统会从待机状态释放应用,允许它们自由访问网络并执行任何待处理的作业和同步。如果设备长时间处于闲置状态,系统将允许闲置应用访问网络,频率大约每天一次

低电耗模式和应用待机模式管理在 Android 6.0 或更高版本上运行的所有应用的行为,无论它们是否专用于 API 级别 23

Android 7.0(API 级别 24)限制了隐式广播

引入了随时随地使用低电耗模式; 使得低电耗模式又前进了一步,随时随地可以省电;只要屏幕关闭了一段时间,且设备未插入电源,低电耗模式就会对应用使用熟悉的 CPU 和网络限制;这意味着用户即使将设备放入口袋里也可以省电

Android 8.0(API 级别 26)进一步限制后台行为

例如在后台获取位置信息和释放缓存的唤醒锁定

Android 9.0(API 级别 28)引入了应用待机存储分区

通过它,系统会根据应用使用模式动态确定应用资源请求的优先级; 应用待机存储分区有助于系统根据应用的使用时间新近度和使用频率对应用资源请求确定优先级

根据应用使用模式,每个应用都会被放置在五个优先级存储分区之一中; 系统会根据应用所在的存储分区限制每个应用可用的设备资源

低电耗模式会在屏幕处于关闭状态且设备处于静止状态时限制应用行为; 应用待机模式会将未使用的应用置于一种特殊状态,进入这种状态后,应用的网络访问、作业和同步会受到限制

如何选择合适的后台解决方案

下面有一张图完美的解答了这个问题

WorkManager概述

WorkManager使用

1 声明依赖项

dependencies {
  def work_version = "2.3.1"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional - GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
}

2 自定义一个继承自Worker的类

重写doWork方法,或者使用协程的话,得继承自CoroutineWorker。doWork方法有一个返回值,来标记任务是否成功或者是否要retry; 返回值有三种,分别是Result.success(),Result.failure(),Result.retry()

执行成功返回Result.success() 执行失败返回Result.failure() 需要重新执行返回Result.retry()

override fun doWork(): Result {
    for (i in 1..3) {
        Thread.sleep(500)
        Log.i("aaa", "count: $i parameter: ${inputData.getString("parameter1")}")
    }
    return Result.success(Data.Builder().putString("result1", "value of result1").build())
}

3 选择worker执行的条件

//添加约束
val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .setRequiresBatteryNotLow(false)
                .setRequiresCharging(false)
                .setRequiresDeviceIdle(false)
                .setRequiresStorageNotLow(false)
                .build()
  //对一次性执行添加约束,如果返回faliure或者retry的话就在适当的约束条件下执行worker
  val request = OneTimeWorkRequestBuilder<CountWorker>()
                .setConstraints(constraints)
                .setInputData(Data.Builder().putString("parameter1", "value of parameter1").build())
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.HOURS)
                .build()
 WorkManager.getInstance(context).enqueue(request)
//或者定时每隔一个小时执行任务  
val periodicWorkRequest = PeriodicWorkRequest.Builder(AppsWorker::class.java,
                                    1, TimeUnit.HOURS)
                                     .setConstraints(constraints)
                                     .build();
 WorkManager.getInstance(context).enqueue(periodicWorkRequest)

需要注意的是类似于JobSceeduler,周期性执行的任务最少间隔时间不能小于15mins

4 下面贴出自定义worker类的全部源码

class CountWorker(context: Context, parameters: WorkerParameters)
    : Worker(context, parameters) {
    companion object {
        fun enqueue(context: ComponentActivity) {
            val constraints = Constraints.Builder()
                    .setRequiredNetworkType(NetworkType.CONNECTED)
                    .setRequiresBatteryNotLow(false)
                    .setRequiresCharging(false)
                    .setRequiresDeviceIdle(false)
                    .setRequiresStorageNotLow(false)
                    .build()
            val request = OneTimeWorkRequestBuilder<CountWorker>()
            		//-----1-----添加约束
                    .setConstraints(constraints)
                    //-----2----- 传入执行worker需要的数据
                    .setInputData(Data.Builder().putString("parameter1", "value of parameter1").build())
                    //-----3-----设置避退策略
                    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.HOURS)
                    .build()
             //-----4-----将任务添加到队列中
            //WorkManager.getInstance(context).enqueue(request)
            //或者采用uniqueName执行
        	WorkManager.getInstance(context).beginUniqueWork("uniqueName", ExistingWorkPolicy.REPLACE, request).enqueue()
            //-----5-----对任务加入监听
            WorkManager.getInstance(context).getWorkInfoByIdLiveData(request.id).observe(context, Observer {
            	//-----8----获取doWork中传入的参数
                Log.i("aaa", "workInfo ${it.outputData.getString("result1")} ${it.state}: ")
            })
            //或者采用tag的方式监听状态
            WorkManager.getInstance(context).getWorkInfosByTagLiveData("tagCountWorker").observe(context, Observer {
            Log.i("aaa", "workInfo tag-- ${it[0].outputData.getString("result1")} ${it[0].state}: ")
        	})
        	//或者采用uniqueName的形式监听任务执行的状态
        	WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("uniqueName").observe(context, Observer {
            Log.i("aaa", "workInfo uniqueName-- ${it[0].outputData.getString("result1")} ${it[0].state}: ")
       	 })
        }
    }
    override fun doWork(): Result {
        for (i in 1..3) {
            Thread.sleep(500)
            //-----6-----获取传入的参数
            Log.i("aaa", "count: $i parameter: ${inputData.getString("parameter1")}")
        }
       //-----7-----传入返回的参数
        return Result.success(Data.Builder().putString("result1", "value of result1").build())
    }
}

5 执行任务的方式

如果我们想要以链式执行一系列任务,如图所示,我们可以使用:

 WorkManager.getInstance(context).beginWith(requestA).then(requestB).enqueue()

如果我们的任务A和任务B之间没有关系,需要在任务A和B都完成的情况下执行任务C的话,如图所示,这时候就可以这么调用:

WorkManager.getInstance(context).beginWith(listOf(requestA,requestB)).then(requestC).enqueue()

如果我们想要AB和CD并行的执行完,然后执行E的话,如图所示,可以采用:

val continuation1 = WorkManager.getInstance(context).beginWith(requestA).then(requestB)
val continuation2 = WorkManager.getInstance(context).beginWith(requestC).then(requestD)
WorkContinuation.combine(listOf(continuation1, continuation2)).then(requestE).enqueue()

需要注意的是任务一旦发起,任务是可以保证一定会被执行的,就算退出应用,甚至重启手机都阻止不了他;但可能由于添加了环境约束等原因会在不确定的时间执行罢了

6 取消任务的执行

//通过request.id取消任务
WorkManager.getInstance(context).cancelWorkById(request.id)
//通过request的tag取消任务
WorkManager.getInstance(context).cancelAllWorkByTag("tag")
//通过request的uniqueName取消任务
WorkManager.getInstance(context).cancelUniqueWork("uniqueName")
//取消所有的work任务
WorkManager.getInstance(context).cancelAllWork()

以上可以看到可以通过四种方式取消任务

加载全部内容

相关教程
猜你喜欢
用户评论