美文网首页
WorkManager 简述

WorkManager 简述

作者: WilburLi | 来源:发表于2019-01-17 17:42 被阅读0次

    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 管理后台任务

    相关文章

      网友评论

          本文标题:WorkManager 简述

          本文链接:https://www.haomeiwen.com/subject/qjzidqtx.html