preface
Background tasks in Android include JobScheduler, Loader, Service and other schemes , The emergence of WorkManager is used to replace all the above Android background task schemes, providing a unified solution for background tasks and ensuring the consistency and stability of api. At the same time, Google also takes into account the impact of background tasks on battery life when developing WorkManager. WorkManager can ensure that the task will be executed, even if the user navigates away from the screen, exits the application or restarts the device.
Basic use
Inheritance is required to use WorkManager Worker Class, background task in doWork() Method
doWork() There are three types of the return value Resutl of the method:
- Result.success(): execution succeeded.
- Result.failure(): execution failed.
- Result.retry(): execution failed, please try again.
class MainWorker (context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) { companion object { const val TAG = "MainWorker" } @SuppressLint("RestrictedApi") override fun doWork(): Result { Log.d(TAG, "MainWorker doWork: The background task is executed") return Result.Success() // doWork successfully executed the task } }
After defining a task, you can use Constraints to set Constraints. For example, you can define that the background task can only be executed when the mobile phone must be connected to the Internet, must be charging, and must be idle. Finally, you must schedule the task with WorkManager to run. More advanced use can be seen here.
fun testBackgroundWork(view: View?) { //Set constraints val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) // Must be networked .setRequiresCharging(true) // Must be charging .setRequiresDeviceIdle(true) // Must be idle .build() // Request object val request = OneTimeWorkRequest.Builder(MainWorker::class.java) .setConstraints(constraints) // Request Association constraints .build() // Join queue WorkManager.getInstance(this).enqueue(request) }
Source code
1. Initialization
In the above example, the singleton object is obtained through WorkManager.getInstance, and then the background task is executed. Take a look at the source code of this method.
public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) { synchronized (sLock) { WorkManagerImpl instance = getInstance();//It is not empty here. Return directly 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; } }
The getInstance method will be called into the WorkManagerImpl implementation class of WorkManager. By default, the first line of code here will actually be returned directly, because getInstance is not empty, and the initialization is completed through the ContentProvider when the app is started.
<provider android:name="androidx.work.impl.WorkManagerInitializer" android:exported="false" android:multiprocess="true" android:authorities="com.derry.workmanager.workmanager-init" android:directBootAware="false" />
public class WorkManagerInitializer extends ContentProvider { @Override public boolean onCreate() { // Initialize WorkManager with the default configuration. WorkManager.initialize(getContext(), new Configuration.Builder().build()); return true; } }
Find apk in the compiled build folder and check its manifest file. You can find that a provider is registered. The onCreate method of this class calls the initialization method of WorkManager. We know that ContentProvider will be initialized before the Application lifecycle method onCreate, so WorkManager will be initialized at this time.
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) { synchronized (sLock) { ... if (sDelegatedInstance == null) { context = context.getApplicationContext(); if (sDefaultInstance == null) { sDefaultInstance = new WorkManagerImpl( context, configuration, new WorkManagerTaskExecutor(configuration.getTaskExecutor())); } sDelegatedInstance = sDefaultInstance; } } }
In the initialize method, configuration.getTaskExecutor() returns the thread pool of a fixed thread number internally, creates WorkManagerTaskExecutor to execute the thread pool's task, and then calls WorkManagerImpl's construction method.
public WorkManagerImpl( @NonNull Context context, @NonNull Configuration configuration, @NonNull TaskExecutor workTaskExecutor, @NonNull WorkDatabase database) { Context applicationContext = context.getApplicationContext(); Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel())); List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor); Processor processor = new Processor( context, configuration, workTaskExecutor, database, schedulers); internalInit(context, configuration, workTaskExecutor, database, schedulers, processor); }
The WorkManagerImpl constructor does the following
1. The Room database is created. The Room is also a re encapsulation of sqlite. The database is used to record the information of each background task, including execution sequence, execution time and so on;
2. Create schedulers. There are three main schedulers: GreedyScheduler, SystemAlarmScheduler and SystemJobScheduler. Choose which one to use according to the system version;
3. Create a Processor, which is used to manage the execution of Schedulers and start or stop tasks;
2. Task queue method enqueue
Enter the WorkManagerImpl.enqueue method, create a WorkContinuationImpl object and execute the enqueue method.
public Operation enqueue( @NonNull List<? extends WorkRequest> workRequests) { // This error is not being propagated as part of the Operation, as we want the // app to crash during development. Having no workRequests is always a developer error. if (workRequests.isEmpty()) { throw new IllegalArgumentException( "enqueue needs at least one WorkRequest."); } return new WorkContinuationImpl(this, workRequests).enqueue(); }
WorkContinuationImpl.enqueue method, create EnqueueRunnable and execute it in the background thread pool
public @NonNull Operation enqueue() { if (!mEnqueued) { EnqueueRunnable runnable = new EnqueueRunnable(this); mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable); mOperation = runnable.getOperation(); } else { Logger.get().warning(TAG, String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds))); } return mOperation; }
EnqueueRunnable.run method, add the workSpec to the database and verify the task status, register the RescheduleReceiver in AndroidManifest.xml, and execute scheduling in the scheduleWorkInBackground method
public void run() { try { if (mWorkContinuation.hasCycles()) { throw new IllegalStateException( String.format("WorkContinuation has cycles (%s)", mWorkContinuation)); } //Add task information to database boolean needsScheduling = addToDatabase(); if (needsScheduling) { //Enable RescheduleReceiver, only when there are workers to be scheduled. final Context context = mWorkContinuation.getWorkManagerImpl().getApplicationContext(); //Allows the RescheduleReceiver to be registered in AndroidManifest.xml PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true); scheduleWorkInBackground(); } mOperation.setState(Operation.SUCCESS); } catch (Throwable exception) { mOperation.setState(new Operation.State.FAILURE(exception)); } }
3.Schedulers scheduler
EnqueueRunnable. scheduleWorkInBackground method, called Schedulers.schedule method, and passed in three objects: configuration, workdatabase and schedulers
public void scheduleWorkInBackground() { WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl(); Schedulers.schedule( workManager.getConfiguration(), workManager.getWorkDatabase(), workManager.getSchedulers()); }
Schedulers.schedule method first performs a series of database operations, mainly querying the unexecuted tasks in the database, and then scheduling each task according to conditions. Schedulers hand over the task to each Scheduler, and greedy Scheduler will handle the task first.
public static void schedule( @NonNull Configuration configuration, @NonNull WorkDatabase workDatabase, List<Scheduler> schedulers) { List<WorkSpec> eligibleWorkSpecs; ... if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) { WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]); // Give it to the underlying scheduler for scheduling for (Scheduler scheduler : schedulers) { scheduler.schedule(eligibleWorkSpecsArray); } } }
The GreedyScheduler class determines whether there are constraints. If there are constraints, the task will be collected. If not, the startWork method will be called. If there are low-power constraints, a receiver named BatteryNotLowProxy will be generated in the list file. The implementation principle is to listen to the broadcast of constraint changes, then through a series of processing, and finally call the startWork method. Other constraints are similar.
public void schedule(@NonNull WorkSpec... workSpecs) { ... for (WorkSpec workSpec : workSpecs) { if (workSpec.state == WorkInfo.State.ENQUEUED && !workSpec.isPeriodic() && workSpec.initialDelay == 0L && !workSpec.isBackedOff()) { if (workSpec.hasConstraints()) { ... //With constraints constrainedWorkSpecs.add(workSpec); constrainedWorkSpecIds.add(workSpec.id); } else { //No constraints mWorkManagerImpl.startWork(workSpec.id); } } } ... }
The WorkManagerImpl.startWork method and the WorkTaskExecutor executed runnable. Next, enter the implementation of run() of StartWorkRunnable
public void startWork( @NonNull String workSpecId, @Nullable WorkerParameters.RuntimeExtras runtimeExtras) { mWorkTaskExecutor .executeOnBackgroundThread( new StartWorkRunnable(this, workSpecId, runtimeExtras)); }
The StartWorkRunnable.run method gives the task information to the Processor, which calls startWork() to execute the task
public void run() { mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras); }
4.Processor
processor processors can intelligently schedule and execute background tasks on demand
processor.startWork method, where the task will be wrapped into a WorkWrapper, which is also a Runnable, and then call the WorkTaskExecutor again to execute Runnnable.
public boolean startWork(@NonNull String id,@Nullable WorkerParameters.RuntimeExtras runtimeExtras) { WorkerWrapper workWrapper; synchronized (mLock) { ... workWrapper = new WorkerWrapper.Builder( mAppContext, mConfiguration, mWorkTaskExecutor, this, mWorkDatabase, id) .withSchedulers(mSchedulers) .withRuntimeExtras(runtimeExtras) .build(); ListenableFuture<Boolean> future = workWrapper.getFuture(); future.addListener( new FutureListener(this, id, future), mWorkTaskExecutor.getMainThreadExecutor()); mEnqueuedWorkMap.put(id, workWrapper); } //Execute Ruunable mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper); return true; }
The WorkWrapper class run method gets the ListenableWorker object by reflection, that is the parent class of the Worker class, then calls the ListenableWorker.startWork method, that is, calling the startWork method of the Worker class.
public void run() { mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId); mWorkDescription = createWorkDescription(mTags); runWorker(); } private void runWorker() { ... //Get ListenableWorker object by reflection if (mWorker == null) { mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( mAppContext, mWorkSpec.workerClassName, params); } if (trySetRunning()) { if (tryCheckForInterruptionAndResolve()) { return; } final SettableFuture<ListenableWorker.Result> future = SettableFuture.create(); mWorkTaskExecutor.getMainThreadExecutor() .execute(new Runnable() { @Override public void run() { try { Logger.get().debug(TAG, String.format("Starting work for %s", mWorkSpec.workerClassName)); //The startWork method is called here mInnerFuture = mWorker.startWork(); future.setFuture(mInnerFuture); } catch (Throwable e) { future.setException(e); } } }); }
Worker.startWork method, here we call the doWork method implemented by ourselves, and the whole process ends.
public abstract @NonNull Result doWork(); public final @NonNull ListenableFuture<Result> startWork() { mFuture = SettableFuture.create(); getBackgroundExecutor().execute(new Runnable() { @Override public void run() { try { Result result = doWork(); mFuture.set(result); } catch (Throwable throwable) { mFuture.setException(throwable); } } }); return mFuture; }
Summary
WorkManager is an excellent framework and easy to use. It is suitable for tasks that can be delayed. Even if the application or device is restarted, it can be guaranteed to be executed. WorkManager will select different execution strategies for different Android versions, so it is recommended to use WorkManager instead of the original scheme if possible.
Please correct the above shortcomings. Welcome to leave a message. Thank you.