Android Jetpack之WorkManager源码分析

Android Jetpack之WorkManager源码分析

Android WorkManager简介

WorkManager 负责用来管理后台任务,它适用于须要保证系统即便应用程序退出也会运行的任务, WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行任务。若是WorkManager在应用程序运行时执行的任务,WorkManager能够在应用程序进程的新线程中运行您的任务。若是您的应用程序未运行,WorkManager会选择一种合适的方式来安排后台任务 。具体取决于设备API级别和包含的依赖项,WorkManager可能会使用 JobScheduler,Firebase JobDispatcher或AlarmManager调度任务。 为何在有了service之后,google还有出WorkManager框架呢? 1.service的滥用致使手机后台任务不断执行,耗电量大。 2.从开发者来讲,Android8.0之后,Android对于后台service管理的更加严格,应用在后台启动的服务必须是前台服务,不然会致使应用crash。固然你也能够选择下降targetSdkVersion。 3.针对targetSdkVersion Google也针对的出了一些限制。ps:2019 年起,每次发布新版本后,全部应用都必须在一年内将 Target API 更新到最新版本。 官方指导地址:官方指地址java

WorkManager的使用

官方DEMO 官方DEMO 2.1 Gradle依赖配置android

def work = "2.1.0"
    implementation"androidx.work:work-runtime:$work"
    implementation"androidx.work:work-testing:$work"
//    implementation"androidx.work:work-firebase:$work"
    implementation"androidx.work:work-runtime-ktx:$work"
复制代码

2.2 定义Worker类 自定义Worker类,继承自Worker,而后复写doWork() 方法,返回当前任务的结果 Result。doWork方法是执行在子线程的。git

class JetpackWork(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters){
    override fun doWork(): Result {
        Log.e("workermanager","work start:")
        Thread.sleep(2_000)
        Log.e("workermanager","do work thread msg :"+Thread.currentThread().name)
        return Result.success()
    }
}
复制代码

2.3 执行任务 (1)使用 OneTimeWorkRequest.Builder 建立对象Worker,将任务加入WorkManager队列。而且OneTimeWorkRequest.Builder建立的是一个单次执行的任务。 (2)将任务排入WorkManager队列,等待执行。 Worker不必定是当即执行的。WorkManager会选择适当的时间运行Worker,平衡诸如系统负载,设备是否插入等考虑因素。可是若是咱们没有指定任何约束条件,WorkManager会当即运行咱们的任务。github

var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
                    .build()
   WorkManager.getInstance(this).enqueue(request)
复制代码

2.4 重复执行任务 (1)使用PeriodicWorkRequest.Builder类建立循环任务,建立一个PeriodicWorkRequest对象 (2)而后将任务添加到WorkManager的任务队列,等待执行。 (3)最小时间间隔是15分钟。数据库

public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
复制代码
var pRequest = PeriodicWorkRequest.Builder(JetpackWork::class.java,1,TimeUnit.SECONDS).build()

 WorkManager.getInstance(this).enqueue(pRequest)

复制代码

2.4 任务的状态 经过获取LiveData查看任务的状态WorkInfo.State,只有当Activityh处于活跃状态下才能够监听成功。bash

WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this, Observer {
            Log.e("workermanager","state :"+it?.state?.name)
        })
复制代码

2.5 任务约束Constraints WorkManager 容许咱们指定任务执行的环境,好比网络已链接、电量充足时等,在知足条件的状况下任务才会执行。 (1)使用Constraints.Builder()建立并配置Constraints对象,能够指定上诉任务运行时间时的约束。 (2)建立Worker时调用setConstraints指定约束条件。网络

var constraint = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .setRequiresCharging(true).build()
            
 var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
            .setConstraints(constraint)
            .build()
复制代码

