美文网首页
Android Jetpack之WorkManager源码分析

Android Jetpack之WorkManager源码分析

作者: 无名长空剑_real | 来源:发表于2019-08-22 19:32 被阅读0次

    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 更新到最新版本。
    官方指导地址:官方指地址

    WorkManager的使用

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

      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方法是执行在子线程的。

    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会立即运行我们的任务。

       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处于活跃状态下才可以监听成功。

       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 对于已经正在运行或完成的任务是无法取消任务的。

    WorkManager.getInstance(this).cancelWorkById(request.id)
    

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

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

    WorkManager.getStatusesByTag() 返回该标记的所有任务的列表信息。

    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() 来创建唯一的工作序列。
    参数含义:1、工作序列的名称 2、当有相同名称序列时采取的策略方式 3、需要执行的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的初始化任务。

    1. 下面重点看一下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,我们可以确定任务的当前状态,并在任务完成后获取返回的任何值。
    下面以贴一张执行的类图信息。


    image

    任务约束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流程一致。

    相关文章

      网友评论

          本文标题:Android Jetpack之WorkManager源码分析

          本文链接:https://www.haomeiwen.com/subject/oemosctx.html