JetPack系列---WorkManager源码分析

WorkManager这个任务管理框架还是非常好用的,有人说可以用它替代Service,额,这个我就不太清楚了,不过个人认为替代AsycnTask和JobScheduler应该是没问题,而WorkManager中的循环任务使用的也正是JobScheduler。这里我不去介绍WorkManager的使用,官网和百度都是学习使用的圣地~,我这里只说源码,通过源码看本质,让我们更加透彻的了解WorkManager的原理。

一、源码分析

对于WorkManager的源码,从三个地方入手分析:WorkManager的初始化,任务(WorkRequest)的加入,任务执行结果

1.WorkManager的初始化

估计有什么蒙了,初始化还有什么好说的吗?不就是WorkManager实例化吗?别着急,我们慢慢看看

我们从WorkManager.getInstance方法进入,然后进入到WorkManagerImpl的getInstance方法中

    //WorkManager的getInstance
    public static @NonNull WorkManager getInstance(@NonNull Context context) {
        return WorkManagerImpl.getInstance(context);
    }

    //WorkManagerImpl的getInstance
    @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("太长了,省略~");
                }
            }

            return instance;
        }
    }

我们再进入无参的getInstance方法中瞧瞧

    public static @Nullable WorkManagerImpl getInstance() {
        synchronized (sLock) {
            if (sDelegatedInstance != null) {
                return sDelegatedInstance;
            }

            return sDefaultInstance;
        }
    }

很明显,sDelegatedInstance和sDefaultInstance都为null,我们回到带参的getInstance方法中:从源码中可以看到,如果instance为空的时候,先判断context是否Configuration.Provider的实现类或子类,这个类是干嘛的我不太清楚,但是我基本上上可以肯定Application没有implement这个接口;如果没有实现这个接口或者不是它的子类的话,直接抛出异常

what?到这里我就蒙了,反反复复看了好几遍,流程的确没问题,有问题的应该是从无参的getInstance中获取的实例上,说明这两个示例有在其他地方初始化。

通过逆向寻找,找到了源头WorkManagerInitializer,上图(所谓的逆向寻找就是从sDelegatedInstance和sDefaultInstance初始化的地方开始向上寻找):

WorkManagerInitializer:从源码上我们可以知道,它继承了ContentProvider。那么清单文件中,一定有它的注册才能执行onCreate方法。我在github上找到了WorkManager的源码(地址),清单文件中的确发现了注册的代码,如下图。

WorkManagerImpl的实例初始化完毕,不过我不太清楚的是为什么要使用这种方式进行初始化,有知道的小伙伴可以留言通知呦~

2.任务(WorkRequest)的加入

如何创建WorkRequest可以在官网上进行学习,创建完成后WorkManager执行enqueue方法进行加入执行队列中,WorkManager中的enqueue是一个抽象方法,而WorkManagerImplWorkManager的实现类,我们直接看WorkManagerImpl中的enqueue方法

    @Override
    @NonNull
    public Operation enqueue(
            @NonNull List<? extends WorkRequest> workRequests) {

        // This error is not being propagated as part of the Operation, as we want the
        // app to crash during development. Having no workRequests is always a developer error.
        if (workRequests.isEmpty()) {
            throw new IllegalArgumentException(
                    "enqueue needs at least one WorkRequest.");
        }
        return new WorkContinuationImpl(this, workRequests).enqueue();
    }