WorkManger提供了如下的约束做为Work执行的条件: (1)setRequiredNetworkType:网络链接设置 (2)setRequiresBatteryNotLow:是否为低电量时运行 默认false (3)setRequiresCharging:是否要插入设备(接入电源),默认false (4)setRequiresDeviceIdle:设备是否为空闲,默认false (5)setRequiresStorageNotLow:设备可用存储是否不低于临界阈值 2.6 取消任务 (1)从WorkRequest()获取Worker的ID (2 调用WorkManager.getInstance().cancelWorkById(workRequest.id)根据ID取消任务。 WorkManager 对于已经正在运行或完成的任务是没法取消任务的。app

WorkManager.getInstance(this).cancelWorkById(request.id)
复制代码

2.7 添加TAG 经过为WorkRequest对象分配标记字符串来对任务进行分组框架

var twoRequest = OneTimeWorkRequest.Builder(JetpackTwoWork::class.java)
            .setConstraints(constraint)
            .addTag("jetpack")
            .build()
复制代码

WorkManager.getStatusesByTag() 返回该标记的全部任务的列表信息。ide

WorkManager.getInstance(this).getWorkInfosByTag("jetpack")
复制代码

WorkManager.cancelAllWorkByTag() 取消具备特定标记的全部任务

WorkManager.getInstance(this).cancelAllWorkByTag("jetpack")
复制代码

经过获取LiveData查看具备特定标记的全部任务的状态WorkInfo.State

WorkManager.getInstance(this).getWorkInfosByTagLiveData("jetpack").observe(this, Observer { 
            
        })
复制代码

进阶使用

3.1 数据交互 WorkManager能够将参数传递给任务,并让任务返回结果。传递和返回值都是键值对形式。 (1)使用 Data.Builder建立 Data 对象,保存参数的键值对。 (2)在建立WorkQuest以前调用WorkRequest.Builder.setInputData()传递Data的实例

var requestData = Data.Builder().putString("jetpack", "workermanager").build()

        var request = OneTimeWorkRequest.Builder(JetpackWork::class.java)
            .setConstraints(constraint)
            .setInputData(requestData)
            .build()
复制代码

(3 在JetpackWork.doWork方法中经过getInputData获取传递值。 (4)在构建Data对象,跟随着任务的结果返回。

class JetpackWork(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters){
    override fun doWork(): Result {
        Log.e("workermanager","work start:"+inputData.getString("jetpack"))
        Thread.sleep(2_000)
        Log.e("workermanager","do work thread msg :"+Thread.currentThread().name)
        var outData = Data.Builder().putString("back","hi,jetpack").build()
        return Result.success(outData)
    }
}
复制代码

(5) 经过LiveData监听Worker返回的数据。

WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this, Observer {
            Log.e("workermanager", "out data :" + it?.outputData?.getString("back"))

        })
复制代码

3.2 链式任务

  1. WorkManager容许拥有多个任务的工做序列按照顺序执行任务。 ()使用该WorkManager.beginWith() 方法建立一个序列 ,并传递一个OneTimeWorkRequest对象;,该方法返回一个WorkContinuation对象。 (2)使用 WorkContinuation.then()添加剩余的任务。 (3)最后调用WorkContinuation.enqueue()将整个序列排入队列 。 若是中间有任何任务返回 Result.failure(),则整个序列结束。而且上一个任务的结果数据能够做为下一个任务的输入数据,实现任务之间的数据传递。
WorkManager.getInstance(this).beginWith(request).then(twoRequest).then(threeRequest).enqueue()
复制代码
  1. 可使用WorkContinuation.combine()方法链接多个链来建立更复杂的序列。

在这里插入图片描述
要创建这个序列,先建立两个单独的链,而后将它们链接在一块儿成为第三个链:

WorkContinuation chainAC = WorkManager.getInstance()
    .beginWith(worker A)
    .then(worker C);
WorkContinuation chainBD = WorkManager.getInstance()
    .beginWith(worker B)
    .then(worker D);
WorkContinuation chainAll = WorkContinuation
    .combine(chainAC, chainBD)
    .then(worker E);
chainAll.enqueue();
复制代码

在这种状况下,WorkManager在worker C以前运行workA,它也在workD以前运行workB, WorkB和workD都完成后,WorkManager 运行workE。 注意:虽然WorkManager依次运行每一个子链,但不能保证链1中的任务与 链2中的任务重叠,例如,workB可能在workC以前或以后运行,或者它们可能同时运行。惟一能够保证的是每一个子链中的任务将按顺序运行。 3.3 惟一的工做序列 咱们能够建立一个惟一的工做序列,在任务队列里,同一个任务只存在一个,避免任务的重复执行。经过调用 beginUniqueWork() 来建立惟一的工做序列。 参数含义:一、工做序列的名称 二、当有相同名称序列时采起的策略方式 三、须要执行的Worker

WorkManager.getInstance(this).beginUniqueWork("jetpack",ExistingWorkPolicy.APPEND,request).enqueue()
复制代码

ExistingWorkPolicy提供如下策略: (1)ExistingWorkPolicy.REPLACE:取消现有序列并将其替换为新序列 (2)ExistingWorkPolicy.KEEP:保留现有序列并忽略您的新请求 (3)ExistingWorkPolicy.APPEND:将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务。

源码分析

下面咱们带着三个问题来看代码,梳理一下WorkManager的源码 1.没有任务约束Constraints的任务是如何执行的。 2.添加任务约束Constraints的任务是如何被触发的。

4.1 WorkManager类

  1. 经过上面咱们知道任务的执行,是经过**WorkManager.getInstance(this).enqueue(request)**执行的。 WorkManager是个抽象类,经过WorkManager.getInstance方法返回的是,它的子类WorkManagerImpl的单例对象。 (1)单例模式初始化WorkManagerImpl对象 (2)调用getInstance方法返回sDelegatedInstance对象。这里sDelegatedInstance对象已经不是nulll了。下面咱们就来分析一下sDelegatedInstance的初始化过程。
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;
        }
    }

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

            return sDefaultInstance;
        }
    }
