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。
WorkManager相关类
Worker
任务的执行者,是一个抽象类,用于指定需要执行的具体任务,需要实现doWork() 这一个方法,它是执行在一个单独的后台线程里的。所有需要在后台执行的任务都在这个方法里完成。
doWork()函数的返回值:
- Worker.Result.SUCCESS:任务执行成功。
- Worker.Result.FAILURE:任务执行失败。
- Worker.Result.RETRY:任务需要重新执行,如果出现这个返回结果,就需要与WorkRequest.Builder中的setBackoffCriteria()函数一起使用。
WorkRequest
代表一个单独的任务,对Worker任务进行包装,一个WorkRequest对应一个Worker类。可以通过WorkRequest来给Worker类添加约束细节,比如设备是否空闲,设备电池是否不应低于临界阈值,指定设备在充电时是否启动任务等等。WorkRequest是一个抽象类,具体要使用两个子类:OneTimeWorkRequest(任务只执行一遍)、PeriodicWorkRequest(任务周期性的执行)。
WorkManager
主要管理任务请求和任务队列,将WorkRequest加入任务队列。 通过WorkManager来调度任务,以分散系统资源的负载。
WorkStatus
当 WorkManager 把任务加入队列后,会为每个WorkRequest对象提供一个 LiveData, LiveData 持有 WorkStatus,包含有任务的状态和任务的信息
引用
在build.gradle中,引用workManager:
implementation "android.arch.work:work-runtime:1.0.0-alpha07"
版本可以选用最新的
定义 Worker
新建一个jave类:MyWorker,继承自Worker,必须实现doWork()方法,要在这个方法里,操作后台任务,
public class MyWorker extends Worker
{
String tag = MyWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork()
{
return Result.SUCCESS;
}
@Override
public void onStopped(boolean cancelled)
{
super.onStopped(cancelled);
Log.e(tag,"Worker Stopped");
}
}
定义WorkRequest
在主Activity中,定义WorkRequest,具体可以选择两个子类,OneTimeWorkRequest(任务只执行一遍)、PeriodicWorkRequest(任务周期性的执行),比如:
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS).build();
这里有一个注意点:这个周期任务,最小周期是15分钟,源码:
/**
* The minimum interval duration for {@link PeriodicWorkRequest} (in milliseconds).
*/
public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
public void setPeriodic(long intervalDuration, long flexDuration) {
if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
Logger.warning(TAG, String.format(
"Interval duration lesser than minimum allowed value; Changed to %s",
MIN_PERIODIC_INTERVAL_MILLIS));
intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
}
....
}
加入队列
已经将Worker与WorkRequest相关联,现在定义WorkManager,将WorkRequest加入队列,
//任务入队,WorkManager调度执行
WorkManager.getInstance().enqueue(request);
传入数据
前台和后台服务,有时候需要传入数据,在Activity定义Data,将需要传入的数据包装一下,然后通过WorkRequest的setInputData()传入
Data data = new Data.Builder().putInt("params1", 1).putString("params2", "hello").build();
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
.setInputData(data)
.build();
这个传入的值,可以在Worker中获取
public class MyWorker extends Worker
{
String tag = MyWorker.class.getSimpleName();
@NonNull
@Override
public Result doWork()
{
int params1 = getInputData().getInt("params1",0);
String params2 = getInputData().getString("params2");
Log.d(tag,"获得参数:"+params1+","+params2);
return Result.SUCCESS;
}
@Override
public void onStopped(boolean cancelled)
{
super.onStopped(cancelled);
Log.e(tag,"Worker Stopped");
}
}
返回数据
后台在处理完任务以后,可以返回一些数据,返回数据,传出数据需要使用outputData(),具体还是在Worker的doWork()方法里
Data resultData = new Data.Builder()
.putString("result","success").build();
setOutputData(resultData);
处理WorkStatus
当 WorkRequest入列后,WorkManager 会给它分配一个 work ID,WorkManager可以通过WorkRequest的id,获取到WorkRequest的WorkStatus,返回的是LiveData 形式:
WorkManager.getInstance().getStatusById(request.getId()).observe(this, new android.arch.lifecycle.Observer<WorkStatus>()
{
@Override
public void onChanged(@Nullable WorkStatus workStatus)
{
if (workStatus != null && workStatus.getState() != null)
{
Log.d("MainActivity", workStatus.getState() + "");
}
}
});
WorkStatus 的state包括:
/**
* 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);
}
}
WorkRequest加标签
可以通过addTag给WorkRequest加入标签,比如:
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
.setInputData(data)
.addTag("A")
.build();
PeriodicWorkRequest request2 = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
.setInputData(data)
.addTag("A")
.build();
通过addTag(),将WorkRequest成为了一个组:A组。以后可以直接控制整个组就行了,组内的每个成员都会受到影响。比如通过WorkManager的cancelAllWorkByTag(String tag):取消一组带有相同标签的任务。
取消任务
WorkManager可以通过WorkRequest的id取消或者停止任务,
WorkManager.getInstance().cancelWorkById(request.id)
WorkManager 并不一定能结束任务,因为任务有可能已经执行完毕了。
还有其他结束的方法:
cancelAllWork():取消所有任务。
cancelAllWorkByTag(String tag):取消一组带有相同标签的任务。
cancelUniqueWork( String uniqueWorkName):取消唯一任务。
添加约束
WorkManager 允许指定任务执行的环境,比如网络已连接、电量充足时等,在满足条件的情况下任务才会执行。
现在支持的约束:
public boolean requiresBatteryNotLow ():执行任务时电池电量不能偏低。
public boolean requiresCharging ():在设备充电时才能执行任务。
public boolean requiresDeviceIdle ():设备空闲时才能执行。
public boolean requiresStorageNotLow ():设备储存空间足够时才能执行。
具体代码:
Constraints constraints = new Constraints.Builder()
.setRequiresDeviceIdle(true)//指定{@link WorkRequest}运行时设备是否为空闲
.setRequiresCharging(true)//指定要运行的{@link WorkRequest}是否应该插入设备
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresBatteryNotLow(true)//指定设备电池是否不应低于临界阈值
.setRequiresCharging(true)//网络状态
.setRequiresDeviceIdle(true)//指定{@link WorkRequest}运行时设备是否为空闲
.setRequiresStorageNotLow(true)//指定设备可用存储是否不应低于临界阈值
.addContentUriTrigger(myUri,false)//指定内容{@link android.net.Uri}时是否应该运行{@link WorkRequest}更新
.build();
其他的都是一个boolean值,网络状态复杂一些
/**
* 指定网络状态执行任务
* NetworkType.NOT_REQUIRED:对网络没有要求
* NetworkType.CONNECTED:网络连接的时候执行
* NetworkType.UNMETERED:不计费的网络比如WIFI下执行
* NetworkType.NOT_ROAMING:非漫游网络状态
* NetworkType.METERED:计费网络比如3G,4G下执行。
*/
可以根据自己需要,来自由组合这些约束,在WorkRequest中,通过setConstraints设置约束
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.SECONDS)
.setInputData(data)
.setConstraints(constraints)
.build();
链式任务
如果处理的不是一个任务,而是一组任务,可以按照一定顺序来执行,也可以按照组合来执行,如果任务链中的任何一个任务,返回WorkerResult.FAILURE,任务链终止
按照一定顺序执行,需要使用WorkManager的then()方法,将需要执行的任务依次加入
WorkManager.getInstance()
.beginWith(work1)
.then(work2)
.then(work3)
.enqueue()
这个是依次执行,work1执行完,work2才能执行,work2执行完,work3才能执行,上一个任务的返回值就会自动转为下一个任务的参数
组合执行:如果有这样的需求:共有A、B、C、D、E这五个任务,要求 AB 串行,CD 串行,但两个串之间要并发,并且最后要把两个串的结果汇总到E。需要使用WorkContinuation的combine
OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(ConbineWorkerA.class).build();
OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(ConbineWorkerB.class).build();
OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(ConbineWorkerC.class).build();
OneTimeWorkRequest requestD = new OneTimeWorkRequest.Builder(ConbineWorkerD.class).build();
OneTimeWorkRequest requestE = new OneTimeWorkRequest.Builder(ConbineWorkerE.class).build();
//A,B任务链
WorkContinuation continuationAB = WorkManager.getInstance().beginWith(requestA).then(requestB);
//C,D任务链
WorkContinuation continuationCD = WorkManager.getInstance().beginWith(requestC).then(requestD);
//合并上面两个任务链,在接入requestE任务,入队执行
WorkContinuation.combine(continuationAB, continuationCD).then(requestE).enqueue();
任务唯一性
有时候需要在任务队列里,同一个任务只存在一个,避免任务的重复执行,需要使用WorkManager的 beginUniqueWork 这个方法:
WorkManager.getInstance()
.beginUniqueWork("unique", ExistingWorkPolicy.REPLACE, request)
.enqueue()
ExistingWorkPolicy的值:
REPLACE(取消现有的序列并将其替换为新序列)
KEEP(保持现有顺序并忽略新请求)
APPEND(将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务)。
参考文章:
Android8.0时代的后台任务JetPack-WorkManager详解
Android架构组件WorkManager详解
Android Jetpack - 使用 WorkManager 管理后台任务