替代Service的WorkManager

总述

从事安卓开发,对四大组件应该都相当熟悉,这个在面试的时候也会经常出现,其实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 管理后台任务

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值