美文网首页android jetpack专题
Jetpack学习之--WorkManager

Jetpack学习之--WorkManager

作者: 初夏的雪 | 来源:发表于2021-09-23 19:45 被阅读0次

    介绍

    官方学习文档

    WorkManager在后台完成异步任务,即使手机重启,app进程被关闭掉,WorkManager也会执行设置的异步任务。

    专门用来处理非及时周期性任务的,一定会执行。

    SDK版本在28 及以上才支持。

    WorkManager的四个角色:

    任务Worker:该角色主要负责执行耗时的异步操作,需要继承抽象类Worker,实现dowork抽象方法。

    约束Constraint: 该角色主要是指定了耗时异步worker执行的最佳的条件

    数据Data: 该角色是在后台耗时任务与应用进程间进行数据交互的数据模型

    Requst: 将任务、约束、数据等封装成一个执行请求,然后通过加入WorkManager 队列

    二、用法

    1、加入依赖

        //workmanager
        def work_version = "2.5.0"
        implementation "androidx.work:work-runtime:$work_version"
    

    2、Worker任务

    1) 必须继承Worker,需要重写doWork()方法;在doWork方法中做异步的操作。

    public class CustomWork extends Worker {
    
        public static final String TAG = CustomWork.class.getSimpleName();
        public CustomWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
            super(context, workerParams);
        }
    
        @NonNull
        @Override
        public Result doWork() {
            Log.e(TAG, "doWork: start");
    
            //下面是简单的异步任务
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return Result.failure();
            } finally {
                Log.e(TAG, "doWork: end");
            }
            return Result.success();
        }
    }
    

    3、创建WorkRequest

    1)单次Request

    任务执行完就结束。
    
    OneTimeWorkRequest oneTimeWorkRequest = OneTimeWorkRequest.from(CustomWork.class);
    

    2) 重复Request

    每隔多久就执行以下Worker任务。这里的时间间隔必须是大于等于15分钟,如果小于15分钟的时间间隔,WorkManager会自动间隔15分钟后执行。

     PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(CustomWork.class, 15, TimeUnit.MINUTES).build();
    

    4、添加约束

    给任务的执行添加一些约束条件,这样任务就会在约束条件满足的情况下才会执行worker,否则不执行。这样就可以确保将延迟任务在最佳条件下运行。

    1.网络约束NetworkType:约束运行工作所需的网络类

    2.低电量约束BatteryNotLow:如果设置为 true,那么当设备处于“电量不足模式”时,工作不会运行

    3.充电约束RequiresCharging:如果设置为 true,那么工作只能在设备充电时运行

    4.空闲状态约束DeviceIdle:如果设置为 true,则要求用户的设备必须处于空闲状态,才能运行工作。

    5.存储空间约束StorageNotLow:如果设置为 true,那么当用户设备上的存储空间不足时,工作不会运行。

    Constraints constraints = new Constraints.Builder()
                    .setRequiredNetworkType(NetworkType.CONNECTED)//联网中
                    .setRequiresBatteryNotLow(true)
                    .setRequiresCharging(true)//充电中
                    .setRequiresDeviceIdle(true)//空闲时
                    .build();
    
            OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(CustomWork.class)
                    .setConstraints(constraints).build();
    

    5、传递数据

    调用方传递数据给Worker:

    Data data = new Data.Builder().putString("leon", "I am from WorkManagerMainActivity").build();
    
    OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(CustomWork.class)
                    .setInputData(data).build();
    

    调用方接收Worker回传回来的数据:

     //获取任务返回的数据
     WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.getId()).observe(this, workInfo ->
                    {
                        if (workInfo.getState().isFinished()) {
                            String backleon = workInfo.getOutputData().getString("leon");
                            Log.e(TAG, "任务返回的数据:" + backleon);
                        }
                    }
            );
    

    Worker中接收数据:

     //获取传递的数据
            String leon = getInputData().getString("leon");
            Log.e(TAG, "传递进来的数据: " + leon);
    

    Worker任务执行完后回传数据:

      //返回Activity数据
            Data data = new Data.Builder().putString("leon","task is completed").build();
            return Result.success(data);
    

    6、WorkRequest入队

    单个workRequest入队:

     WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);
    

    多个顺序WorkRequest入队

     OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(TaskWork.class).build();
            OneTimeWorkRequest oneTimeWorkRequest2 = new OneTimeWorkRequest.Builder(TaskWork2.class).build();
            OneTimeWorkRequest oneTimeWorkRequest3 = new OneTimeWorkRequest.Builder(TaskWork3.class).build();
            OneTimeWorkRequest oneTimeWorkRequest4 = new OneTimeWorkRequest.Builder(TaskWork4.class).build();
    
            WorkManager.getInstance(this)
                    .beginWith(oneTimeWorkRequest)
                    .then(oneTimeWorkRequest2)
                    .then(oneTimeWorkRequest3)
                    .then(oneTimeWorkRequest4)
                    .enqueue();
    

    三、原理

    在创建WorkManager实例时,会将任务信息和约束条件都保存到Room数据库中,并添加广播接收器,将任务加入队列时,会从数据库中找到符合条件的任务开始执行,并调回到自定义的异步任务中,执行完成后将任务的状态置为success ;当约束条件成立时,通过系统的SystemAlarmService服务来进行唤醒任务继续执行。

    1、WorkManager实例化流程

    获取实例流程.png

    WorkManager.java

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

    WorkManagerImpl.java

    public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
            synchronized (sLock) {
                //这里首先会获取单例,说明一开始调用getInstance()时,有可能已经创建好了???
                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 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) {
                        //这里是创建WorkManagerImpl的单例对象,
                        sDefaultInstance = new WorkManagerImpl(
                                context,
                                configuration,
                                new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
                    }
                    sDelegatedInstance = sDefaultInstance;
                }
            }
        }
    
    

    WorkManagerTaskExecutor.java

    public WorkManagerTaskExecutor(@NonNull Executor backgroundExecutor) {
            // Wrap it with a serial executor so we have ordering guarantees on commands
            // being executed.
            mBackgroundExecutor = new SerialExecutor(backgroundExecutor);
        }
    

    SerialEecutor.java

    主要的工作:

    1、保存Task任务到队里里面,

    2、开启一个线程,将任务队列里面的任务按照顺序交个Execuor来执行

    public class SerialExecutor implements Executor {
        private final ArrayDeque<Task> mTasks;
        private final Executor mExecutor;
        private final Object mLock;
        private volatile Runnable mActive;
    
        public SerialExecutor(@NonNull Executor executor) {
            mExecutor = executor;
            mTasks = new ArrayDeque<>();
            mLock = new Object();
        }
    
        @Override
        public void execute(@NonNull Runnable command) {
            synchronized (mLock) {
                mTasks.add(new Task(this, command));
                if (mActive == null) {
                    scheduleNext();
                }
            }
        }
    
        // Synthetic access
        void scheduleNext() {
            synchronized (mLock) {
                if ((mActive = mTasks.poll()) != null) {
                    mExecutor.execute(mActive);
                }
            }
        }
    
        /**
         * @return {@code true} if there are tasks to execute in the queue.
         */
        public boolean hasPendingTasks() {
            synchronized (mLock) {
                return !mTasks.isEmpty();
            }
        }
    
        @NonNull
        @VisibleForTesting
        public Executor getDelegatedExecutor() {
            return mExecutor;
        }
    
        /**
         * A {@link Runnable} which tells the {@link SerialExecutor} to schedule the next command
         * after completion.
         */
        static class Task implements Runnable {
            final SerialExecutor mSerialExecutor;
            final Runnable mRunnable;
    
            Task(@NonNull SerialExecutor serialExecutor, @NonNull Runnable runnable) {
                mSerialExecutor = serialExecutor;
                mRunnable = runnable;
            }
    
            @Override
            public void run() {
                try {
                    mRunnable.run();
                } finally {
                    mSerialExecutor.scheduleNext();
                }
            }
        }
    }
    

    WorkManagerImpl.java构造方法

        public WorkManagerImpl(
                @NonNull Context context,
                @NonNull Configuration configuration,
                @NonNull TaskExecutor workTaskExecutor,
                boolean useTestDatabase) {
            this(context,
                    configuration,
                    workTaskExecutor,
                    WorkDatabase.create(
                            context.getApplicationContext(),
                            workTaskExecutor.getBackgroundExecutor(),
                            useTestDatabase)
            );
        }
    

    WorkDataBase.java

    创建Room数据库,将任务信息全部保存到数据库中,并给数据库增加一些配置升级等信息。

    public static WorkDatabase create(
                @NonNull final Context context,
                @NonNull Executor queryExecutor,
                boolean useTestDatabase) {
            RoomDatabase.Builder<WorkDatabase> builder;
            if (useTestDatabase) {
                builder = Room.inMemoryDatabaseBuilder(context, WorkDatabase.class)
                        .allowMainThreadQueries();
            } else {
                String name = WorkDatabasePathHelper.getWorkDatabaseName();
                builder = Room.databaseBuilder(context, WorkDatabase.class, name);
                builder.openHelperFactory(new SupportSQLiteOpenHelper.Factory() {
                    @NonNull
                    @Override
                    public SupportSQLiteOpenHelper create(
                            @NonNull SupportSQLiteOpenHelper.Configuration configuration) {
                        SupportSQLiteOpenHelper.Configuration.Builder configBuilder =
                                SupportSQLiteOpenHelper.Configuration.builder(context);
                        configBuilder.name(configuration.name)
                                .callback(configuration.callback)
                                .noBackupDirectory(true);
                        FrameworkSQLiteOpenHelperFactory factory =
                                new FrameworkSQLiteOpenHelperFactory();
                        return factory.create(configBuilder.build());
                    }
                });
            }
    
            return builder.setQueryExecutor(queryExecutor)
                    .addCallback(generateCleanupCallback())
                    .addMigrations(WorkDatabaseMigrations.MIGRATION_1_2)
                    .addMigrations(
                            new WorkDatabaseMigrations.RescheduleMigration(context, VERSION_2,
                                    VERSION_3))
                    .addMigrations(MIGRATION_3_4)
                    .addMigrations(MIGRATION_4_5)
                    .addMigrations(
                            new WorkDatabaseMigrations.RescheduleMigration(context, VERSION_5,
                                    VERSION_6))
                    .addMigrations(MIGRATION_6_7)
                    .addMigrations(MIGRATION_7_8)
                    .addMigrations(MIGRATION_8_9)
                    .addMigrations(new WorkDatabaseMigrations.WorkMigration9To10(context))
                    .addMigrations(
                            new WorkDatabaseMigrations.RescheduleMigration(context, VERSION_10,
                                    VERSION_11))
                    .fallbackToDestructiveMigration()
                    .build();
        }
    

    2、将Worker加入enqueue流程

    enqueue流程.png

    WorkManagerImpl.java

        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();
        }
    
      public void startWork(
                @NonNull String workSpecId,
                @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
            mWorkTaskExecutor
                    .executeOnBackgroundThread(
                            new StartWorkRunnable(this, workSpecId, runtimeExtras));
        }
    

    WorkContinuationImpl.java

        @Override
        public @NonNull Operation enqueue() {
            // Only enqueue if not already enqueued.
            if (!mEnqueued) {
                // The runnable walks the hierarchy of the continuations
                // and marks them enqueued using the markEnqueued() method, parent first.
                EnqueueRunnable runnable = new EnqueueRunnable(this);
                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.java

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

    Schedulers.java

    public static void schedule(
                @NonNull Configuration configuration,
                @NonNull WorkDatabase workDatabase,
                List<Scheduler> schedulers) {
            if (schedulers == null || schedulers.size() == 0) {
                return;
            }
    
            WorkSpecDao workSpecDao = workDatabase.workSpecDao();
            List<WorkSpec> eligibleWorkSpecsForLimitedSlots;
            List<WorkSpec> allEligibleWorkSpecs;
    
            workDatabase.beginTransaction();
            try {
                // Enqueued workSpecs when scheduling limits are applicable.
                eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(
                        configuration.getMaxSchedulerLimit());
    
                // Enqueued workSpecs when scheduling limits are NOT applicable.
                allEligibleWorkSpecs = workSpecDao.getAllEligibleWorkSpecsForScheduling(
                        MAX_GREEDY_SCHEDULER_LIMIT);
    
                if (eligibleWorkSpecsForLimitedSlots != null
                        && eligibleWorkSpecsForLimitedSlots.size() > 0) {
                    long now = System.currentTimeMillis();
    
                    // Mark all the WorkSpecs as scheduled.
                    // Calls to Scheduler#schedule() could potentially result in more schedules
                    // on a separate thread. Therefore, this needs to be done first.
                    for (WorkSpec workSpec : eligibleWorkSpecsForLimitedSlots) {
                        workSpecDao.markWorkSpecScheduled(workSpec.id, now);
                    }
                }
                workDatabase.setTransactionSuccessful();
            } finally {
                workDatabase.endTransaction();
            }
    
            if (eligibleWorkSpecsForLimitedSlots != null
                    && eligibleWorkSpecsForLimitedSlots.size() > 0) {
    
                WorkSpec[] eligibleWorkSpecsArray =
                        new WorkSpec[eligibleWorkSpecsForLimitedSlots.size()];
                eligibleWorkSpecsArray =
                        eligibleWorkSpecsForLimitedSlots.toArray(eligibleWorkSpecsArray);
    
                // Delegate to the underlying schedulers.
                for (Scheduler scheduler : schedulers) {
                    if (scheduler.hasLimitedSchedulingSlots()) {
                        scheduler.schedule(eligibleWorkSpecsArray);
                    }
                }
            }
    
            if (allEligibleWorkSpecs != null && allEligibleWorkSpecs.size() > 0) {
                WorkSpec[] enqueuedWorkSpecsArray = new WorkSpec[allEligibleWorkSpecs.size()];
                enqueuedWorkSpecsArray = allEligibleWorkSpecs.toArray(enqueuedWorkSpecsArray);
                // Delegate to the underlying schedulers.
                for (Scheduler scheduler : schedulers) {
                    if (!scheduler.hasLimitedSchedulingSlots()) {
                        scheduler.schedule(enqueuedWorkSpecsArray);
                    }
                }
            }
        }
    

    GreedyScheduler.java

     public void schedule(@NonNull WorkSpec... workSpecs) {
            if (mInDefaultProcess == null) {
                checkDefaultProcess();
            }
    
            if (!mInDefaultProcess) {
                Logger.get().info(TAG, "Ignoring schedule request in a secondary process");
                return;
            }
    
            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.
            Set<WorkSpec> constrainedWorkSpecs = new HashSet<>();
            Set<String> constrainedWorkSpecIds = new HashSet<>();
    
            for (WorkSpec workSpec : workSpecs) {
                long nextRunTime = workSpec.calculateNextRunTime();
                long now = System.currentTimeMillis();
                if (workSpec.state == WorkInfo.State.ENQUEUED) {
                    if (now < nextRunTime) {
                        // Future work
                        if (mDelayedWorkTracker != null) {
                            mDelayedWorkTracker.schedule(workSpec);
                        }
                    } else if (workSpec.hasConstraints()) {
                        if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
                            // Ignore requests that have an idle mode constraint.
                            Logger.get().debug(TAG,
                                    String.format("Ignoring WorkSpec %s, Requires device idle.",
                                            workSpec));
                        } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                            // Ignore requests that have content uri triggers.
                            Logger.get().debug(TAG,
                                    String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
                                            workSpec));
                        } else {
                            constrainedWorkSpecs.add(workSpec);
                            constrainedWorkSpecIds.add(workSpec.id);
                        }
                    } else {
                        Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
                        //这里就回到WorkManagerImpl.java的startWork()
                        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);
                }
            }
        }
    

    StartWorkRunnable.java

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

    Processor.java

    public boolean startWork(
                @NonNull String id,
                @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
    
            WorkerWrapper workWrapper;
            synchronized (mLock) {
                // Work may get triggered multiple times if they have passing constraints
                // and new work with those constraints are added.
                if (isEnqueued(id)) {
                    Logger.get().debug(
                            TAG,
                            String.format("Work %s is already enqueued for processing", id));
                    return false;
                }
    
                workWrapper =
                        new WorkerWrapper.Builder(
                                mAppContext,
                                mConfiguration,
                                mWorkTaskExecutor,
                                this,
                                mWorkDatabase,
                                id)
                                .withSchedulers(mSchedulers)
                                .withRuntimeExtras(runtimeExtras)
                                .build();
                ListenableFuture<Boolean> future = workWrapper.getFuture();
                future.addListener(
                        new FutureListener(this, id, future),
                        mWorkTaskExecutor.getMainThreadExecutor());
                mEnqueuedWorkMap.put(id, workWrapper);
            }
            mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
            Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
            return true;
        }
    

    WorkWrapper.java

     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);
                    mWorkDatabase.setTransactionSuccessful();
                    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);
                        mWorkDatabase.setTransactionSuccessful();
                        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 {
                InputMergerFactory inputMergerFactory = mConfiguration.getInputMergerFactory();
                String inputMergerClassName = mWorkSpec.inputMergerClassName;
                InputMerger inputMerger =
                        inputMergerFactory.createInputMergerWithDefaultFallback(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(),
                    new WorkProgressUpdater(mWorkDatabase, mWorkTaskExecutor),
                    new WorkForegroundUpdater(mWorkDatabase, mForegroundProcessor, mWorkTaskExecutor));
    
            // 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));
                                    //这里会调用Worker的startWork()
                                    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();
            }
        }
    

    Worker.java

        public final @NonNull ListenableFuture<Result> startWork() {
            mFuture = SettableFuture.create();
            getBackgroundExecutor().execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //这里就回调到自定义的worker的dowork()方法中了
                        Result result = doWork();
                        mFuture.set(result);
                    } catch (Throwable throwable) {
                        mFuture.setException(throwable);
                    }
    
                }
            });
            return mFuture;
        }
    

    总结

    技术点:

        建造者模式、ROOM数据库存放任务信息、线程同步,同步锁机制,状态机
    

    相关文章

      网友评论

        本文标题:Jetpack学习之--WorkManager

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