复制代码
  1. 经过反编译咱们的APP,咱们在AndroidManifest.xml文件中找到了一个provider的配置项。WorkManagerInitializer类又继承自ContentProvider,关于ContentProvider的启动过程这里不过多介绍,在应用的启动时候,会经过ActivityThread初始化ContentProvider(WorkManagerInitializer),即执行了onCreate方法。
<provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:exported="false"
            android:multiprocess="true"
            android:authorities="com.jandroid.multivideo.workmanager-init"
            android:directBootAware="false" />
复制代码

在WorkManagerInitializer的onCreate方法中调用了WorkManager.initialize的方法进行初始化。

public boolean onCreate() {
        // Initialize WorkManager with the default configuration.
        WorkManager.initialize(getContext(), new Configuration.Builder().build());
        return true;
    }
复制代码

在WorkManager.initialize内部经过调用 WorkManagerImpl.initialize(context, configuration)完成WorkManagerImpl的初始化任务。 3. 下面重点看一下WorkManagerImpl.initializ内部作了那些初始化操做。 (1)sDelegatedInstance和sDefaultInstance都不为空,说明已经初始化过,抛出异常 (2)调用WorkManagerImpl的构造方法完成初始化任务。 (3)configuration.getTaskExecutor())内部返回默认的线程池。 (4)WorkManagerTaskExecutor内部经过调用SerialExecutor实现线程的调度。

public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        synchronized (sLock) {
            if (sDelegatedInstance != null && sDefaultInstance != null) {
                throw new IllegalStateException("WorkManager is already initialized. Did you "
                        + "try to initialize it manually without disabling "
                        + "WorkManagerInitializer? See "
                        + "WorkManager#initialize(Context, Configuration) or the class level"
                        + "Javadoc for more information.");
            }

            if (sDelegatedInstance == null) {
                context = context.getApplicationContext();
                if (sDefaultInstance == null) {
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }
复制代码
private @NonNull Executor createDefaultExecutor() {
        return Executors.newFixedThreadPool(
                // This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR.
                Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)));
    }
复制代码
  1. initialize方法内部经过调用WorkManagerImpl的构造方法完成初始化任务。 (1)WorkDatabase建立数据库擦操做,内部使用的是Room框架。 (2)createSchedulers建立调度者集合。这里面主要有两种:GreedyScheduler和SystemJobScheduler(若是系统版本号大于23的话)。 (3)建立Processor类。接下来会分析该类的做用和代码。
public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            boolean useTestDatabase) {

        Context applicationContext = context.getApplicationContext();
        WorkDatabase database = WorkDatabase.create(
                applicationContext, configuration.getTaskExecutor(), useTestDatabase);
        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);
    }