这里将WorkManagerImpl和WorkRequests集合,加入到了WorkContinuationImpl这类中,然后执行enqueue方法。

    @Override
    public @NonNull Operation enqueue() {
        // Only enqueue if not already enqueued.
        if (!mEnqueued) {
            // The runnable walks the hierarchy of the continuations
            // and marks them enqueued using the markEnqueued() method, parent first.
            EnqueueRunnable runnable = new EnqueueRunnable(this);
            //将Runnable加入到线程池执行
            mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
            mOperation = runnable.getOperation();
        } else {
            Logger.get().warning(TAG,
                    String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
        }
        return mOperation;
    }

EnqueueRunnable实现了Runnable接口,所以我们直接看run方法:

    @Override
    public void run() {
        try {
            if (mWorkContinuation.hasCycles()) {
                throw new IllegalStateException(
                        String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
            }
            //加入到Room数据库
            boolean needsScheduling = addToDatabase();
            if (needsScheduling) {
                // Enable RescheduleReceiver, only when there are Worker's that need scheduling.
                final Context context =
                        mWorkContinuation.getWorkManagerImpl().getApplicationContext();
                PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
                //执行任务
                scheduleWorkInBackground();
            }
            mOperation.setState(Operation.SUCCESS);
        } catch (Throwable exception) {
            mOperation.setState(new Operation.State.FAILURE(exception));
        }
    }

addToDatabase方法,其实就是把任务(WorkSpec这个类)存储到了Room数据库。经过如下方法的跳转,在enqueueWorkWithPrerequisites中找到了inserWorkSpec方法

workDatabase.workSpecDao().insertWorkSpec(workSpec);

我们继续回到EnqueueRunnable中的run方法,其中scheduleWorkInBackground方法是执行任务,我们进入查看源码

    public void scheduleWorkInBackground() {
        WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
        Schedulers.schedule(
                workManager.getConfiguration(),
                workManager.getWorkDatabase(),
                workManager.getSchedulers());
    }

进入Schedulers.schedule方法中

public static void schedule(
            @NonNull Configuration configuration,
            @NonNull 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 {
            // Enqueued workSpecs when scheduling limits are applicable.
            //有限制的任务集合
            eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(
                    configuration.getMaxSchedulerLimit());

            // Enqueued workSpecs when scheduling limits are NOT applicable.
            //所有任务的集合
            allEligibleWorkSpecs = workSpecDao.getAllEligibleWorkSpecsForScheduling();

            if (eligibleWorkSpecsForLimitedSlots != null
                    && eligibleWorkSpecsForLimitedSlots.size() > 0) {
                long now = System.currentTimeMillis();

                // Mark all the WorkSpecs as scheduled.
                // Calls to Scheduler#schedule() could potentially result in more schedules
                // on a separate thread. Therefore, this needs to be done first.
                for (WorkSpec workSpec : eligibleWorkSpecsForLimitedSlots) {
                    workSpecDao.markWorkSpecScheduled(workSpec.id, now);
                }
            }
            workDatabase.setTransactionSuccessful();
        } finally {
            workDatabase.endTransaction();
        }

        if (eligibleWorkSpecsForLimitedSlots != null
                && eligibleWorkSpecsForLimitedSlots.size() > 0) {

            WorkSpec[] eligibleWorkSpecsArray =
                    new WorkSpec[eligibleWorkSpecsForLimitedSlots.size()];
            eligibleWorkSpecsArray =
                    eligibleWorkSpecsForLimitedSlots.toArray(eligibleWorkSpecsArray);

            // Delegate to the underlying schedulers.
            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);
            // Delegate to the underlying schedulers.
            for (Scheduler scheduler : schedulers) {
                if (!scheduler.hasLimitedSchedulingSlots()) {
                    scheduler.schedule(enqueuedWorkSpecsArray);
                }
            }
        }
    }

其实上述的代码主要是两个功能:第一:从数据库中查找出任务集合  第二:使用具体的Scheduler去执行任务

我们这里从allEligibleWorkSpec(无限制任务)入手,而执行它的Scheduler是GreedyScheduler,我们直接看它的scheduler方法,关键代码如下

        for (WorkSpec workSpec : workSpecs) {
            long nextRunTime = workSpec.calculateNextRunTime();
            long now = System.currentTimeMillis();
            if (workSpec.state == WorkInfo.State.ENQUEUED) {
                if (now < nextRunTime) {
                    // Future work
                    if (mDelayedWorkTracker != null) {
                        mDelayedWorkTracker.schedule(workSpec);
                    }
                } else if (workSpec.hasConstraints()) {
                    if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
                        // Ignore requests that have an idle mode constraint.
                        Logger.get().debug(TAG,
                                String.format("Ignoring WorkSpec %s, Requires device idle.",
                                        workSpec));
                    } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                        // Ignore requests that have content uri triggers.
                        Logger.get().debug(TAG,
                                String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
                                        workSpec));
                    } else {
                        constrainedWorkSpecs.add(workSpec);
                        constrainedWorkSpecIds.add(workSpec.id);
                    }
                } else {
                    Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
                    mWorkManagerImpl.startWork(workSpec.id);
                }
            }
        }

我们看到最后一行代码,执行了mWorkManagerImpl的startWork方法,也就是说整个任务又回到了WorkManagerImpl中,那为什么饶了上面的一大圈呢?主要是对任务存储数据库的工作及任务状态改变的工作

我们继续追根溯源,定位到了两个参数的startWork方法

    public void startWork(
            @NonNull String workSpecId,
            @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
        mWorkTaskExecutor
                .executeOnBackgroundThread(
                        new StartWorkRunnable(this, workSpecId, runtimeExtras));
    }

