亲宝软件园·资讯

展开

Android WorkManager使用以及源码分析

众少成多积小致巨 人气:0

1、前言

WorkManager 是适合用于持久性工作的推荐解决方案。如果工作始终要通过应用重启和系统重新启动来调度,便是持久性的工作。由于大多数后台处理操作都是通过持久性工作完成的,因此 WorkManager 是适用于后台处理操作的主要推荐 API。 WorkManager 可处理三种类型的持久性工作:

2、使用

2.1、引用

implementation "androidx.work:work-runtime:2.7.1" //基础使用
implementation "androidx.work:work-multiprocess:2.7.1" //跨进程时引用

2.2 使用

执行一次性任务

Data data = new Data.Builder().putBoolean("is_test", false).build();
WorkManager.getInstance(this).enqueue(
        new OneTimeWorkRequest.Builder(Test.class) // 执行任务一次性任务
                .setInputMerger(NewInputMerge.class) // 输入数据合并策略,这里并没有用,链式处理时,多个上流执行结果合并,作为下流输入数据
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES) // 重试策略
                .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) // 加急处理
                .addTag("test").addTag("huahua") // 标识
                .setInputData(data) // 输入数据
                .setInitialDelay(5, TimeUnit.SECONDS) // 执行延时时间
                .setConstraints(new Constraints.Builder().setRequiresBatteryNotLow(true).build()) // 约束,部分约束只对高版本有效
                .build()
);

执行周期性任务

WorkManager.getInstance(this).enqueue(
        new PeriodicWorkRequest.Builder(Test.class, 2, TimeUnit.HOURS) // 执行周期性任务,周期2小时
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
                .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
                .addTag("test").addTag("huahua")
                .setInputData(data)
                .setInitialDelay(5, TimeUnit.SECONDS) // 首次执行延时时间
                .setConstraints(new Constraints.Builder().setRequiresBatteryNotLow(true).build())
                .build()
);

拥有名字的任务

WorkManager.getInstance(this).enqueueUniqueWork("test", ExistingWorkPolicy.KEEP,
        new OneTimeWorkRequest.Builder(Test.class).build()); // 此种方法会对重复名字的任务进行处理

监听状态

WorkManager.getInstance(this).getWorkInfosByTagLiveData("test").observe(this, 
        new Observer<List<WorkInfo>>() {
            @Override
            public void onChanged(List<WorkInfo> workInfos) {

            }
        });

定义Work代码

public class Test extends Worker {
    public Test(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
        Data input = getInputData(); // 获取输入数据
        boolean isTest = input.getBoolean("is_test", true);
    }

    @NonNull
    @Override
    public Result doWork() {
        return Result.success();
    }
}

继承使Worker类,实现doWork()方法,此方法是实现任务的主体;doWork()返回的 Result会通知 WorkManager 服务工作是否成功,以及工作失败时是否应重试工作。

配置初始化 不同的版本初始化不同,但是都是通过Provider来进行的,2.6之前是WorkManagerInitializer, 2.6之后是InitializationProvider;这里按照2.7.1的版本来说,老版本有需要留言回复;有两种处理方案

按照提供初始化流程处理

1.首先注意一个字符串配置:这个很重要,不建议修改

<string name="androidx_startup" translatable="false">androidx.startup</string>

2.提供实现Initializer的类,这个类是被调用的关键

3.移除默认实现

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="androidx.work.WorkManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove" /> // 这表示移除
</provider>

默认实现Initializer类WorkManagerInitializer,使用的默认configuration

public final class WorkManagerInitializer implements Initializer<WorkManager> {

    private static final String TAG = Logger.tagWithPrefix("WrkMgrInitializer");

    @NonNull
    @Override
    public WorkManager create(@NonNull Context context) {
        Logger.get().debug(TAG, "Initializing WorkManager with default configuration.");
        WorkManager.initialize(context, new Configuration.Builder().build()); // 这里提供Configuration
        return WorkManager.getInstance(context);
    }

