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

链式任务

image.png

如果处理的不是一个任务,而是一组任务,可以按照一定顺序来执行,也可以按照组合来执行,如果任务链中的任何一个任务,返回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 管理后台任务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342