复制代码
  1. createSchedulers主要是为了建立调度者集合。 (1)createBestAvailableBackgroundScheduler建立一个最有效的后台调度者。 (2)建立GreedyScheduler调度者。
public @NonNull List<Scheduler> createSchedulers(Context context, 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, taskExecutor, this));
    }
复制代码
  1. createBestAvailableBackgroundScheduler方法 (1)若是Android版本号>=23,返回SystemJobScheduler,内部主要是使用JobScheduler完成调度 (2)若是手机支持GCM,则返回GcmScheduler调度者,国内基本告别了。 (3)其余状况下返回SystemAlarmScheduler,内部使用AlarmManager实现原理。
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;
    }
复制代码

4.2 任务的入执行队列enqueue方法

  1. 经过以上分析咱们知道WorkManager.getInstance返回的是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();
    }
复制代码
  1. 直接进入WorkContinuationImpl.enqueue方法看看。 (1)建立EnqueueRunnable继承自Runnable (2)getWorkTaskExecutor获取WorkManagerTaskExecutor对象。 (3)经过以前在Configuration建立的线程池中执行EnqueueRunnable任务。
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);
            mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
            mOperation = runnable.getOperation();
        } else {
            Logger.get().warning(TAG,
                    String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
        }
        return mOperation;
    }
复制代码
  1. EnqueueRunnable类 在scheduleWorkInBackground方法中调度任务执行,内部调用Schedulers类的schedule方法,分配任务。
public void run() {
        try {
            if (mWorkContinuation.hasCycles()) {
                throw new IllegalStateException(
                        String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
            }
            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)); } } public void scheduleWorkInBackground() { WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl(); Schedulers.schedule( workManager.getConfiguration(), workManager.getWorkDatabase(), workManager.getSchedulers()); } 复制代码
  1. Schedulers类。 在调用Scheduler的schedule的掉配任务。在分析WorkManager初始化的时候,咱们知道主要有GreedyScheduler等调度类。下面重点分析一下该类。
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> eligibleWorkSpecs;
        if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
            WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
            // Delegate to the underlying scheduler.
            for (Scheduler scheduler : schedulers) {
                scheduler.schedule(eligibleWorkSpecsArray);
            }
        }
    }
复制代码
  1. GreedyScheduler类 (1)判断时候有约束条件。没有则调用startWork执行任务。有则将任务入集合
