service
一直被用来做后台运行的操作,包括一些保活,上传数据之类的,这个后台运行的弊端很多,比如耗电,比如设计用户隐私之类的,谷歌对这些后台行为进行了一些处理,从Android Oreo(API 26)
开始,如果一个应用的目标版本为Android 8.0,当它在某些不被允许创建后台服务的场景下,调用了Service
的startService()
方法,该方法会抛出IllegalStateException
。并且出台了一些新政策:
1、2018年8月: 所有新开发应用的target API level
必须是26(Android 8.0)
甚至更高。
2、2018年11月: 所有已发布应用的target API level必须更新至26甚至更高。
3、2019年起: 在每一次发布新版本的Android系统之后,所有新开发以及待更新的应用都必须在一年内将target API level调整至对应的系统版本甚至更高。
如果想继续使用service
,必须调用Context.startForegroundService()
,在前台启动新服务,系统创建服务,应用有五秒的时间来调用该服务的 startForeground()
方法以显示新服务的用户可见通知。 如果应用在此时间限制内未调用startForeground()
,则系统将停止服务并声明此应用为 ANR
。所以,在不久的将来,service
的使用范围会越来越小,取而代之的,是谷歌推出的新的技术:WorkManager
。
WorkManager
在工作的触发器 满足时, 运行可推迟的后台工作。WorkManager
会根据设备API的情况,自动选用JobScheduler,
或是AlarmManager
来实现后台任务,WorkManager
里面的任务在应用退出之后还可以继续执行(注意:是程序退出,并非杀死进程还可以继续执行任务),这个技术适用于在应用退出之后任务还需要继续执行的需求,对于在应用退出的之后任务也需要终止的需求,可以选择ThreadPool、AsyncTask
。
首先添加依赖:
//如果创建后台任务请求,编译器抛出异常,则使用
android {
kotlinOptions {
jvmTarget = 1.8
}
}
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:2.3.1"
WorkManager的简单使用
- 创建后台任务,并且实现具体的任务逻辑。
- 配置后台运行任务运行条件和约束条件,并且构建后台任务请求
- 将后天任务请求传入
WorkManager.enqueue()
中,系统会在合适的时间运行。
1. 创建后台任务
class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
//该方法不会运行在主线程,所以我们可以在此处进行耗时操作。
override fun doWork(): Result {
Log.e("TAG","start")
Thread.sleep(2000)
Log.e("TAG","end")
return Result.success() //返回成功
// return Result.failure() //返回失败
// return Result.retry() //重试,其实也返回失败,只是与WorkRequest.Builder的setBackoffCriteria()结合后进行重新启动
}
}
2. 构建后台任务请求
因为可配置的条件比较多,所以等一下讲解,现在进行基本用法。
// 对于一次性 WorkRequest,请使用 OneTimeWorkRequest,
//对于周期性工作,请使用 PeriodicWorkRequest
// 构建一次性请求,下面是两种不同的创建创建方式
val workRequest = OneTimeWorkRequestBuilder<MyWork>()
.build()
//1 小时进行一次周期性任务请求
val periodicWorkRequestBuilder =
PeriodicWorkRequestBuilder<MyWork>(1, TimeUnit.HOURS)
.build()
注意:
为了降低设备性能消耗,如果创建周期性的工作,那么其运行周期不能短与15分钟。
3.将后台任务请求,传递给WorkMnager的enqueue()
//添加一次性请求任务
WorkManager.getInstance(this)..enqueue(workRequest)
//添加周期性请求任务
WorkManager.getInstance(this).enqueue(periodicWorkRequestBuilder)
4.取消和停止工作
val workRequest = OneTimeWorkRequestBuilder<MyWork>()
.addTag("workmanager")
.build()
WorkManager.getInstance(this).cancelAllWorkByTag("workmanager")
WorkManager.getInstance().cancelWorkById(request.getId());
//会返回 LiveData 和具有该标记的所有任务的状态列表
WorkManager.getInstance().getWorkInfosByTagLiveData(TAG);
使用id
只能取消单个后台任务请求,而使用标签的话,则可以将同一标签名的所有后台任务请求全部取消。
进阶使用
1.约束条件
val uri = Uri.parse("xxxxx")
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) //指定需要在有网的情况下
.setRequiresBatteryNotLow(true)//指定电量在可接受范围内运行
.setRequiresStorageNotLow(true)//指定在存储量在可接受范围内运行
.addContentUriTrigger(uri, true)//当Uri发生变化的时候运行
.setRequiresDeviceIdle(true)//当设备处于空闲状态时运行
.setRequiresCharging(true)//当设备处于充电状态时运行
.build()
//创建请求
val request = OneTimeWorkRequestBuilder<MyWork>()
.setConstraints(constraints)//添加约束
.build()
//当满足约束条件后才会执行该任务
WorkManager.getInstance(this).enqueue(request)
注意:如果指定了多个约束,你的任务将仅在满足所有约束时才会运行。
如果在任务运行期间某个约束不再得到满足,则 WorkManager
将停止工作器。当约束继续得到满足时,系统将重新尝试执行该任务。
2. 延时处理
val workRequest = OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(1,TimeUnit.MINUTES) //延迟一分钟后执行
.build()
3.设置回退/重试的策略,当doWork()
返回Result.retry()
时启用
val workRequest = OneTimeWorkRequestBuilder<MyWork>()
.setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.MINUTES)
.build()
该方法接受三个参数:
第一个参数:指定任务再次执行失败,下次重试的时间应该以什么样的形式延迟。这个很好理解,假如任务一直执行失败,不断地重新执行也没什么意义,只会徒增设备的性能消耗。而随着失败次数增多,下次重试的时间也应该进行适当的延迟。第一个参数可选值有两种,分别是LINEAR
和EXPONENTIAL
,前者表示下次重试时间以线性的方式延迟,后者代表下次重试时间为指数的方式延迟。
第二个参数:时间
第三个参数:时间类型
注意:最短时间不能少于10秒钟
4.传入参数/标记请求任务
btn.setOnClickListener {
//创建参数
val imageData:Data=Data.Builder()
.putString(DATA_KEY,"开始执行")
.build()
val request = OneTimeWorkRequestBuilder<MyWork>()
.setInputData(imageData) //传入参数
.build()
mWorkManager.enqueue(request)
}
class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val inputData = inputData
var string = inputData.getString(DATA_KEY)
Log.e("Tag",string)
//创建输出结果
val build = Data.Builder()
.putString(DATA_KEY, "我接受到了参数了")
.build();
return Result.success(build)
}
}
5.监听工作状态
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.getId())
.observe(this, object : Observer<WorkInfo>{
override fun onChanged(@Nullable workInfo: WorkInfo?) {
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
//获取成功返回的结果
Log.e("TAG",workInfo.outputData.getString(DATA_KEY))
}
}
})
6. 指定并发或者按照顺序执行任务
val imageData:Data=Data.Builder()
.putString(DATA_KEY,"开始执行")
.build()
val imageData2:Data=Data.Builder()
.putString(DATA_KEY,"开始执行1")
.build()
val request = OneTimeWorkRequestBuilder<MyWork>()
.setInputData(imageData) //传入参数
.build()
val request1 = OneTimeWorkRequestBuilder<MyWork>().
setInputData(imageData2) //传入参数
.build()
val request2 = OneTimeWorkRequestBuilder<MyWork>().build()
val request3 = OneTimeWorkRequestBuilder<MyWork>()
.setInputMerger(OverwritingInputMerger::class.java)
.build();
val request4 = OneTimeWorkRequestBuilder<MyWork>().build();
// 为了管理来自多个父级 OneTimeWorkRequest 的输入,WorkManager 使用 InputMerger。
// WorkManager 提供两种不同类型的 InputMerger:
// OverwritingInputMerger 会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。
// ArrayCreatingInputMerger 会尝试合并输入,并在必要时创建数组。
WorkManager.getInstance(this)
//使用beginWith()可以并行执行request、request1、request2
.beginWith( mutableListOf<OneTimeWorkRequest>(request,request1,request2))
//使用then()可以按顺序执行任务
.then(request3)//在执行request3
.then(request4)//在执行request4
.enqueue();
这里我们需要注意一下如果我们使用这种链式调用的话,WorkManager
还要求,必须在前一个后台任务运行完成之后,下一个后台任务才会运行。也就是说,如果某一个后台任务运行失败,或者取消,那么接下来的后台任务就都得不到运行了。
7.唯一工作序列
我们要想创建一个唯一的工作序列,只需调用
beginUniqueWork()
而不是beginWith()
.来开始序列。每个唯一的工作序列都有一个名字,WorkManager
一次只允许一个工作序列使用该名称,当我们创建一个新的唯一工作序列时,如果已经有一个未完成的序列具有相同的名称,则指定WorkManager
应执行的操作:取消现有的序列并用新序列其替换 保持现有顺序并忽略新的请求 将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务
如果我们有一个不应该多次入队的任务,则唯一工作序列可能很有用。例如,如果我们的应用需要将其数据同步到网络,我们可能会排列一个名为“sync”的序列,并指定如果已经有一个具有该名称的序列,则应该忽略我们的新任务。如果我们需要逐步建立一个长期的任务链,那么唯一的工作序列也会很有用,例如,照片编辑应用可能会让用户撤消一长串的操作,每个撤销操作可能需要一段时间,但必须按正确的顺序执行,在这种情况下,应用程序可以创建一个“撤消”链并根据需要将每个撤销操作追加到链中。
如果进程被杀死,或者不满足约束条件时,那么WorkManager
是不会运行的。当约束继续得到满足时,或者程序重新启动时,系统将重新尝试执行该任务。
例子:
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
textViewA.text=sharedPreferences?.getInt("A", 0).toString()
textViewB.text=sharedPreferences?.getInt("B", 0).toString()
}
private val mWorkManager by lazy {
WorkManager.getInstance(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sharedPreferences = getSharedPreferences("WORK", 0)
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
btn.setOnClickListener {
val build = OneTimeWorkRequestBuilder<MyWork>()
.setInputData(workDataOf("A" to "A"))
.build()
val build2 = OneTimeWorkRequestBuilder<MyWork>()
.setInputData(workDataOf("A" to "B"))
.build()
mWorkManager.beginWith(build)
.then(build2)
.enqueue()
}
}
}
open class MyWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
Thread.sleep(6000)
val sp =applicationContext.getSharedPreferences("WORK", 0)
val key = inputData.getString("A")
var int = sp.getInt(key, 0)
++int;
sp.edit().putInt(key,int).apply()
return Result.success()
}
}
如果有什么地方有问题,也欢迎大家提出。
参考博客:
Android Jetpack架构组件之WorkManager入门
WorkManager 入门指南
WorkManger