Android Jetpack库重要组件WorkManager的使用

目录
  • 前言
  • 后台处理指南
  • 后台处理面临的挑战
  • 如何选择合适的后台解决方案
  • WorkManager概述
  • WorkManager使用
    • 1 声明依赖项
    • 2 自定义一个继承自Worker的类
    • 3 选择worker执行的条件
    • 4 下面贴出自定义worker类的全部源码
    • 5 执行任务的方式
    • 6 取消任务的执行

前言

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)引入了应用待机存储分区

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

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

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

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

  • Android 7.0(API 级别 24)限制了隐式广播,并引入了随时随地使用低电耗模式
  • Android 8.0(API 级别 26)进一步限制了后台行为,例如在后台获取位置信息和释放缓存的唤醒锁定
  • Android 9(API 级别 28)引入了应用待机存储分区,通过它,系统会根据应用使用模式动态确定应用资源请求的优先级

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

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

  • 从上图我们可以清晰的了解如何选择后台解决方案,如果是一个长时间的http下载的话就使用DownloadManager
  • 否则的话就看是不是一个可以延迟的任务,如果不可以就使用Foreground service
  • 如果是的话就看是不是可以由系统条件触发,如果是的话就使用WorkManager
  • 如果不是就看是不是需要在一个固定的时间执行这个任务,如果是的话就使用AlarmManager
  • 如果不是的话就使用WorkManager

WorkManager概述

  • 使用 WorkManager API 可以轻松地调度可延迟的工作以及预计即使您的设备或应用重启也会运行的工作,即使在应用退出或设备重启时仍应运行的可延迟异步任务
  • 最高向后兼容到 API 14
  • 在运行 API 23 及以上级别的设备上使用 JobScheduler
  • 在运行 API 14-22 的设备上结合使用 BroadcastReceiver 和 AlarmManager
  • 添加网络可用性或充电状态等工作约束
  • 调度一次性或周期性异步任务
  • 监控和管理计划任务
  • 将任务链接起来
  • 确保任务执行,即使应用或设备重启也同样执行任务
  • 遵循低电耗模式等省电功能

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())
    }
}
  • 为了测试方便,我把执行的代码写在了enqueue中了,在enqueue中,我们首先在注释1处添加了约束
  • 在注释2处添加了执行worker需要的参数。这个参数可以在doWork中获取到,如注释6处所示;传入的数据不能超过10kb
  • 注释3处我们设置了避退策略,如果我们的一次性任务返回了retry,这里就可以起作用了,避退策略有两种方式。一种是指数级的EXPONENTIAL,还有一种是线性的LINEAR
  • 然后注释4处将任务加入到队列中,这里仅仅是加入队列,并不能保证执行,因为WorkManager主要的定位就是针对可延迟的任务,它需要根据添加的约束和系统自身的情况来做出什么时间执行这个任务
  • 注释5处可以根据request的id获取到任务的执行状态,返回值是一个LiveData类型的,并将其加入到生命周期观察序列中;所以当任务的执行状态发生变化的时候就会在注释8处打印信息
  • 我们还可以在任务执行结束的时候传入需要返回的参数,但是只能在success和failure的时候传入,传入的数据可以再注释8处获取

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()

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

到此这篇关于Android Jetpack库重要组件WorkManager的使用的文章就介绍到这了,更多相关Android Jetpack WorkManager内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-08-09

Android开发Jetpack组件WorkManager用例详解

目录 一.简介 二.导入 三.基本使用 3.1 定义后台任务 3.2 配置任务运行条件 3.2.1 只需执行一次的任务 3.2.2 周期性执行的任务 3.3 将任务传给 WorkManager 四.高级配置 4.1 设置任务延迟执行 4.2 给任务添加标签 4.3 取消任务 4.3.1 根据标签取消任务 4.3.2 根据 request 的 id 取消任务 4.3.3 取消所有任务 4.4 任务重试 4.5 监听任务结果 4.6 传递数据 4.7 链式任务 一.简介 WorkManager 用于

Android使用Kotlin API实践WorkManager

WorkManager 提供了一系列 API 可以更加便捷地规划异步任务,即使在应用被关闭之后或者设备重启之后,仍然需要保证立即执行的或者推迟执行的任务被正常处理.对于 Kotlin 开发者,WorkManager 为协程提供了最佳的支持.在本文中,我将通过实践 WorkManager codelab 为大家展示 WorkManager 中与协程相关的基本操作.那么让我们开始吧! WorkManager 基础 当您需要某个任务保持运行状态,即使用户切换到别的界面或者用户将应用切换到后台,甚至设备

Android WorkManager浅谈