public void schedule(@NonNull WorkSpec... workSpecs) {
        registerExecutionListenerIfNeeded();

        // Keep track of the list of new WorkSpecs whose constraints need to be tracked.
        // Add them to the known list of constrained WorkSpecs and call replace() on
        // WorkConstraintsTracker. That way we only need to synchronize on the part where we
        // are updating mConstrainedWorkSpecs.
        List<WorkSpec> constrainedWorkSpecs = new ArrayList<>();
        List<String> constrainedWorkSpecIds = new ArrayList<>();
        for (WorkSpec workSpec: workSpecs) {
            if (workSpec.state == WorkInfo.State.ENQUEUED
                    && !workSpec.isPeriodic()
                    && workSpec.initialDelay == 0L
                    && !workSpec.isBackedOff()) {
                if (workSpec.hasConstraints()) {
                    // Exclude content URI triggers - we don't know how to handle them here so the // background scheduler should take care of them. if (Build.VERSION.SDK_INT < 24 || !workSpec.constraints.hasContentUriTriggers()) { constrainedWorkSpecs.add(workSpec); constrainedWorkSpecIds.add(workSpec.id); } } else { Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id)); mWorkManagerImpl.startWork(workSpec.id); } } } // onExecuted() which is called on the main thread also modifies the list of mConstrained // WorkSpecs. Therefore we need to lock here. synchronized (mLock) { if (!constrainedWorkSpecs.isEmpty()) { Logger.get().debug(TAG, String.format("Starting tracking for [%s]", TextUtils.join(",", constrainedWorkSpecIds))); mConstrainedWorkSpecs.addAll(constrainedWorkSpecs); mWorkConstraintsTracker.replace(mConstrainedWorkSpecs); } } } 复制代码
  1. WorkManagerImpl.startWork方法 仍是调用WorkManagerTaskExecutor的executeOnBackgroundThread方法执行StartWorkRunnable。
public void startWork(String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras) {
        mWorkTaskExecutor
                .executeOnBackgroundThread(
                        new StartWorkRunnable(this, workSpecId, runtimeExtras));
    }
复制代码
  1. StartWorkRunnable类 getProcessor方法的是咱们在建立WorkManager时建立的Processor对象。这里会调用Processor的startWork方法。
public void run() {
        mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
    }
复制代码
  1. Processor类 内部经过构建Work的包装类WorkerWrapper,而后再次调用WorkManagerTaskExecutor类执行WorkerWrapper任务。
public boolean startWork(String id, 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 (mEnqueuedWorkMap.containsKey(id)) {
                Logger.get().debug(
                        TAG,
                        String.format("Work %s is already enqueued for processing", id));
                return false;
            }

            workWrapper =
                    new WorkerWrapper.Builder(
                            mAppContext,
                            mConfiguration,
                            mWorkTaskExecutor,
                            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;
    }
复制代码
  1. WorkerWrapper类 (1)反射机制获取到ListenableWorker对象。其中Worker类继承自ListenableWorker类。 (2)调用ListenableWorker.startWork,其实是调用Worker类的startWork方法。 (3)在Worker类的startWork方法中又会调用doWork方法,也就是咱们复写的doWork方法。
public void run() {
        mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
        mWorkDescription = createWorkDescription(mTags);
        runWorker();
    }

    private void runWorker() {
        if (tryCheckForInterruptionAndResolve()) {
            return;
        }

        mWorkDatabase.beginTransaction();
        try {
            mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
            if (mWorkSpec == null) {
                Logger.get().error(
                        TAG,
                        String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
                resolve(false);
                return;
            }

            // Do a quick check to make sure we don't need to bail out in case this work is already // running, finished, or is blocked. if (mWorkSpec.state != ENQUEUED) { resolveIncorrectStatus(); mWorkDatabase.setTransactionSuccessful(); Logger.get().debug(TAG, String.format("%s is not in ENQUEUED state. Nothing more to do.", mWorkSpec.workerClassName)); return; } // Case 1: // Ensure that Workers that are backed off are only executed when they are supposed to. // GreedyScheduler can schedule WorkSpecs that have already been backed off because // it is holding on to snapshots of WorkSpecs. So WorkerWrapper needs to determine // if the ListenableWorker is actually eligible to execute at this point in time. // Case 2: // On API 23, we double scheduler Workers because JobScheduler prefers batching. // So is the Work is periodic, we only need to execute it once per interval. // Also potential bugs in the platform may cause a Job to run more than once. if (mWorkSpec.isPeriodic() || mWorkSpec.isBackedOff()) { long now = System.currentTimeMillis(); // Allow first run of a PeriodicWorkRequest // to go through. This is because when periodStartTime=0; // calculateNextRunTime() always > now. // For more information refer to b/124274584 boolean isFirstRun = mWorkSpec.periodStartTime == 0; if (!isFirstRun && now < mWorkSpec.calculateNextRunTime()) { Logger.get().debug(TAG, String.format( "Delaying execution for %s because it is being executed " + "before schedule.", mWorkSpec.workerClassName)); // For AlarmManager implementation we need to reschedule this kind of Work. // This is not a problem for JobScheduler because we will only reschedule // work if JobScheduler is unaware of a jobId. resolve(true); return; } } // Needed for nested transactions, such as when we're in a dependent work request when
            // using a SynchronousExecutor.
            mWorkDatabase.setTransactionSuccessful();
        } finally {
            mWorkDatabase.endTransaction();
        }

        // Merge inputs.  This can be potentially expensive code, so this should not be done inside
        // a database transaction.
        Data input;
        if (mWorkSpec.isPeriodic()) {
            input = mWorkSpec.input;
        } else {
            InputMerger inputMerger = InputMerger.fromClassName(mWorkSpec.inputMergerClassName);
            if (inputMerger == null) {
                Logger.get().error(TAG, String.format("Could not create Input Merger %s",
                        mWorkSpec.inputMergerClassName));
                setFailedAndResolve();
                return;
            }
            List<Data> inputs = new ArrayList<>();
            inputs.add(mWorkSpec.input);
            inputs.addAll(mWorkSpecDao.getInputsFromPrerequisites(mWorkSpecId));
            input = inputMerger.merge(inputs);
        }

        WorkerParameters params = new WorkerParameters(
                UUID.fromString(mWorkSpecId),
                input,
                mTags,
                mRuntimeExtras,
                mWorkSpec.runAttemptCount,
                mConfiguration.getExecutor(),
                mWorkTaskExecutor,
                mConfiguration.getWorkerFactory());

        // Not always creating a worker here, as the WorkerWrapper.Builder can set a worker override
        // in test mode.
        if (mWorker == null) {
            mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
                    mAppContext,
                    mWorkSpec.workerClassName,
                    params);
        }

        if (mWorker == null) {
            Logger.get().error(TAG,
                    String.format("Could not create Worker %s", mWorkSpec.workerClassName));
            setFailedAndResolve();
            return;
        }

        if (mWorker.isUsed()) {
            Logger.get().error(TAG,
                    String.format("Received an already-used Worker %s; WorkerFactory should return "
                            + "new instances",
                            mWorkSpec.workerClassName));
            setFailedAndResolve();
            return;
        }
        mWorker.setUsed();

        // Try to set the work to the running state.  Note that this may fail because another thread
        // may have modified the DB since we checked last at the top of this function.
        if (trySetRunning()) {
            if (tryCheckForInterruptionAndResolve()) {
                return;
            }

            final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
            // Call mWorker.startWork() on the main thread.
            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);
                            }

                        }
                    });

            // Avoid synthetic accessors.
            final String workDescription = mWorkDescription;
            future.addListener(new Runnable() {
                @Override
                @SuppressLint("SyntheticAccessor")
                public void run() {
                    try {
                        // If the ListenableWorker returns a null result treat it as a failure.
                        ListenableWorker.Result result = future.get();
                        if (result == null) {
                            Logger.get().error(TAG, String.format(
                                    "%s returned a null result. Treating it as a failure.",
                                    mWorkSpec.workerClassName));
                        } else {
                            Logger.get().debug(TAG, String.format("%s returned a %s result.",
                                    mWorkSpec.workerClassName, result));
                            mResult = result;
                        }
                    } catch (CancellationException exception) {
                        // Cancellations need to be treated with care here because innerFuture
                        // cancellations will bubble up, and we need to gracefully handle that.
                        Logger.get().info(TAG, String.format("%s was cancelled", workDescription),
                                exception);
                    } catch (InterruptedException | ExecutionException exception) {
                        Logger.get().error(TAG,
                                String.format("%s failed because it threw an exception/error",
                                        workDescription), exception);
                    } finally {
                        onWorkFinished();
                    }
                }
            }, mWorkTaskExecutor.getBackgroundExecutor());
        } else {
            resolveIncorrectStatus();
        }
    }
