JetPack WorkManager源码分析

前言

Android中执行后台任务有JobScheduler、Loader、Service等方案 ,WorkManager的出现用来替换以上所有的 Android 后台任务方案,为后台任务提供了一套统一的解决方案,保证了api的一致性和稳定性,同时谷歌在开发WorkManager时也考虑到后台任务对电池续航的影响。WorkManager 能保证任务一定会执行,即使用户导航离开屏幕、退出应用或重启设备也不影响。

基本使用

使用 WorkManager 需要继承 Worker 类,后台任务在doWork() 方法中实现

doWork() 方法的返回值Resutl有三种类型:

  • Result.success():执行成功。
  • Result.failure():执行失败。
  • Result.retry():执行失败,重试。
class MainWorker (context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {

    companion object { const val TAG = "MainWorker" }

    @SuppressLint("RestrictedApi")
    override fun doWork(): Result {
        Log.d(TAG, "MainWorker doWork: 后台任务执行了")
        return Result.Success() // doWork成功 执行任务完毕
    }
}

定义任务后,可以使用Constraints设置约束条件,例如定义手机必须联网、必须在充电中、必须是空闲时才执行该后台任务,最后须用 WorkManager 进行调度该任务才能运行。更多进阶使用可以看这里

    fun testBackgroundWork(view: View?) {
        //设置约束条件
        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED) // 必须是联网中
            .setRequiresCharging(true) // 必须是充电中
            .setRequiresDeviceIdle(true) // 必须是空闲时
            .build()

        // 请求对象
        val request = OneTimeWorkRequest.Builder(MainWorker::class.java)
            .setConstraints(constraints) // Request 关联 约束条件
            .build()

        // 加入队列
        WorkManager.getInstance(this).enqueue(request)
    }

源码

1.初始化

上面例子中是通过WorkManager.getInstance获得单例对象,然后再执行后台任务,看一下这个方法的源码。

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

getInstance方法会调用到WorkManager的实现类WorkManagerImpl中,默认情况下这里第一行代码其实会直接返回,因为getInstance不为空,初始化工作在app启动时就通过ContentProvider完成了。

 <provider
      android:name="androidx.work.impl.WorkManagerInitializer"
      android:exported="false"
      android:multiprocess="true"
      android:authorities="com.derry.workmanager.workmanager-init"
      android:directBootAware="false" />
public class WorkManagerInitializer extends ContentProvider {
    @Override
    public boolean onCreate() {
        // Initialize WorkManager with the default configuration.
        WorkManager.initialize(getContext(), new Configuration.Builder().build());
        return true;
    }
}

找到编译后的build文件夹中的apk,查看其清单文件可以发现注册了一个provider,这个类的onCreate方法中就调用了WorkManager的初始化方法,我们知道ContentProvider在Application生命周期方法onCreate之前就会初始化,所以WorkManager也在这时就被初始化了。

    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        synchronized (sLock) {
            ...
            if (sDelegatedInstance == null) {
                context = context.getApplicationContext();
                if (sDefaultInstance == null) {
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }

initialize方法里,configuration.getTaskExecutor()内部返回一个固定线程数量的线程池,创建WorkManagerTaskExecutor用来执行线程池的任务,然后调用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, workTaskExecutor);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
    }

WorkManagerImpl构造方法中做了以下几件事

1.创建了Room数据库,Room也是对sqlite的再封装,数据库用来记录每一个后台任务的信息,包括执行顺序 、执行时间等信息;

2.创建schedulers调度器,主要有三种GreedyScheduler、SystemAlarmScheduler、SystemJobScheduler,根据系统版本来选择用哪种;

3.创建Processor处理器,Processor用来管理Schedulers的执行,启动或停止任务;

2.任务入队方法enqueue

 进入WorkManagerImpl.enqueue方法,创建一个WorkContinuationImpl对象执行enqueue方法。

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