一.原文翻译 WorkManager API 可以很容易的指定可延迟的异步任务.允许你创建任务,并把它交给WorkManager来立即运行或在适当的时间运行.WorkManager根据设备API的级别和应用程序状态等因素来选择适当的方式运行任务.如果WorkManager在应用程序运行时执行你的任务,它会在应用程序进程的新线程中执行.如果应用程序没有运行,WorkManager会根据设备API级别和包含的依赖项选择适当的方式安排后台任务,可能会使用JobScheduler.Firebase Jo

浅谈android中数据库的拷贝

SQLiteDatabase不支持直接从assets读取文件,所以要提前拷贝数据库.在读取数据库时,先在项目中建立assets文件夹用于存放外部文件,将数据库文件拷到该目录下. 代码方法: /** * 拷贝数据库至file文件夹下 * @param dbName 数据库名称 */ private void initAddressDB(String dbName) { //1,在files文件夹下创建同名dbName数据库文件过程 File files=getFilesDir();//获取/dat

浅谈Android Activity与Service的交互方式

实现更新下载进度的功能 1. 通过广播交互 Server端将目前的下载进度,通过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上. 优缺点分析: 通过广播的方式实现Activity与Service的交互操作简单且容易实现,可以胜任简单级的应用.但缺点也十分明显,发送广播受到系统制约.系统会优先发送系统级广播,在某些特定的情况下,我们自定义的广播可能会延迟.同时在广播接收器中不能处理长耗时操作,否则系统会出现ANR即应用程序无响应

浅谈android获取设备唯一标识完美解决方案

本文介绍了浅谈android获取设备唯一标识完美解决方案,分享给大家,具体如下: /** * deviceID的组成为:渠道标志+识别符来源标志+hash后的终端识别符 * * 渠道标志为: * 1,andriod(a) * * 识别符来源标志: * 1, wifi mac地址(wifi): * 2, IMEI(imei): * 3, 序列号(sn): * 4, id:随机码.若前面的都取不到时,则随机生成一个随机码,需要缓存. * * @param context * @return */ p

浅谈Android Studio 3.0 工具新特性的使用 Android Profiler 、Device File Explorer

前言: 其实 studio3.0的工具大家也已经使用过一段时间了,自己呢,就是从bate版开始使用的,我觉得比较好用的几个地方.就几个,可能还没用到其他的精髓. 但我觉的这个两个功能对我是比较实用的.好那么下面就给大家介绍一下吧. 正文: 话不多说咱们直接上图吧.(个人比较喜欢看图说话) 第一个(Android Profiler)我要介绍的就是这个了.(先看一下效果"震撼一下") (图-1) (图-2) (图-3) (厉害不厉害,牛逼不牛逼)那么我们怎么来操作这个工具呢,来咱们接着看图

浅谈Android获取ImageView上的图片,和一个有可能遇到的问题

1.在获取图片前先调用setDrawingCacheEnabled(true)这个方法: 举例:mImageView.setDrawingCacheEnabled(true); 2.之后可以通过getDrawingCache()获取图片 举例:Bitmap obmp = Bitmap.createBitmap(mImageView.getDrawingCache());  //获取到Bitmap的图片 3.获取完图片后记得调用setDrawingCacheEnabled(false) 举例:mI

浅谈Android View绘制三大流程探索及常见问题

View绘制的三大流程,指的是measure(测量).layout(布局).draw(绘制) measure负责确定View的测量宽/高,也就是该View需要占用屏幕的大小,确定完View需要占用的屏幕大小后,就会通过layout确定View的最终宽/高和四个顶点在手机界面上的位置,等通过measure和layout过程确定了View的宽高和要显示的位置后,就会执行draw绘制View的内容到手机屏幕上. 在详细介绍这三大流程之前,需要简单了解一下ViewRootImpl,View绘制的三大步骤

浅谈Android编码规范及命名规范

前言: 目前工作负责两个医疗APP项目的开发,同时使用LeanCloud进行云端配合开发,完全单挑. 现大框架已经完成,正在进行细节模块上的开发 抽空总结一下Android项目的开发规范:1.编码规范 2.命名规范 注:个人经验,经供参考 一.Android编码规范 1.学会使用string.xml文件 在我看来,当一个文本信息出现的次数大于一次的时候就必须要使用string.xml 比如一个保存按钮 , 不规范写法: <Button android:id="@+id/editinfo_b

浅谈Android Studio JNI生成so库

1.新建Android studio工程 2.新建class:AppKey.java.主要为了保存密钥 代码块 package com...adminapp.lib.utils.jni; /** * Created by seven on 16/9/8. */ public class AppKey { static { System.loadLibrary("AppKey"); } public static native String WechatId(); public stat

浅谈Android为RecyclerView增加监听以及数据混乱的小坑

为 RecyclerView增加监听 1.在实现好的MyAdapter中写内部接口: public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { this.onItemLongClickListener = onItemLongClickListener; } public void setOnItemClickListener(OnItemClickListener onIt