复制代码

小结

(1)Worker:指定咱们须要执行的任务。 WorkManager API包含一个抽象的Worker类WorkManagerImpl,咱们须要继承这个类而且在这里执行工做。 (2)WorkRequest:表明一个单独的任务。一个WorkRequest 对象指定哪一个 Woker 类应该执行该任务,并且,咱们还能够向 WorkRequest 对象添加详细信息,指定任务运行的环境等。每一个 WorkRequest 都有一个自动生成的惟一ID,咱们可使用该ID来执行诸如取消排队的任务或获取任务状态等内容。 WorkRequest 是一个抽象类,在代码中,咱们须要使用它的直接子类,OneTimeWorkRequest 或 PeriodicWorkRequest.。 (3)WorkRequest.Builder:用于建立WorkRequest对象的辅助类,一样,咱们要使用它的一个子OneTimeWorkRequest.Builder 和PeriodicWorkRequest.Builder 。 (4)Constraints:指定任务在什么时候运行(例如,“仅在链接到网络时”)。咱们能够经过Constraints.Builder 来建立Constraints对象,并在建立WorkRequest以前,将 Constraints 对象传递给 WorkRequest.Builder。 (5)WorkManager:将WorkRequest入队和管理WorkRequest。咱们要将WorkRequest对象传递给 WorkManager ,WorkManager 以这样的方式调度任务,以便分散系统资源的负载,同时遵照咱们指定的约束条件。 (6)WorkStatus:包含有关特定任务的信息。WorkManager 为每一个 WorkRequest 对象提供一个()LiveData,LiveData持有一个WorkStatus对象,经过观察LiveData,咱们能够肯定任务的当前状态,并在任务完成后获取返回的任何值。 下面以贴一张执行的类图信息。