    @NonNull
    @Override
    public List<Class<? extends androidx.startup.Initializer<?>>> dependencies() {
        return Collections.emptyList(); // 提供需要执行的其它Initializer
    }
}

4.进行注册自定义实现; 在meta-data数据处理,key为你需要调用的初始化类,value必须为R.sting.androidx_startup这个字符串的值; 如下

<provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge" >
        <meta-data
            android:name="androidx.work.WorkManagerInitializer" // 这里为你的实现的Initializer
            android:value="androidx.startup" /> // 这里必须与R.sting.androidx_startup保持一致
    </provider>

2.3 重要概念

任务标识

任务类型

任务链

使用 WorkManager 创建工作链并将其加入队列。工作链用于指定多个依存任务并定义这些任务的运行顺序。使用 WorkManager.beginWith(OneTimeWorkRequest 或 WorkManager.beginWith(List)返回WorkContinuation实例,然后通过其 then(OneTimeWorkRequest)或 then(List)添加任务链,最后使用WorkContinuation.enqueue()进行执行。

任务链先添加的任务为后续任务的先决条件, 也就是前面任务成功了后面任务才会执行

重试退避政策

针对worker执行返回Result.retry()时处理策略, 使用方法setBackoffCriteria设置, 有两个指标,策略和延时时间(允许的最小值,即 10 秒);情况有下面2种

输入合并器

针对一次性任务,且在工作链中,父级工作请求的输出将作为子级的输入传入的场景中使用。输入中会存在相同关键字,这时,输入就会存在冲突

WorkManager 提供两种不同类型的 InputMerger:

冲突解决政策

调度唯一工作时,发生冲突时要执行的操作。可以通过在将工作加入队列时传递一个枚举来实现此目的。

对于一次性工作,您需要提供一个 ExistingWorkPolicy,有4 个选项。

约束条件

任务执行的先决条件,使用Contraints.Builder()进行构建实例。有一下约束

还有以下约束,对于>=24的版本有效:

3、原理

合理分为几个部分来说

这里有需要理解的一个技术点:SettableFuture,实现了ListenableFuture接口,并且增加了下面接口ListenableFuture。这个类在源码中频繁使用

ListenableFuture只有一个方法

void addListener(Runnable listener, Executor executor)

调用了这个方法,这个类才有使用意义;调用之后,表示SettableFuture若完成,则使用executor执行listener

另外一个让我觉得有意思的地方,或者说不同之前future的地方是, SettableFuture未实现Runnable接口,也就是其结果不是来源于自己,来源于调用下面3个方法

    public boolean set(@Nullable V value) // 正常设置结果

    public boolean setException(Throwable throwable) // 执行结果异常

    public boolean setFuture(ListenableFuture<? extends V> future) // 设置执行结果来源,也就是ListenableFuture的执行结果

3.1 约束检测

类图如下

结合源码分析,结论有:

实际检测技术点,也就是ConstraintTracker实现

3.1.1 StorageNotLowTracker类

存储空间的是否低,根据系统广播来处理

Intent.ACTION_DEVICE_STORAGE_OK // 存储空间够用
Intent.ACTION_DEVICE_STORAGE_LOW // 存储空间很低

初步检测状态:

@Override
public Boolean getInitialState() {
    Intent intent = mAppContext.registerReceiver(null, getIntentFilter());
    if (intent == null || intent.getAction() == null) {
        return true;
    } else {
        switch (intent.getAction()) {
            case Intent.ACTION_DEVICE_STORAGE_OK:
                return true;
            case Intent.ACTION_DEVICE_STORAGE_LOW:
                return false;
            default:
                return null;
        }
    }
}

实时更新通过接收广播信息:

public void onBroadcastReceive(Context context, @NonNull Intent intent) {
    if (intent.getAction() == null) {
        return;
    }

    switch (intent.getAction()) {
        case Intent.ACTION_DEVICE_STORAGE_OK:
            setState(true);
            break;
        case Intent.ACTION_DEVICE_STORAGE_LOW:
            setState(false);
            break;
    }
}

3.1.2 BatteryChargingTracker类

充电状态,同样通过广播来进行处理;

接收广播根据版本不同而不同如下:

    public IntentFilter getIntentFilter() {
        IntentFilter intentFilter = new IntentFilter();
        if (Build.VERSION.SDK_INT >= 23) {
            intentFilter.addAction(BatteryManager.ACTION_CHARGING);
            intentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
        } else {
            intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
            intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        }
        return intentFilter;
    }

初始状态:

    @Override
    public Boolean getInitialState() {
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = mAppContext.registerReceiver(null, intentFilter);
        if (intent == null) {
            Logger.get().error(TAG, "getInitialState - null intent received");
            return null;
        }
        return isBatteryChangedIntentCharging(intent);
    }
    
    private boolean isBatteryChangedIntentCharging(Intent intent) {
        boolean charging;
        if (Build.VERSION.SDK_INT >= 23) {
            int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
            charging = (status == BatteryManager.BATTERY_STATUS_CHARGING
                    || status == BatteryManager.BATTERY_STATUS_FULL);
        } else {
            int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
            charging = (chargePlug != 0);
        }
        return charging;
    }

实时状态:

@Override
public void onBroadcastReceive(Context context, @NonNull Intent intent) {
    String action = intent.getAction();
    if (action == null) {
        return;
    }
    Logger.get().debug(TAG, String.format("Received %s", action));
    switch (action) {
        case BatteryManager.ACTION_CHARGING:
            setState(true);
            break;
        case BatteryManager.ACTION_DISCHARGING:
            setState(false);
            break;
        case Intent.ACTION_POWER_CONNECTED:
            setState(true);
            break;
        case Intent.ACTION_POWER_DISCONNECTED:
            setState(false);
            break;
    }
}

3.1.3 BatteryNotLowTracker类

电量是否低,同样通过广播实时获取状态

    Intent.ACTION_BATTERY_OKAY //电量正常
    Intent.ACTION_BATTERY_LOW //电量低

初始状态:

    @Override
    public Boolean getInitialState() {
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = mAppContext.registerReceiver(null, intentFilter);
        if (intent == null) {
            Logger.get().error(TAG, "getInitialState - null intent received");
            return null;
        }

        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        float batteryPercentage = level / (float) scale;

        return (status == BatteryManager.BATTERY_STATUS_UNKNOWN
                || batteryPercentage > BATTERY_LOW_THRESHOLD); // 这里是小于15%算是低
    }

实时更新:

    @Override
    public void onBroadcastReceive(Context context, @NonNull Intent intent) {
        if (intent.getAction() == null) {
            return;
        }
        Logger.get().debug(TAG, String.format("Received %s", intent.getAction()));
        switch (intent.getAction()) {
            case Intent.ACTION_BATTERY_OKAY:
                setState(true);
                break;
            case Intent.ACTION_BATTERY_LOW:
                setState(false);
                break;
        }
    }

3.1.4 NetworkStateTracker类

网络状态,这个根据版本不同使用也不同

初始状态:

NetworkState getActiveNetworkState() {
    NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
    boolean isConnected = info != null && info.isConnected();
    boolean isValidated = isActiveNetworkValidated();
    boolean isMetered = ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager);
    boolean isNotRoaming = info != null && !info.isRoaming();
    return new NetworkState(isConnected, isValidated, isMetered, isNotRoaming);
}          