WorkContinuationImpl.enqueue方法,创建EnqueueRunnable并在后台线程池中执行

    public @NonNull Operation enqueue() {
        if (!mEnqueued) {
            EnqueueRunnable runnable = new EnqueueRunnable(this);
            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.run方法,将workSpec添加到数据库并对任务状态进行校验,将RescheduleReceiver注册到AndroidManifest.xml内,在scheduleWorkInBackground方法中执行调度工作

    public void run() {
        try {
            if (mWorkContinuation.hasCycles()) {
                throw new IllegalStateException(
                        String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
            }
            //将任务信息添加到数据库
            boolean needsScheduling = addToDatabase();
            if (needsScheduling) {
                //启用 RescheduleReceiver,仅当有需要调度的Worker时。
                final Context context =
                        mWorkContinuation.getWorkManagerImpl().getApplicationContext();
                //允许将RescheduleReceiver注册到AndroidManifest.xml内
                PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
                scheduleWorkInBackground();
            }
            mOperation.setState(Operation.SUCCESS);
        } catch (Throwable exception) {
            mOperation.setState(new Operation.State.FAILURE(exception));
        }
    }

3.Schedulers调度器

EnqueueRunnable. scheduleWorkInBackground方法,调用了Schedulers.schedule方法,传入了Configuration, WorkDatabase, Schedulers三个对象

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

Schedulers.schedule方法,先进行了一系列的数据库操作,主要是查询数据库中未执行的任务,然后根据条件对每个任务进行调度。Schedulers将任务交给每一个Scheduler去处理,GreedyScheduler会先处理这个任务。

    public static void schedule(
            @NonNull Configuration configuration,
            @NonNull WorkDatabase workDatabase,
            List<Scheduler> schedulers) {
        List<WorkSpec> eligibleWorkSpecs;
        ...
        if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
            WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
            // 交给底层调度器去调度
            for (Scheduler scheduler : schedulers) {
                scheduler.schedule(eligibleWorkSpecsArray);
            }
        }
    }

GreedyScheduler类,判断是否有约束条件,有则将任务入集合,没有则调用startWork方法。假如有低电量的约束条件,则会在清单文件生成名为BatteryNotLowProxy的receiver,实现原理就是通过监听约束条件变化的广播,然后通过一系列处理,最后也是调用到startWork方法,其他约束条件也是大同小异。

    public void schedule(@NonNull WorkSpec... workSpecs) {
        ...
        for (WorkSpec workSpec : workSpecs) {
            if (workSpec.state == WorkInfo.State.ENQUEUED
                    && !workSpec.isPeriodic()
                    && workSpec.initialDelay == 0L
                    && !workSpec.isBackedOff()) {
                
                if (workSpec.hasConstraints()) {
                    ...
                    //有约束条件
                    constrainedWorkSpecs.add(workSpec);
                    constrainedWorkSpecIds.add(workSpec.id);
                } else {
                    //无约束条件
                    mWorkManagerImpl.startWork(workSpec.id);
                }
            }
        }
        ...
    }

WorkManagerImpl.startWork方法,WorkTaskExecutor执行了Runnnable,下面进入 StartWorkRunnable的run()的实现

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

 StartWorkRunnable.run方法,将任务的信息交给Processor,由Processor调用startWork()去执行任务

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

4.Processor处理器

processor处理器可以智能地按需安排和执行后台任务

processor.startWork方法,这里会将任务包装成一个WorkWrapper,WorkWrapper也是一个Runnable,然后再次调用WorkTaskExecutor执行Runnnable。

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

        WorkerWrapper workWrapper;
        synchronized (mLock) {
            ...
            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);
        }
        //执行Ruunable
        mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
        return true;
    }

WorkWrapper类的run方法,通过反射获取到ListenableWorker对象也就是Worker类的父类,然后调用ListenableWorker.startWork方法,也就是调用Worker类的startWork方法。

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

    private void runWorker() {
        ...
        //反射获取ListenableWorker对象
        if (mWorker == null) {
            mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
                    mAppContext,
                    mWorkSpec.workerClassName,
                    params);
        }
        if (trySetRunning()) {
            if (tryCheckForInterruptionAndResolve()) {
                return;
            }

            final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
            
            mWorkTaskExecutor.getMainThreadExecutor()
                    .execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Logger.get().debug(TAG, String.format("Starting work for %s",
                                        mWorkSpec.workerClassName));
                                //这里调用startWork方法
                                mInnerFuture = mWorker.startWork();
                                future.setFuture(mInnerFuture);
                            } catch (Throwable e) {
                                future.setException(e);
                            }

                        }
                    });
    }

Worker.startWork方法,这里即调用到我们自己实现的doWork方法,至此整个流程结束。

    public abstract @NonNull Result doWork();

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

小结

WorkManager是一个很优秀的框架使用方法也很简单,适用于可延迟执行的任务,即使应用程序或设备重新启动也能保证一定会执行。WorkManager会针对不同Android版本的选择不同的执行策略,所以可以的话推荐使用WorkManager替代原有的方案。

以上不足之处请指正,欢迎留言,谢谢。

上一篇:Android ShapeableImageView使用详解,告别shape、三方库


下一篇:SQL编程:存储过程+事务处理+回滚+数据迁移