任务约束Constraints的任务是如何被触发

  1. 结下来分析一下若是带条件约束的任务是如何被触发的。以网络变化为例分析该场景。 经过反编译咱们的APP,咱们在AndroidManifest.xml文件中找到了一个receiver的配置项。 经过action,咱们知道主要是为了监听网络的变化的。
<receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
            android:enabled="false"
            android:exported="false"
            android:directBootAware="false">

            <intent-filter>

                <action
                    android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>
复制代码
  1. NetworkStateProxy类
public static class NetworkStateProxy extends ConstraintProxy {
    }
复制代码
  1. ConstraintProxy类 在ConstraintProxy类的onReceive方法中,startService一个SystemAlarmService,其中ACTION 为ACTION_CONSTRAINTS_CHANGED。
@Override
    public void onReceive(Context context, Intent intent) {
        Logger.get().debug(TAG, String.format("onReceive : %s", intent));
        Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
        context.startService(constraintChangedIntent);
    }

  static Intent createConstraintsChangedIntent(@NonNull Context context) {
        Intent intent = new Intent(context, SystemAlarmService.class);
        intent.setAction(ACTION_CONSTRAINTS_CHANGED);
        return intent;
    }
复制代码
  1. SystemAlarmService类 内部调用mDispatcher.add方法
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        if (mIsShutdown) {
            Logger.get().info(TAG,
                    "Re-initializing SystemAlarmDispatcher after a request to shut-down.");

            // Destroy the old dispatcher to complete it's lifecycle. mDispatcher.onDestroy(); // Create a new dispatcher to setup a new lifecycle. initializeDispatcher(); // Set mIsShutdown to false, to correctly accept new commands. mIsShutdown = false; } if (intent != null) { mDispatcher.add(intent, startId); } // If the service were to crash, we want all unacknowledged Intents to get redelivered. return Service.START_REDELIVER_INTENT; } 复制代码
  1. SystemAlarmDispatcher类 内部调用processCommand方法
public boolean add(@NonNull final Intent intent, final int startId) {
        Logger.get().debug(TAG, String.format("Adding command %s (%s)", intent, startId));
        assertMainThread();
        String action = intent.getAction();
        if (TextUtils.isEmpty(action)) {
            Logger.get().warning(TAG, "Unknown command. Ignoring");
            return false;
        }

        // If we have a constraints changed intent in the queue don't add a second one. We are // treating this intent as special because every time a worker with constraints is complete // it kicks off an update for constraint proxies. if (CommandHandler.ACTION_CONSTRAINTS_CHANGED.equals(action) && hasIntentWithAction(CommandHandler.ACTION_CONSTRAINTS_CHANGED)) { return false; } intent.putExtra(KEY_START_ID, startId); synchronized (mIntents) { boolean hasCommands = !mIntents.isEmpty(); mIntents.add(intent); if (!hasCommands) { // Only call processCommand if this is the first command. // The call to dequeueAndCheckForCompletion will process the remaining commands // in the order that they were added. processCommand(); } } return true; } 复制代码
  1. processCommand方法 内部调用CommandHandler.onHandleIntent方法
private void processCommand() {
        assertMainThread();
        PowerManager.WakeLock processCommandLock =
                WakeLocks.newWakeLock(mContext, PROCESS_COMMAND_TAG);
        try {
            processCommandLock.acquire();
            // Process commands on the background thread.
            mWorkManager.getWorkTaskExecutor().executeOnBackgroundThread(new Runnable() {
                @Override
                public void run() {
                    synchronized (mIntents) {
                        mCurrentIntent = mIntents.get(0);
                    }

                    if (mCurrentIntent != null) {
                        final String action = mCurrentIntent.getAction();
                        final int startId = mCurrentIntent.getIntExtra(KEY_START_ID,
                                DEFAULT_START_ID);
                        Logger.get().debug(TAG,
                                String.format("Processing command %s, %s", mCurrentIntent,
                                        startId));
                        final PowerManager.WakeLock wakeLock = WakeLocks.newWakeLock(
                                mContext,
                                String.format("%s (%s)", action, startId));
                        try {
                            Logger.get().debug(TAG, String.format(
                                    "Acquiring operation wake lock (%s) %s",
                                    action,
                                    wakeLock));

                            wakeLock.acquire();
                            mCommandHandler.onHandleIntent(mCurrentIntent, startId,
                                    SystemAlarmDispatcher.this);
                        } catch (Throwable throwable) {
                            Logger.get().error(
                                    TAG,
                                    "Unexpected error in onHandleIntent",
                                    throwable);
                        }  finally {
                            Logger.get().debug(
                                    TAG,
                                    String.format(
                                            "Releasing operation wake lock (%s) %s",
                                            action,
                                            wakeLock));
                            wakeLock.release();
                            // Check if we have processed all commands
                            postOnMainThread(
                                    new DequeueAndCheckForCompletion(SystemAlarmDispatcher.this));
                        }
                    }
                }
            });
        } finally {
            processCommandLock.release();
        }
    }

复制代码
  1. onHandleIntent方法 onHandleIntent传入的Action 是ACTION_CONSTRAINTS_CHANGED。而后执行handleConstraintsChanged方法,在该方法内部通过一系列转化,🈶️会回到onHandleIntent方法中,并且ACTION为ACTION_DELAY_MET。
void onHandleIntent(
            @NonNull Intent intent,
            int startId,
            @NonNull SystemAlarmDispatcher dispatcher) {

        String action = intent.getAction();

        if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
            handleConstraintsChanged(intent, startId, dispatcher);
        } else if (ACTION_RESCHEDULE.equals(action)) {
            handleReschedule(intent, startId, dispatcher);
        } else {
            Bundle extras = intent.getExtras();
            if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
                Logger.get().error(TAG,
                        String.format("Invalid request for %s, requires %s.",
                                action,
                                KEY_WORKSPEC_ID));
            } else {
                if (ACTION_SCHEDULE_WORK.equals(action)) {
                    handleScheduleWorkIntent(intent, startId, dispatcher);
                } else if (ACTION_DELAY_MET.equals(action)) {
                    handleDelayMet(intent, startId, dispatcher);
                } else if (ACTION_STOP_WORK.equals(action)) {
                    handleStopWork(intent, startId, dispatcher);
                } else if (ACTION_EXECUTION_COMPLETED.equals(action)) {
                    handleExecutionCompleted(intent, startId, dispatcher);
                } else {
                    Logger.get().warning(TAG, String.format("Ignoring intent %s", intent));
                }
            }
        }
    }