实时状态: 在回调NetworkCallback或者接收到广播onReceive时,调用getActiveNetworkState得到

3.2 任务调度器

实现Scheduler接口,调度器有3个:

调度器的创建在WorkManagerImpl类

   public List<Scheduler> createSchedulers(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor taskExecutor) {

        return Arrays.asList(
                Schedulers.createBestAvailableBackgroundScheduler(context, this), // 可以被系统唤起调用手段
                new GreedyScheduler(context, configuration, taskExecutor, this)); // 进程内调用手段
    }

调度器被调用的逻辑在Schedulers

public static void schedule(Configuration configuration,WorkDatabase workDatabase,List<Scheduler> schedulers) {
        if (schedulers == null || schedulers.size() == 0) {
            return;
        }
        WorkSpecDao workSpecDao = workDatabase.workSpecDao();
        List<WorkSpec> eligibleWorkSpecsForLimitedSlots;
        List<WorkSpec> allEligibleWorkSpecs;
        workDatabase.beginTransaction();
        try {
            eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(configuration.getMaxSchedulerLimit()); 
            allEligibleWorkSpecs = workSpecDao.getAllEligibleWorkSpecsForScheduling(MAX_GREEDY_SCHEDULER_LIMIT);

            if (eligibleWorkSpecsForLimitedSlots != null && eligibleWorkSpecsForLimitedSlots.size() > 0) {
                long now = System.currentTimeMillis();
                for (WorkSpec workSpec : eligibleWorkSpecsForLimitedSlots) {
                    workSpecDao.markWorkSpecScheduled(workSpec.id, now); // 进行插槽状态处理,非默认才会被GreedyScheduler进行调度
                }
            }
            workDatabase.setTransactionSuccessful();
        } finally {
            workDatabase.endTransaction();
        }

        if (eligibleWorkSpecsForLimitedSlots != null && eligibleWorkSpecsForLimitedSlots.size() > 0) {
            WorkSpec[] eligibleWorkSpecsArray =
                    new WorkSpec[eligibleWorkSpecsForLimitedSlots.size()];
            eligibleWorkSpecsArray =
                    eligibleWorkSpecsForLimitedSlots.toArray(eligibleWorkSpecsArray);
            for (Scheduler scheduler : schedulers) {
                if (scheduler.hasLimitedSchedulingSlots()) {
                    scheduler.schedule(eligibleWorkSpecsArray);
                }
            }
        }

        if (allEligibleWorkSpecs != null && allEligibleWorkSpecs.size() > 0) {
            WorkSpec[] enqueuedWorkSpecsArray = new WorkSpec[allEligibleWorkSpecs.size()];
            enqueuedWorkSpecsArray = allEligibleWorkSpecs.toArray(enqueuedWorkSpecsArray);
            for (Scheduler scheduler : schedulers) {
                if (!scheduler.hasLimitedSchedulingSlots()) {
                    scheduler.schedule(enqueuedWorkSpecsArray);
                }
            }
        }
    }

