zoukankan      html  css  js  c++  java
  • WorkManager详解

    WorkManager详解

    前言  

      WorkManager组件是用来管理后台工作任务。Android不是已经有很多管理后台任务的类,比如JobScheduler, AlarmManger;在比如AsyncTask, ThreadPool,WorkManager。WorkManager的优势在哪里,我们为啥要使用WorkManager。我们从几个方面来说明WorkManager的优势。
     
    • WorkManager对比JobScheduler, AlarmManger的优势:AlarmManager是一直存在,适用于类似闹钟那样必须准时唤醒的任务,但是JobScheduler是Android 5.x之后才有。WorkManager的底层实现,会根据你的设备API的情况,自动选用JobScheduler, 或是AlarmManager来实现后台任务。什么?在哪里体现的?耐心等待下文的讲解。

    • WorkManager对比AsyncTask, ThreadPool的优势:WorkManager里面的任务在应用退出之后还可以继续执行。AsyncTask, ThreadPool里面的任务在应用退出之后不会执行。WorkManager适用于那些在应用退出之后任务还需要继续执行的需求(比如应用数据上报服务器的情况),对应那些在应用退出的之后任务也需要终止的情况就需要选择ThreadPool、AsyncTask来实现。

    • WorkManager的出现,为应用程序中那些不需要及时完成的任务,提供统一的解决方案,以便在设备电量和用户体验之间达到一个比较好的平衡。

    • WorkManager能保证任务一定会被执行,即使你的应用程序当前不在运行中,哪怕你的设备重启,任务仍然会在适当的时候被执行。这是因为WorkManager有自己的数据库,关于任务的所有信息和数据都保存在这个数据库中,因此,只要你的任务交给了WorkManager,哪怕你的应用程序彻底退出,或者设备重新启动,WorkManager依然能够保证完成你交给的任务。

    兼容范围广

      WorkManager最低能兼容API Level 14,并且不需要你的设备安装有Google Play Services。因此,你不用过于担心兼容性问题,因为API Level 14已经能够兼容几乎100%的设备了。

    WorkManager依据设备情况选择方案

      WorkManager能依据设备的情况,选择不同的执行方案。在API Level 23+,通过JobScheduler来完成任务,而在API Level 23以下的设备中,通过AlarmManager和Broadcast Receivers组合完成任务。但无论采用哪种方案,任务最终都是交由Executor来完成。 

      WorkManager不是一种新的工作线程,它的出现不是为了替代其它类型的工作线程。工作线程通常立即运行,并在执行完成后给到用户反馈。而WorkManager不是即时的,它不能保证任务能立即得到执行。

    在项目中使用WorkManager

    1.在app的build.gradle中添加依赖。

    app build.gradle
    dependencies {
        implementation rootProject.ext.work_runtime
    }
    
    工程根目录下的config.gradle
    WorkRuntimeVersion = "2.4.0"
    work_runtime = [
                "androidx.work:work-runtime:$WorkRuntimeVersion"
        ]

    2.使用Worker定义任务 。

    public class UploadLogWorker extends Worker {
    
        private static final String TAG = "WorkManager";
    
        public UploadLogWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
            super(context, workerParams);
        }
    
        /**
         * 耗时的任务,在doWork()方法中执行
         * <p>
         * 有三种类型的返回值:
         * 执行成功返回Result.success()
         * 执行失败返回Result.failure()
         * 需要重新执行返回Result.retry()
         */
        @NonNull
        @Override
        public Result doWork() {
            Log.i("==lwf==", "doWork");
            String inputData = getInputData().getString("input_data");
            // 任务执行完成后返回数据
            Data outputData = new Data.Builder().putString("output_data", inputData).build();
            return Result.success(outputData);
        }
    }

    3.使用WorkRequest配置任务。通过WorkRequest配置我们的任务何时运行以及如何运行。

    • 设置任务触发条件。例如,我们可以设置在设备处于充电,网络已连接,且电池电量充足的状态下,才触发我们设置的任务
    Constraints constraints = new Constraints.Builder()
                .setRequiresCharging(true)//充电
                .setRequiredNetworkType(NetworkType.CONNECTED)//网络已连接
                .setRequiresBatteryNotLow(true)//电池电量充足
                .build();

    将该Constraints设置到WorkRequest。WorkRequest是一个抽象类,它有两种实现,OneTimeWorkRequestPeriodicWorkRequest,分别对应的是一次性任务和周期性任务。

     
    //只执行一次任务
    OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
                 .addTag("UploadTag")
                 .setConstraints(constraints)//设置触发条件
                 .setInitialDelay(10, TimeUnit.SECONDS)//符合触发条件后,延迟10秒执行
                 .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)//线性重试方案 最大5h 最小10s
                 .build();
    为任务设置Tag标签。设置Tag后,你就可以通过该抱歉跟踪任务的状态WorkManager.getWorkInfosByTagLiveData(String tag)或者取消任务WorkManager.cancelAllWorkByTag(String tag)。
    这里看到重试属性,为何最大5h,最小10s ?原因如下:
    public static final long MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000; // 5 hours.
    
    public static final long MIN_BACKOFF_MILLIS = 10 * 1000; // 10 seconds.
    
    public void setBackoffDelayDuration(long backoffDelayDuration) {
            if (backoffDelayDuration > MAX_BACKOFF_MILLIS) {
                Logger.get().warning(TAG, "Backoff delay duration exceeds maximum value");
                backoffDelayDuration = MAX_BACKOFF_MILLIS;
            }
            if (backoffDelayDuration < MIN_BACKOFF_MILLIS) {
                Logger.get().warning(TAG, "Backoff delay duration less than minimum value");
                backoffDelayDuration = MIN_BACKOFF_MILLIS;
            }
            this.backoffDelayDuration = backoffDelayDuration;
        }

    4.将任务提交给系统。WorkManager.enqueue()方法会将你配置好的WorkRequest交给系统来执行 

    WorkManager.getInstance().enqueue(compressionWork);

    5.观察任务的状态。

    任务在提交给系统后,通过WorkInfo获知任务的状态,WorkInfo包含了任务的id,tag,以及Worker对象传递过来的outputData,以及任务当前的状态。有三种方式可以得到WorkInfo对象。

    WorkManager.getWorkInfosByTag()
    
    WorkManager.getWorkInfoById()
    
    WorkManager.getWorkInfosForUniqueWork()
    
    如果你希望能够实时获知任务的状态。这三个方法还有对应的LiveData方法。
    
    WorkManager.getWorkInfosByTagLiveData()
    
    WorkManager.getWorkInfoByIdLiveData()
    
    WorkManager.getWorkInfosForUniqueWorkLiveData()

    通过LiveData,我们便可以在任务状态发生变化的时候,收到通知。

    WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(this, new Observer<WorkInfo>()
            {
                @Override
                public void onChanged(WorkInfo workInfo)
                {
                    if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED)
                    {
                        String outputData = workInfo.getOutputData().getString("output_data");
                        Log.d("==lwf==", "outputData: " + outputData);
                    }
                }
            });

    6.取消任务。与观察任务类似的,我们也可以根据Id或者Tag取消某个任务,或者取消所有任务。

    WorkManager.getInstance(MainActivity.this).cancelAllWork();

    7.WorkManager和Worker之间的参数传递。数据的传递通过Data对象来完成。

    Data inputData = new Data.Builder().putString("input_data", "Hello World!").build();
            OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
                    .setInputData(inputData)
                    .build();
    WorkManager.getInstance(this).enqueue(uploadWorkRequest);

    8.周期任务PeriodicWorkRequest。

    PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest
                    .Builder(U
    ploadLogWorker.class, 15, TimeUnit.MINUTES)
                    .setConstraints(constraints)//设置触发条件
                    .build();

    需要注意的是:周期性任务的间隔时间不能小于15分钟。

    原因如下:

    public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
    
    public void setPeriodic(long intervalDuration) {
            if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
                Logger.get().warning(TAG, String.format(
                        "Interval duration lesser than minimum allowed value; Changed to %s",
                        MIN_PERIODIC_INTERVAL_MILLIS));
                intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
            }
            setPeriodic(intervalDuration, intervalDuration);
        }

    9.任务链。如果你有一系列的任务需要顺序执行,那么可以利用WorkManager.beginWith().then().then()...enqueue()方法。例如:我们在上传数据之前,需要先对数据进行压缩

    WorkManager.getInstance(this).beginWith(compressWorkRequest).then(uploadWorkRequest).enqueue();

    假设在上传数据之前,除了压缩数据,还需要更新本地数据。压缩与更新本地数据二者没有顺序,但与上传数据存在先后顺序。

    WorkManager.getInstance(this).beginWith(compressWorkRequest, updateLocalWorkRequest).then(uploadWorkRequest).enqueue();

    如果有更复杂的任务链,还可以考虑使用WorkContinuation.combine()方法,将任务链组合起来。

    OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
            OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
            OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
            //A任务链
            WorkContinuation continuationA = WorkManager.getInstance().beginWith(requestA);
            //B任务链
            WorkContinuation continuationB = WorkManager.getInstance().beginWith(requestB);
            //合并上面两个任务链,在接入requestE任务,入队执行
            List<WorkContinuation> list = new ArrayList<>();
            list.add(continuationA);
            list.add(continuationB);
            WorkContinuation continuation = WorkContinuation.combine(list).then(requestC);
            continuation.enqueue();

     采用WorkContinuation.combine()的任务链执行顺序

     解析

    Workmanager在怎么选择使用的JobScheduler, AlarmManger?

    我们再提交任务的时候,是都使用Workmanager.getInstance(Context),看看这个是怎么实现的。

       public static @NonNull WorkManager getInstance(@NonNull Context context) {
            return WorkManagerImpl.getInstance(context);
        }
        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
            synchronized (sLock) {
                WorkManagerImpl instance = getInstance();
                if (instance == null) {
                    Context appContext = context.getApplicationContext();
                    if (appContext instanceof Configuration.Provider) {
                        initialize(
                                appContext,
                                ((Configuration.Provider) appContext).getWorkManagerConfiguration());
                        instance = getInstance(appContext);
                    } else {
                        throw new IllegalStateException("WorkManager is not initialized properly.  You "
                                + "have explicitly disabled WorkManagerInitializer in your manifest, "
                                + "have not manually called WorkManager#initialize at this point, and "
                                + "your Application does not implement Configuration.Provider.");
                    }
                }
    
                return instance;
            }
        }

    看到这里进行了初始化initialize,为什么开机就能运行呢?

    反编译APK,发现AndroidManifest.xml

    <provider
                android:name="androidx.work.impl.WorkManagerInitializer"
                android:exported="false"
                android:multiprocess="true"
                android:authorities="com.example.myapplication.workmanager-init"
                android:directBootAware="false" />
    注册了WorkManagerInitializer
    public class WorkManagerInitializer extends ContentProvider

    原来WorkManagerInitializer是一个ContentProvider

    public boolean onCreate() {
            // Initialize WorkManager with the default configuration.
            WorkManager.initialize(getContext(), new Configuration.Builder().build());
            return true;
        }
        public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
            WorkManagerImpl.initialize(context, configuration);
        }

    原来是这样初始化的,在WorkManagerImpl初始化过程中选择了JobScheduler, AlarmManger方式

    public WorkManagerImpl(
                @NonNull Context context,
                @NonNull Configuration configuration,
                @NonNull TaskExecutor workTaskExecutor,
                @NonNull WorkDatabase database) {
            Context applicationContext = context.getApplicationContext();
            Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
            List<Scheduler> schedulers =
                    createSchedulers(applicationContext, configuration, workTaskExecutor);
            Processor processor = new Processor(
                    context,
                    configuration,
                    workTaskExecutor,
                    database,
                    schedulers);
            internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
        }
        public List<Scheduler> createSchedulers(
                @NonNull Context context,
                @NonNull Configuration configuration,
                @NonNull TaskExecutor taskExecutor) {
    
            return Arrays.asList(
                    Schedulers.createBestAvailableBackgroundScheduler(context, this),
                    // Specify the task executor directly here as this happens before internalInit.
                    // GreedyScheduler creates ConstraintTrackers and controllers eagerly.
                    new GreedyScheduler(context, configuration, taskExecutor, this));
        }

     

    static Scheduler createBestAvailableBackgroundScheduler(
                @NonNull Context context,
                @NonNull WorkManagerImpl workManager) {
    
            Scheduler scheduler;
    
            if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
                scheduler = new SystemJobScheduler(context, workManager);
                setComponentEnabled(context, SystemJobService.class, true);
                Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
            } else {
                scheduler = tryCreateGcmBasedScheduler(context);
                if (scheduler == null) {
                    scheduler = new SystemAlarmScheduler(context);
                    setComponentEnabled(context, SystemAlarmService.class, true);
                    Logger.get().debug(TAG, "Created SystemAlarmScheduler");
                }
            }
            return scheduler;
        }

    原来是在这里选择的

  • 相关阅读:
    maven常用仓库
    AD域安装及必要设置
    oracle创建表空间
    javascript弹出模态窗体
    win8.1 AMD 屏幕亮度无法调整
    tomcat优化
    CentOS 6.2 中文
    tomcat之JNDI数据源配置
    eclipse中tomcat配置(待完善)
    Ant打jar包指定MainClass
  • 原文地址:https://www.cnblogs.com/xinmengwuheng/p/14181910.html
Copyright © 2011-2022 走看看