StartWorkRunnable中的run方法

    @Override
    public void run() {
        mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
    }

Processor中的startWork方法

    public boolean startWork(
            @NonNull String id,
            @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {

        WorkerWrapper workWrapper;
        synchronized (mLock) {
            // Work may get triggered multiple times if they have passing constraints
            // and new work with those constraints are added.
            if (isEnqueued(id)) {
                Logger.get().debug(
                        TAG,
                        String.format("Work %s is already enqueued for processing", id));
                return false;
            }

            workWrapper =
                    new WorkerWrapper.Builder(
                            mAppContext,
                            mConfiguration,
                            mWorkTaskExecutor,
                            this,
                            mWorkDatabase,
                            id)
                            .withSchedulers(mSchedulers)
                            .withRuntimeExtras(runtimeExtras)
                            .build();
            ListenableFuture<Boolean> future = workWrapper.getFuture();
            future.addListener(
                    new FutureListener(this, id, future),
                    mWorkTaskExecutor.getMainThreadExecutor());
            mEnqueuedWorkMap.put(id, workWrapper);
        }
        mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
        Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
        return true;
    }

执行了WorkWrapper,这个类实现了Runnable接口,我们看其中的run方法

    @WorkerThread
    @Override
    public void run() {
        mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
        mWorkDescription = createWorkDescription(mTags);
        runWorker();
    }

    public void runWorker(){
    ...此处省略...
            mWorkTaskExecutor.getMainThreadExecutor()
                    .execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Logger.get().debug(TAG, String.format("Starting work for %s",
                                        mWorkSpec.workerClassName));
                                mInnerFuture = mWorker.startWork();
                                future.setFuture(mInnerFuture);
                            } catch (Throwable e) {
                                future.setException(e);
                            }

                        }
                    });
    ...此处省略...
    }

我们可以看到,执行了mWorker的startWork方法,我们看Work.java的startWork方法

    @Override
    public final @NonNull ListenableFuture<Result> startWork() {
        mFuture = SettableFuture.create();
        getBackgroundExecutor().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Result result = doWork();
                    mFuture.set(result);
                } catch (Throwable throwable) {
                    mFuture.setException(throwable);
                }

            }
        });
        return mFuture;
    }

我们看到了doWork这个方法,我擦,终于找了WorkRequest中的doWork方法,使用WorkManager的小伙伴对这个doWork肯定不陌生,到这里为止呢,enqueue的过程就结束了,我画了一张图便于大家理解这个enqueue的过程

WorkManagerTaskExecutor在这个过程的作用是线程的调度

3.任务执行结果

任务执行结果这里,其实没有特别的东西需要介绍的,官网介绍了几种:RxJava,LiveData等,我个人使用的都是LiveData的方式,调用的是WorkManagerImpl中的getWorkInfoByIdLiveData方法,如下

    @Override
    public @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id) {
        WorkSpecDao dao = mWorkDatabase.workSpecDao();
        LiveData<List<WorkSpec.WorkInfoPojo>> inputLiveData =
                dao.getWorkStatusPojoLiveDataForIds(Collections.singletonList(id.toString()));
        return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData,
                new Function<List<WorkSpec.WorkInfoPojo>, WorkInfo>() {
                    @Override
                    public WorkInfo apply(List<WorkSpec.WorkInfoPojo> input) {
                        WorkInfo workInfo = null;
                        if (input != null && input.size() > 0) {
                            workInfo = input.get(0).toWorkInfo();
                        }
                        return workInfo;
                    }
                },
                mWorkTaskExecutor);
    }

我们可以看到是使用了Room数据库,Room数据库是支持LiveData的,数据改变,自然会更新到Observer的onChanged方法

二、遇到的问题

看了上面的源码,估计同学们还是有很多问题,的确,WorkManager涉及的内容比较广,上面的源码只是一个普通流程的分析,比如传值,约束,循环任务等都没有说到,所以我这里抛出几个我自己在撸源码时给自己提出的问题

1.WorkManager的约束条件是如何执行的?

查看约束条件的源码,我们要从ConstrantProxy这里开始,估计有的同学会问,你怎么找到这个类的?因为约束条件是电量,网络等,所以我猜测WorkManager应该使用的广播,所以我直接查看了WorkManger的清单文件,发现了如下的代码:

<receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
            android:enabled="false"
            android:exported="false"
            android:directBootAware="false"
            tools:targetApi="n">
            <intent-filter>
                <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
            android:enabled="false"
            android:exported="false"
            android:directBootAware="false"
            tools:targetApi="n">
            <intent-filter>
                <action android:name="android.intent.action.BATTERY_OKAY"/>
                <action android:name="android.intent.action.BATTERY_LOW"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
            android:enabled="false"
            android:exported="false"
            android:directBootAware="false"
            tools:targetApi="n">
            <intent-filter>
                <action android:name="android.intent.action.DEVICE_STORAGE_LOW"/>
                <action android:name="android.intent.action.DEVICE_STORAGE_OK"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
            android:enabled="false"
            android:exported="false"
            android:directBootAware="false"
            tools:targetApi="n">
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
            </intent-filter>
        </receiver>

电量,网络改变等的广播都是ConstraintProxy的内部类,所以我就找到了ConstraintProxy。我这里就不直接介绍源码的流程了,因为这个过程比较绕,用语言显得比较乱,我直接用流程图表示,感兴趣的同学完全可以根据流程图来找源码,我这里以网络变化为例

最后一步,startWork方法应该不陌生,在上面源码解析中,也有startWork这个方法,之后的流程和源码解析中的流程是一样的了

2.WorkManager中的约束任务和非约束任务在执行上有什么不同?

上面第一个问题所讲述的是这样的一个场景:任务执行时,条件不满足,此时任务挂起。当条件满足时,执行上述流程。

那么当任务执行时就满足条件,任务是如何执行的呢?我们先看一下第2题这个问题,约束任务和非约束任务在执行上的不同。其实不同的地方我们在源码探索时也简单说过,在Schedulers.schedule方法中。非约束条件使用的是GreedyScheduler进行任务的执行,那么约束条件呢?我们先从WorkManagerImpl的构造方法找起:

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

再进入createSchedulers方法

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    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));
    }

进入Schedulers的createBestAvailableBackgroundScheduler方法中

    @NonNull
    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;
    }

我们发现,不同的版本使用的Scheduler不一样,我们先说一下if中的代码,也就是版本号大于等于23时所使用的的Scheduler,如上述代码所写,使用是SystemJobScheduler,我们进入SystemJobScheduler内部可以看到JobScheduler这个类

private final JobScheduler mJobScheduler;

当执行scheduler()方法时,会进入scheduleInternal()方法,然后执行mSystemJobInfoConverter.convert()方法,我们就不进入源码查看了,感兴趣的同学可以自己看~。我们从convert()方法中可以看见,设置了低电量,低存储等约束条件,因为JobShceduler本身是支持这些约束条件的。所以大于等于23版本,直接使用了JobScheduler执行了约束条件任务。

我们再说一下else中的方法:tryCreateGcmBasedScheduler方法上有一个gcm,是不是很眼熟啊,是的我们在gradle中进行依赖库的时候依赖过下面的代码:

implementation "androidx.work:work-gcm:$work_version"

也就是说依赖了work-gcm的,使用的是GcmScheduler(可以进如tryCreateGcmBasedScheduler方法中查看),GcmScheduler其实和JobScheduler原理一样,算是一个支持低版本的JobScheduler,也是支持约束条件任务的执行。

如果没有依赖work-gcm的话,使用的是SystemAlarmScheduler,我们从SystemAlarmScheduler的scheduler方法中进入,然后到schedulerWorkSpec方法中

    private void scheduleWorkSpec(@NonNull WorkSpec workSpec) {
        Logger.get().debug(TAG, String.format("Scheduling work with workSpecId %s", workSpec.id));
        Intent scheduleIntent = CommandHandler.createScheduleWorkIntent(mContext, workSpec.id);
        mContext.startService(scheduleIntent);
    }

CommandHandler很眼熟对吧,第一个问题中已经说过了,流程和第一个问题中的基本一样

3.WorkManager中的一次性任务和循环任务是如何执行的?

其实这个问题比较好描述,我们可以从PeriodicWorkRequest中发现,直接把时间间隔赋值了WorkSpec中的inervalDuration和flexDuration;而一次性任务这两个字段肯定是初始值。根据这两个值来决定任务是否循环。

4.WorkManagerImpl中的实例是如何初始化的?为什么要采用这种方式?

这个问题我也不知道为啥?有懂的朋友,希望不吝赐教~(抱拳)

5.如果WorkRequest在enqueue时约束条件不符合,没有执行,之后约束条件符合后会执行吗?从源码上说为什么?

如果上面的问题都理解了的话,我相信这个问题应该很好回答了,我这里就不赘述了

WorkManager终于撸完了,有问题的小伙伴可以留言讨论~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值