复制代码
  1. DelayMetCommandHandler 通过成成调用,会调用到DelayMetCommandHandler类onAllConstraintsMet方法。在该方法内部会调用startWork方法。而startWork方法正式Processor的方法。又回到了上面分析的正常work的工做流程了。
public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
        // WorkConstraintsTracker will call onAllConstraintsMet with list of workSpecs whose
        // constraints are met. Ensure the workSpecId we are interested is part of the list
        // before we call Processor#startWork().
        if (!workSpecIds.contains(mWorkSpecId)) {
            return;
        }

        synchronized (mLock) {
            if (mCurrentState == STATE_INITIAL) {
                mCurrentState = STATE_START_REQUESTED;

                Logger.get().debug(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId));
                // Constraints met, schedule execution
                // Not using WorkManagerImpl#startWork() here because we need to know if the
                // processor actually enqueued the work here.
                boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);

                if (isEnqueued) {
                    // setup timers to enforce quotas on workers that have
                    // been enqueued
                    mDispatcher.getWorkTimer()
                            .startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this);
                } else {
                    // if we did not actually enqueue the work, it was enqueued before
                    // cleanUp and pretend this never happened.
                    cleanUp();
                }
            } else {
                Logger.get().debug(TAG, String.format("Already started work for %s", mWorkSpecId));
            }
        }
    }
复制代码

小结

实现原理就是经过监听各类约束条件变化的广播,而后通过层层转化,最终的处理逻辑和无限制条件的work流程一致。

相关文章
相关标签/搜索