GreedyScheduler调度器处理任务正在排队,任务最大限制是200条;其它调度器执行在排队且未被调度器处理的,且和现在调度器已经处理的总和不超过配置中的最大限制,默认是版本23是10,其它版本20

3.2.1 GreedyScheduler调度器

类图:

通过流程分析有以下结论:

3.2.2 SystemJobScheduler调度器

类图:

这个调度器比较简单,其实现主要通过了JobScheduler的实现,JobScheduler的实现原理这里不做介绍

3.2.3 SystemAlarmScheduler调度器

这个调度器实现就比较复杂了;类图:

3.3、任务流程

任务流程涉及调度的具体过程,在以下流程图中就会直接从Schedulers方法直接异步执行启动任务;调度过程省略;

3.3.1 enqueue流程

WorkManager调用时,均是先包装成WorkContinuationImpl然后调用其方法enqueue执行;

WorkContinuationImpl: 包含了一个链表,指向了所有依赖的WorkContinuation对象;也记录了任务本身的一些信息和流程状态

流程图

3.3.2 cancel流程

取消时,可以通过任务的id、name、tag或者取消所有;不管是哪种调用,都是通过标识获取满足条件的任务id集合,并进行取消;其流程也只是在id集合查询时不同而已

流程图

4、总结

上面只是大概介绍了常规使用,以及相关类图、流程,代码细节并没有展示。代码中的一些技术点需要至少会用

从代码架构上,也有需要去多思考的地方

加载全部内容

相关教程
猜你喜欢
用户评论