preface

Jetpack provides workManagers to perform some tasks, and since executing tasks involves task creation, task properties, task execution conditions, how to perform tasks, and so on. This article demystifies workManager through task attributes, task constraints, and how to perform tasks

Task Creation (Task Attributes)

// Task definition
class UploadWorker(conText: Context, params: WorkerParameters) : Worker(conText, params) {

    override fun doWork(a): Result {
        var name = inputData.getString("name")
        Log.d("UploadWorker ==" ,"Dowork performed"+name)
        Toast.maketext (applicationContext, "execute requestManager", toast.length_long).show()
        return Result.retry() //Result.failure() Result.retry()}}Copy the code
As you can see, UploadWorker inherits Worker, which in turn inherits ListenableWorker, passing in two parameters: context and WorkerParameters
public abstract class Worker extends ListenableWorker {

    // Package-private to avoid synthetic accessor.
    SettableFuture<Result> mFuture;

    @Keep
    @SuppressLint("BanKeepAnnotation")
    public Worker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @WorkerThread
    public abstract @NonNull Result doWork();

    // The startWork method executes the doWork method in the thread pool and sets the return value of the doWork method to return in the Future
    @Override
    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); }}});returnmFuture; }}Copy the code

The Worker code is also simple, exposing the doWork () method for subclasses to implement, and then providing the startWork method to perform doWork with a thread pool and put the return value into a SettableFuture.

//ListenableWorker
public abstract class ListenableWorker {
    / / context
    private @NonNull Context mAppContext;
    // Pass in parameters
    private @NonNull WorkerParameters mWorkerParams;
    // Whether to pause
    private volatile boolean mStopped;
    // Whether it is used
    private boolean mUsed;
    // Whether to run in foreground
    private boolean mRunInForeground;
    // The constructor context and WorkerParameters cannot be null; an exception is thrown when null
    public ListenableWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {
        // Actually make sure we don't get nulls.
        if (appContext == null) {
            throw new IllegalArgumentException("Application Context is null");
        }

        if (workerParams == null) {
            throw new IllegalArgumentException("WorkerParameters is null");
        }

        mAppContext = appContext;
        mWorkerParams = workerParams;
    }
    
    public final @NonNull Context getApplicationContext() {
        return mAppContext;
    }

    public final @NonNull UUID getId() {
        return mWorkerParams.getId();
    }

    public final @NonNull Data getInputData() {
        return mWorkerParams.getInputData();
    }

    public final @NonNull Set<String> getTags() {
        return mWorkerParams.getTags();
    }

    @RequiresApi(24)
    public final @NonNull List<Uri> getTriggeredContentUris() {
        return mWorkerParams.getTriggeredContentUris();
    }

    @RequiresApi(24)
    public final @NonNull List<String> getTriggeredContentAuthorities() {
        return mWorkerParams.getTriggeredContentAuthorities();
    }

    @RequiresApi(28)
    public final @Nullable Network getNetwork() {
        return mWorkerParams.getNetwork();
    }

    @IntRange(from = 0)
    public final int getRunAttemptCount() {
        return mWorkerParams.getRunAttemptCount();
    }
    
    // The startWork method is implemented by the Woker class, where the call needs to be mainThread
    @MainThread
    public abstract @NonNull ListenableFuture<Result> startWork();

    // Update the execution progress
    @NonNull
    public final ListenableFuture<Void> setProgressAsync(@NonNull Data data) {
        return mWorkerParams.getProgressUpdater()
                .updateProgress(getApplicationContext(), getId(), data);
    }

    
    @NonNull
    public final ListenableFuture<Void> setForegroundAsync(@NonNull ForegroundInfo foregroundInfo) {
        mRunInForeground = true;
        return mWorkerParams.getForegroundUpdater()
                .setForegroundAsync(getApplicationContext(), getId(), foregroundInfo);
    }
    // Suspend related operations
    public final boolean isStopped() {
        return mStopped;
    }
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public final void stop() {
        mStopped = true;
        onStopped();
    }

    public void onStopped() {
        // Do nothing by default.
    }

    // Whether it is already in use
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public final boolean isUsed() {
        return mUsed;
    }
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public final void setUsed() {
        mUsed = true;
    }
    
    // Whether to run in foreground
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public boolean isRunInForeground() {
        return mRunInForeground;
    }

    / * * *@hide* /
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull Executor getBackgroundExecutor() {
        return mWorkerParams.getBackgroundExecutor();
    }

    / * * *@hide* /
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull TaskExecutor getTaskExecutor() {
        return mWorkerParams.getTaskExecutor();
    }

    / * * *@hide* /
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull WorkerFactory getWorkerFactory() {
        return mWorkerParams.getWorkerFactory();
    }
    / / the execution result result/Success/Failure/Retry
    public abstract static class Result {
       
        @NonNull
        public static Result success() {
            return new Success();
        }

        @NonNull
        public static Result success(@NonNull Data outputData) {
            return new Success(outputData);
        }

        @NonNull
        public static Result retry() {
            return new Retry();
        }

        @NonNull
        public static Result failure() {
            return new Failure();
        }

        @NonNull
        public static Result failure(@NonNull Data outputData) {
            return new Failure(outputData);
        }

        / * * *@hide* /
        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        Result() {
            // Restricting access to the constructor, to give Result a sealed class
            // like behavior.
        }

        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        public static final class Success extends Result {
            private final Data mOutputData;

            public Success() {
                this(Data.EMPTY);
            }

            public Success(@NonNull Data outputData) {
                super(a); mOutputData = outputData; }/ * * *@hide* /
            @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
            public @NonNull Data getOutputData() {
                return mOutputData;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null|| getClass() ! = o.getClass())return false;

                Success success = (Success) o;

                return mOutputData.equals(success.mOutputData);
            }

            @Override
            public int hashCode() {
                String name = Success.class.getName();
                return 31 * name.hashCode() + mOutputData.hashCode();
            }

            @Override
            public String toString() {
                return "Success {" + "mOutputData=" + mOutputData + '} '; }}@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        public static final class Failure extends Result {
            private final Data mOutputData;

            public Failure() {
                this(Data.EMPTY);
            }

            public Failure(@NonNull Data outputData) {
                super(a); mOutputData = outputData; }@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
            public @NonNull Data getOutputData() {
                return mOutputData;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null|| getClass() ! = o.getClass())return false;

                Failure failure = (Failure) o;

                return mOutputData.equals(failure.mOutputData);
            }

            @Override
            public int hashCode() {
                String name = Failure.class.getName();
                return 31 * name.hashCode() + mOutputData.hashCode();
            }

            @Override
            public String toString() {
                return "Failure {" +  "mOutputData=" + mOutputData +  '} '; }}@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        public static final class Retry extends Result {
            public Retry() {
                super(a); }@Override
            public boolean equals(Object o) {
                if (this == o) return true;
                // We are treating all instances of Retry as equivalent.
                returno ! =null && getClass() == o.getClass();
            }

            @Override
            public int hashCode() {
                String name = Retry.class.getName();
                return name.hashCode();
            }

            @Override
            public String toString() {
                return "Retry"; }}}}// Task related parameters
public final class WorkerParameters {

    private @NonNull UUID mId;   // random sequence
    private @NonNull Data mInputData;// Enter parameters
    private @NonNull Set<String> mTags;The TAG / / task
    private @NonNull RuntimeExtras mRuntimeExtras; // Run time parameters
    private int mRunAttemptCount;  // Number of retries
    private @NonNull Executor mBackgroundExecutor;  // Background task execution thread pool
    private @NonNull TaskExecutor mWorkTaskExecutor; // Task execution thread pool
    private @NonNull WorkerFactory mWorkerFactory; // Mission factory
    private @NonNull ProgressUpdater mProgressUpdater;// Progress updates
    private @NonNull ForegroundUpdater mForegroundUpdater;// Foreground updater
}
Copy the code

Summary:

WorkerParameters defines a series of task-related parameters, including mInputData, task identification TAG, task execution thread pool, task progress update, task factory, etc

Results: the Result/Success/Failure/Retry)

Ii. Task execution conditions (Task constraints)

We perform the task with a set of constraints, and the fields are annotated with @columnInfo, which means they are written to the database

A. Constraints on network conditions are as follows:
public enum NetworkType {
    // The task has no network requirements
    NOT_REQUIRED,
    // You can execute tasks as long as you have a network connection
    CONNECTED,
    // Non-metered network links can perform tasks
    UNMETERED,
    // Non-roaming network connections can perform tasks
    NOT_ROAMING,
    // Metering network connection can perform tasks
    METERED
}

// Network condition judgment
public class NetworkStateTracker extends ConstraintTracker<NetworkState> {

    // Synthetic Accessor
    static final String TAG = Logger.tagWithPrefix("NetworkStateTracker");

    private final ConnectivityManager mConnectivityManager;

    @RequiresApi(24)
    private NetworkStateCallback mNetworkCallback;
    private NetworkStateBroadcastReceiver mBroadcastReceiver;

    public NetworkStateTracker(@NonNull Context context, @NonNull TaskExecutor taskExecutor) {
        super(context, taskExecutor);
        mConnectivityManager =
                (ConnectivityManager) mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        // If NetworkCallback is supported, use NetworkCallback to listen for network connections
        // Otherwise use broadcast
        if (isNetworkCallbackSupported()) {
            mNetworkCallback = new NetworkStateCallback();
        } else {
            mBroadcastReceiver = newNetworkStateBroadcastReceiver(); }}@Override
    public NetworkState getInitialState(a) {
        return getActiveNetworkState();
    }

    @Override
    public void startTracking(a) {
        if (isNetworkCallbackSupported()) {
            try {
                Logger.get().debug(TAG, "Registering network callback");
                mConnectivityManager.registerDefaultNetworkCallback(mNetworkCallback);
            } catch (IllegalArgumentException | SecurityException e) {
            }
        } else {
            Logger.get().debug(TAG, "Registering broadcast receiver");
            mAppContext.registerReceiver(mBroadcastReceiver,
                    newIntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); }}@Override
    public void stopTracking(a) {
        if (isNetworkCallbackSupported()) {
            try {
                Logger.get().debug(TAG, "Unregistering network callback");
                mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
            } catch (IllegalArgumentException | SecurityException e) {
               
            }
        } else {
            Logger.get().debug(TAG, "Unregistering broadcast receiver"); mAppContext.unregisterReceiver(mBroadcastReceiver); }}// Check whether NetworkCallback API version is supported >=24
    private static boolean isNetworkCallbackSupported(a) {
        // Based on requiring ConnectivityManager#registerDefaultNetworkCallback - added in API 24.
        return Build.VERSION.SDK_INT >= 24;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    NetworkState getActiveNetworkState(a) {
        // Use getActiveNetworkInfo() instead of getNetworkInfo(network) because it can detect VPNs.
        NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
        booleanisConnected = info ! =null && info.isConnected();
        boolean isValidated = isActiveNetworkValidated();
        boolean isMetered = ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager);
        booleanisNotRoaming = info ! =null && !info.isRoaming();
        return new NetworkState(isConnected, isValidated, isMetered, isNotRoaming);
    }
    // Check whether the network is valid
    private boolean isActiveNetworkValidated(a) {
        if (Build.VERSION.SDK_INT < 23) {
            return false; // NET_CAPABILITY_VALIDATED not available until API 23. Used on API 26+.
        }
        Network network = mConnectivityManager.getActiveNetwork();
        NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(network);
        returncapabilities ! =null
                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
    }

    @RequiresApi(24)
    private class NetworkStateCallback extends NetworkCallback {
        NetworkStateCallback() {
        }

        @Override
        public void onCapabilitiesChanged(
                @NonNull Network network, @NonNull NetworkCapabilities capabilities) {
            setState(getActiveNetworkState());
        }

        @Override
        public void onLost(@NonNull Network network) { setState(getActiveNetworkState()); }}private class NetworkStateBroadcastReceiver extends BroadcastReceiver {
        NetworkStateBroadcastReceiver() {
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null || intent.getAction() == null) {
                return;
            }
            if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                Logger.get().debug(TAG, "Network broadcast received"); setState(getActiveNetworkState()); }}}}Copy the code
When API>=24, NetworkStateCallback is used to monitor the change of network connection status. When API < 24, BroadcastReceiver is used to monitor system broadcast CONNECTIVITY_ACTION to monitor the change of network connection status. After the change, network metering, roaming, connection status, etc., will be updated synchronously
B. Charging constraints
public class BatteryChargingTracker extends BroadcastReceiverConstraintTracker<Boolean> {

    private static final String TAG = Logger.tagWithPrefix("BatteryChrgTracker");

    /**
     * Create an instance of {@link BatteryChargingTracker}.
     * @param context The application {@link Context}
     * @param taskExecutor The internal {@link TaskExecutor} being used by WorkManager.
     */
    public BatteryChargingTracker(@NonNull Context context, @NonNull TaskExecutor taskExecutor) {
        super(context, taskExecutor);
    }
    // Get whether the initial state is charging
    @Override
    public Boolean getInitialState(a) {
        // {@link ACTION_CHARGING} and {@link ACTION_DISCHARGING} are not sticky broadcasts, so
        // we use {@link ACTION_BATTERY_CHANGED} on all APIs to get the initial state.
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = mAppContext.registerReceiver(null, intentFilter);
        if (intent == null) {
            Logger.get().error(TAG, "getInitialState - null intent received");
            return null;
        }
        return isBatteryChangedIntentCharging(intent);
    }

    @Override
    public IntentFilter getIntentFilter(a) {
        IntentFilter intentFilter = new IntentFilter();
        // Version 23 and above Monitor ACTION_CHARGING and ACTION_ADEQUACY
        ACTION_POWER_CONNECTED and ACTION_POWER_DISCONNECTED are listened for at least version 23
        if (Build.VERSION.SDK_INT >= 23) {
            intentFilter.addAction(BatteryManager.ACTION_CHARGING);
            intentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
        } else {
            intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
            intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        }
        return intentFilter;
    }
    // Monitor system broadcast is mainly charge and power
    @Override
    public void onBroadcastReceive(Context context, @NonNull Intent intent) {
        String action = intent.getAction();
        if (action == null) {
            return;
        }

        Logger.get().debug(TAG, String.format("Received %s", action));
        switch (action) {
            case BatteryManager.ACTION_CHARGING:
                setState(true);
                break;

            case BatteryManager.ACTION_DISCHARGING:
                setState(false);
                break;

            case Intent.ACTION_POWER_CONNECTED:
                setState(true);
                break;

            case Intent.ACTION_POWER_DISCONNECTED:
                setState(false);
                break; }}// Check whether the battery is charging
    private boolean isBatteryChangedIntentCharging(Intent intent) {
        boolean charging;
        // Version 23 and above get batteryManager. EXTRA_STATUS to judge
        //23 Get batteryManager. EXTRA_PLUGGED below to determine
        if (Build.VERSION.SDK_INT >= 23) {
            int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
            charging = (status == BatteryManager.BATTERY_STATUS_CHARGING
                    || status == BatteryManager.BATTERY_STATUS_FULL);
        } else {
            int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); charging = (chargePlug ! =0);
        }
        returncharging; }}Copy the code
Api23 is the boundary for judging whether the device is in charging state. Following 23, charging and power-off can be judged by listening to broadcast ACTION_POWER_CONNECTED and ACTION_POWER_DISCONNECTED. 23 and above Determine the charging status of the BATTERY by monitoring ACTION_CHARGING and Action_ADEQUACY
C. Check whether the device is idle

< API>=23; < API>=23; Foreground tasks cannot set this condition; Tasks that have set setBackoffCriteria cannot use this condition

D. Device memory constraints
public class StorageNotLowTracker extends BroadcastReceiverConstraintTracker<Boolean> {

    private static final String TAG = Logger.tagWithPrefix("StorageNotLowTracker");
    public StorageNotLowTracker(@NonNull Context context, @NonNull TaskExecutor taskExecutor) {
        super(context, taskExecutor);
    }

    @Override
    public Boolean getInitialState(a) {
        Intent intent = mAppContext.registerReceiver(null, getIntentFilter());
        if (intent == null || intent.getAction() == null) {
            return true;
        } else {
            switch (intent.getAction()) {
                case Intent.ACTION_DEVICE_STORAGE_OK:
                    return true;

                case Intent.ACTION_DEVICE_STORAGE_LOW:
                    return false;

                default:
                    return null; }}}@Override
    public IntentFilter getIntentFilter(a) {
        // In API 26+, DEVICE_STORAGE_OK/LOW are deprecated and are no longer sent to
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
        return intentFilter;
    }

    @Override
    public void onBroadcastReceive(Context context, @NonNull Intent intent) {
        if (intent.getAction() == null) {
            return; // Should never happen since the IntentFilter was configured.
        }

        Logger.get().debug(TAG, String.format("Received %s", intent.getAction()));

        switch (intent.getAction()) {
            case Intent.ACTION_DEVICE_STORAGE_OK:
                setState(true);
                break;

            case Intent.ACTION_DEVICE_STORAGE_LOW:
                setState(false);
                break; }}}Copy the code
As you can see, the code logic is also very simple. It also listens to the broadcasts ACTION_DEVICE_STORAGE_OK and ACTION_DEVICE_STORAGE_LOW to determine the device storage constraint. However, in version > API26 these two broadcasts are deprecated and may not be sent in the future, so use this condition with caution
E. Electric quantity constraint
public class BatteryNotLowTracker extends BroadcastReceiverConstraintTracker<Boolean> {

    private static final String TAG = Logger.tagWithPrefix("BatteryNotLowTracker");
    // Low power threshold <=15%
    static final float BATTERY_LOW_THRESHOLD = 0.15 f;

    public BatteryNotLowTracker(@NonNull Context context, @NonNull TaskExecutor taskExecutor) {
        super(context, taskExecutor);
    }
    // Get the initial state
    @Override
    public Boolean getInitialState(a) {
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = mAppContext.registerReceiver(null, intentFilter);
        if (intent == null) {
            Logger.get().error(TAG, "getInitialState - null intent received");
            return null;
        }

        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        float batteryPercentage = level / (float) scale;
        return (status == BatteryManager.BATTERY_STATUS_UNKNOWN
                || batteryPercentage > BATTERY_LOW_THRESHOLD);
    }

    @Override
    public IntentFilter getIntentFilter(a) {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_BATTERY_OKAY);
        intentFilter.addAction(Intent.ACTION_BATTERY_LOW);
        return intentFilter;
    }
    // Monitor power changes
    @Override
    public void onBroadcastReceive(Context context, @NonNull Intent intent) {
        if (intent.getAction() == null) {
            return;
        }

        Logger.get().debug(TAG, String.format("Received %s", intent.getAction()));

        switch (intent.getAction()) {
            case Intent.ACTION_BATTERY_OKAY:
                setState(true);
                break;

            case Intent.ACTION_BATTERY_LOW:
                setState(false);
                break; }}}Copy the code
The code is also relatively simple: whether the battery is 15% lower than the threshold. Listen to ACTION_BATTERY_OKAY and ACTION_BATTERY_LOW broadcasts to determine whether this constraint is met. The problem is that the threshold is 15% as defined. This will lead to inconsistencies, where the trigger conditions may not be consistent.

3. Task information

public final class WorkInfo {
    // Task ID
    private @NonNull UUID mId;
    // Task status
    private @NonNull State mState
    // Task output data
    private @NonNull Data mOutputData;
    // Task identifier tag
    private @NonNull Set<String> mTags;
   // Task progress
    private @NonNull Data mProgress;
    // Number of task attempts
    private int mRunAttemptCount;

    // Task status
    public enum State {

        / / team
        ENQUEUED,

        / / implementation
        RUNNING,

        // The command is successfully executed
        SUCCEEDED,

        // Execution failed
        FAILED,

        // Task is blocked
        BLOCKED,

        // Task cancelled
        CANCELLED;
        // Whether the task is in the completed state (succeeded, failed, canceled)
        public boolean isFinished(a) {
            return (this == SUCCEEDED || this == FAILED || this== CANCELLED); }}}Copy the code

The information of a task contains a series of states and some data during the execution of the task, such as progress and status. WorkSpec is mainly the data written into the database related to the task information

Iv. Task execution

With task creation, task constraints, and task constraints, let’s look at how tasks are executed.

Single mission:

// The upper layer calls enqueue
WorkManager.getInstance(this).enqueue(workRequest)
  
//getInstance returns the implementation class WorkManagerImpl
public static @NonNull WorkManager getInstance(@NonNull Context context) {
    return WorkManagerImpl.getInstance(context);
}

// workManager. Java converts the workRequest to a list and calls enQueue again
@NonNull
public final Operation enqueue(@NonNull WorkRequest workRequest) {
    return enqueue(Collections.singletonList(workRequest));
}

//WorkManagerImpl.java
// EnQueue is the interface method and WorkManagerImpl is the implementation of WorkManager
 @Override
 @NonNull
 public Operation enqueue(@NonNull List<? extends WorkRequest> workRequests) {
    // Check whether the input parameter is empty
    if (workRequests.isEmpty()) {
        throw new IllegalArgumentException(
                "enqueue needs at least one WorkRequest.");
    }
    // New WorkContinuationImpl, then call enQueue of WorkContinuationImpl
    return new WorkContinuationImpl(this, workRequests).enqueue();
}

//WorkContinuationImpl.java 
WorkContinuationImpl(
            @NonNull WorkManagerImpl workManagerImpl,
            @NonNull List<? extends WorkRequest> work) {
        this(
                workManagerImpl,
                null,
                ExistingWorkPolicy.KEEP,
                work,
                null);
    }
/ / equeue method
  @Override
    public @NonNull Operation enqueue(a) {
        // Check whether the queue is in
        if(! mEnqueued) {// Wrap the runabl as runable and execute the runabl in the thread pool. Runable holds WorkContinuationImpl
            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;
    }

// The above method calls the thread pool to execute runable, so let's look at runable's run method
 @Override
    public void run(a) {
        try {
            // Check if there is a loop
            if (mWorkContinuation.hasCycles()) {
                throw new IllegalStateException(
                        String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
            }
            // Add to database
            boolean needsScheduling = addToDatabase();
            // If true, start RescheduleReceiver and call scheduleWorkInBackground()
            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);          // Background execution
                scheduleWorkInBackground();
            }
            mOperation.setState(Operation.SUCCESS);
        } catch (Throwable exception) {
            mOperation.setState(newOperation.State.FAILURE(exception)); }}// Let's start with addToDatabase
// Get the database and call processContinuation
    @VisibleForTesting
    public boolean addToDatabase(a) {
        WorkManagerImpl workManagerImpl = mWorkContinuation.getWorkManagerImpl();
        WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();
        workDatabase.beginTransaction();
        try {
            boolean needsScheduling = processContinuation(mWorkContinuation);
            workDatabase.setTransactionSuccessful();
            return needsScheduling;
        } finally{ workDatabase.endTransaction(); }}private static boolean processContinuation(@NonNull WorkContinuationImpl workContinuation) {
        boolean needsScheduling = false;
        List<WorkContinuationImpl> parents = workContinuation.getParents();
        if(parents ! =null) {
            for (WorkContinuationImpl parent : parents) {
                if(! parent.isEnqueued()) { needsScheduling |= processContinuation(parent); }else {
                    Logger.get().warning(TAG, String.format("Already enqueued work ids (%s).",
                            TextUtils.join(",", parent.getIds()))); }}}// Add parents to empty, enqueueContinuation is called
        needsScheduling |= enqueueContinuation(workContinuation);
        return needsScheduling;
    }

//
 private static boolean enqueueContinuation(@NonNull WorkContinuationImpl workContinuation) {
        Set<String> prerequisiteIds = WorkContinuationImpl.prerequisitesFor(workContinuation);

        boolean needsScheduling = enqueueWorkWithPrerequisites(
                workContinuation.getWorkManagerImpl(),
                workContinuation.getWork(),
                prerequisiteIds.toArray(new String[0]),
                workContinuation.getName(),
                workContinuation.getExistingWorkPolicy());

        workContinuation.markEnqueued();
        return needsScheduling;
    }

    private static boolean enqueueWorkWithPrerequisites(
            WorkManagerImpl workManagerImpl,
            @NonNull List<? extends WorkRequest> workList,
            String[] prerequisiteIds,
            String name,
            ExistingWorkPolicy existingWorkPolicy) {

        boolean needsScheduling = false;

        long currentTimeMillis = System.currentTimeMillis();
        WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();

        booleanhasPrerequisite = (prerequisiteIds ! =null && prerequisiteIds.length > 0);
        boolean hasCompletedAllPrerequisites = true;
        boolean hasFailedPrerequisites = false;
        boolean hasCancelledPrerequisites = false;

        if (hasPrerequisite) {
            for (String id : prerequisiteIds) {
                WorkSpec prerequisiteWorkSpec = workDatabase.workSpecDao().getWorkSpec(id);
                if (prerequisiteWorkSpec == null) {
                    Logger.get().error(TAG,
                            String.format("Prerequisite %s doesn't exist; not enqueuing", id));
                    return false;
                }

                WorkInfo.State prerequisiteState = prerequisiteWorkSpec.state;
                hasCompletedAllPrerequisites &= (prerequisiteState == SUCCEEDED);
                if (prerequisiteState == FAILED) {
                    hasFailedPrerequisites = true;
                } else if (prerequisiteState == CANCELLED) {
                    hasCancelledPrerequisites = true; }}}booleanisNamed = ! TextUtils.isEmpty(name);booleanshouldApplyExistingWorkPolicy = isNamed && ! hasPrerequisite;if (shouldApplyExistingWorkPolicy) {
            List<WorkSpec.IdAndState> existingWorkSpecIdAndStates =
                    workDatabase.workSpecDao().getWorkSpecIdAndStatesForName(name);

            if(! existingWorkSpecIdAndStates.isEmpty()) {// Policy processing
                if (existingWorkPolicy == APPEND || existingWorkPolicy == APPEND_OR_REPLACE) {
                    DependencyDao dependencyDao = workDatabase.dependencyDao();
                    List<String> newPrerequisiteIds = new ArrayList<>();
                    for (WorkSpec.IdAndState idAndState : existingWorkSpecIdAndStates) {
                        if(! dependencyDao.hasDependents(idAndState.id)) { hasCompletedAllPrerequisites &= (idAndState.state == SUCCEEDED);if (idAndState.state == FAILED) {
                                hasFailedPrerequisites = true;
                            } else if (idAndState.state == CANCELLED) {
                                hasCancelledPrerequisites = true; } newPrerequisiteIds.add(idAndState.id); }}if (existingWorkPolicy == APPEND_OR_REPLACE) {
                        if (hasCancelledPrerequisites || hasFailedPrerequisites) {
                            // Delete all WorkSpecs with this name
                            WorkSpecDao workSpecDao = workDatabase.workSpecDao();
                            List<WorkSpec.IdAndState> idAndStates =
                                    workSpecDao.getWorkSpecIdAndStatesForName(name);
                            for (WorkSpec.IdAndState idAndState : idAndStates) {
                                workSpecDao.delete(idAndState.id);
                            }
                            // Treat this as a new chain of work.
                            newPrerequisiteIds = Collections.emptyList();
                            hasCancelledPrerequisites = false;
                            hasFailedPrerequisites = false;
                        }
                    }
                    prerequisiteIds = newPrerequisiteIds.toArray(prerequisiteIds);
                    hasPrerequisite = (prerequisiteIds.length > 0);
                } else {
                    if (existingWorkPolicy == KEEP) {
                        for (WorkSpec.IdAndState idAndState : existingWorkSpecIdAndStates) {
                            if (idAndState.state == ENQUEUED || idAndState.state == RUNNING) {
                                return false;
                            }
                        }
                    }

                    CancelWorkRunnable.forName(name, workManagerImpl, false).run();
                    needsScheduling = true;
                    WorkSpecDao workSpecDao = workDatabase.workSpecDao();
                    for(WorkSpec.IdAndState idAndState : existingWorkSpecIdAndStates) { workSpecDao.delete(idAndState.id); }}}}/ / traverse workRequest
        for (WorkRequest work : workList) {
            // Get the work parameter
            WorkSpec workSpec = work.getWorkSpec();

            if(hasPrerequisite && ! hasCompletedAllPrerequisites) {if (hasFailedPrerequisites) {
                    workSpec.state = FAILED;
                } else if (hasCancelledPrerequisites) {
                    workSpec.state = CANCELLED;
                } else{ workSpec.state = BLOCKED; }}else {
                // If it is a periodic task, record the current start time
                if(! workSpec.isPeriodic()) { workSpec.periodStartTime = currentTimeMillis; }else {
                    // Otherwise, the start time is 0
                    workSpec.periodStartTime = 0L; }}//API 23 =< <=25 tryDelegateConstrainedWorkSpec
             / / API < = 22 and usesScheduler also call tryDelegateConstrainedWorkSpec
            if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL
                    && Build.VERSION.SDK_INT <= 25) {
                tryDelegateConstrainedWorkSpec(workSpec);
            } else if (Build.VERSION.SDK_INT <= WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL
                    && usesScheduler(workManagerImpl, Schedulers.GCM_SCHEDULER)) {
                tryDelegateConstrainedWorkSpec(workSpec);
            }

            if (workSpec.state == ENQUEUED) {
                needsScheduling = true;
            }
            // Add to database
            workDatabase.workSpecDao().insertWorkSpec(workSpec);

            if (hasPrerequisite) {
                for (String prerequisiteId : prerequisiteIds) {
                    Dependency dep = newDependency(work.getStringId(), prerequisiteId); workDatabase.dependencyDao().insertDependency(dep); }}for (String tag : work.getTags()) {
                workDatabase.workTagDao().insert(new WorkTag(tag, work.getStringId()));
            }

            if (isNamed) {
                workDatabase.workNameDao().insert(newWorkName(name, work.getStringId())); }}return needsScheduling;
    }

// Power-dependent and space-dependent versions 23-25 ConstraintTrackingWorker and above 26 rely on JobScheduler
private static void tryDelegateConstrainedWorkSpec(WorkSpec workSpec) {
        // requiresBatteryNotLow and requiresStorageNotLow require API 26 for JobScheduler.
        // Delegate to ConstraintTrackingWorker between API 23-25.
        Constraints constraints = workSpec.constraints;
        if (constraints.requiresBatteryNotLow() || constraints.requiresStorageNotLow()) {
            String workerClassName = workSpec.workerClassName;
            Data.Builder builder = new Data.Builder();
            // Copy all argumentsbuilder.putAll(workSpec.input) .putString(ARGUMENT_CLASS_NAME, workerClassName); workSpec.workerClassName = ConstraintTrackingWorker.class.getName(); workSpec.input = builder.build(); }}/** * Schedules work on the background scheduler. */
    @VisibleForTesting
    public void scheduleWorkInBackground(a) {
        WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
        Schedulers.schedule(
                workManager.getConfiguration(),
                workManager.getWorkDatabase(),
                workManager.getSchedulers());
    }

Copy the code

Finally, schedulers.schedule is dialed, passing in the configuration, database, and Scheluer list

Let’s first look at the schedulers list, which is an argument to the workManagerImpl

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
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()));
    Schedulers are generated on createSchedulers
    List<Scheduler> schedulers =
            createSchedulers(applicationContext, configuration, workTaskExecutor);
    Processor processor = new Processor(
            context,
            configuration,
            workTaskExecutor,
            database,
            schedulers);
    internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    public List<Scheduler> createSchedulers(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor taskExecutor) {
        / / the first list item is createBestAvailableBackgroundScheduler
        // The second list item is GreedyScheduler
        return Arrays.asList(
                Schedulers.createBestAvailableBackgroundScheduler(context, this),
                new GreedyScheduler(context, configuration, taskExecutor, this));
    }

 //Shedulers.java
  @NonNull
    static Scheduler createBestAvailableBackgroundScheduler(
            @NonNull Context context,
            @NonNull WorkManagerImpl workManager) {

        Scheduler scheduler;
        //23 and above use SystemJobScheduler and depend on SystemJobService
        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 {
            //23 Reflection is used to obtain the GcmScheduler. If not, SystemAlarmScheduler is used and SystemAlarmService is used
            scheduler = tryCreateGcmBasedScheduler(context);
            if (scheduler == null) {
                scheduler = new SystemAlarmScheduler(context);
                setComponentEnabled(context, SystemAlarmService.class, true);
                Logger.get().debug(TAG, "Created SystemAlarmScheduler"); }}return scheduler;
    }

    @Nullable
    private static Scheduler tryCreateGcmBasedScheduler(@NonNull Context context) {
        try{ Class<? > klass = Class.forName(GCM_SCHEDULER); Scheduler scheduler = (Scheduler) klass.getConstructor(Context.class).newInstance(context); Logger.get().debug(TAG, String.format("Created %s", GCM_SCHEDULER));
            return scheduler;
        } catch (Throwable throwable) {
            Logger.get().debug(TAG, "Unable to create GCM Scheduler", throwable);
            return null; }}Copy the code

How does the constraint work? We know that the last call is sheduler. shedule. Let’s look at this method:

@Override
public void schedule(@NonNull WorkSpec... workSpecs) {
    if (mIsMainProcess == null) {
        mIsMainProcess = TextUtils.equals(mContext.getPackageName(), getProcessName());
    }

    if(! mIsMainProcess) { Logger.get().info(TAG,"Ignoring schedule request in non-main process");
        return;
    }

    registerExecutionListenerIfNeeded();
    // A constrained set of work parameters
    Set<WorkSpec> constrainedWorkSpecs = new HashSet<>();
    // A constrained set of workids
    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()) {
                    // The constraint that the device is idle is ignored above
                    Logger.get().debug(TAG,
                            String.format("Ignoring WorkSpec %s, Requires device idle.",
                                    workSpec));
                } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                    // The contentUri constraint is ignored above
                    Logger.get().debug(TAG,
                            String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
                                    workSpec));
                } else {
                    // Add the constraint to the collection
                    constrainedWorkSpecs.add(workSpec);
                    // Add the task ID to the collectionconstrainedWorkSpecIds.add(workSpec.id); }}else {
                // There is no constraint to call startWork directly
                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); }}}Copy the code

Startwork is not executed if there is a constraint. Let’s assume there is no constraint. Look at startWork

/ * * *@param workSpecId The {@link WorkSpec} id to start
 * @hide* /
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void startWork(@NonNull String workSpecId) {
    startWork(workSpecId, null);
}

/ * * *@param workSpecId The {@link WorkSpec} id to start
 * @param runtimeExtras The {@link WorkerParameters.RuntimeExtras} associated with this work
 * @hide* /
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void startWork(
        @NonNull String workSpecId,
        @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
    // Call the background thread to execute the encapsulated runable
    mWorkTaskExecutor
            .executeOnBackgroundThread(
                    new StartWorkRunnable(this, workSpecId, runtimeExtras));
}

// startWorkRunnable. Java calls getProcessor.startwork
 @Override
    public void run(a) {
        mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
    }

//Processor.java wraps workWrapper and calls the thread pool for execution
    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;
    }

/ / WorkRapper. Java run method
  @WorkerThread
    @Override
    public void run(a) {
        mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
        mWorkDescription = createWorkDescription(mTags);
        / / call runworker
        runWorker();
    }

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

        mWorkDatabase.beginTransaction();
        try {
            // Get task parameters
            mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
            if (mWorkSpec == null) {
                Logger.get().error(
                        TAG,
                        String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
                resolve(false);
                return;
            }
            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;
            }

            // Periodic task and delayed task processing
            if (mWorkSpec.isPeriodic() || mWorkSpec.isBackedOff()) {
                long now = System.currentTimeMillis();
                // Is not the first time to execute the task and calculate the next time to execute the task
                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));
                    resolve(true);
                    return;
                }
            }
            mWorkDatabase.setTransactionSuccessful();
        } finally {
            mWorkDatabase.endTransaction();
        }
        // Handle the parameters carried
        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));
        // Check whether it is empty or in use before executing the task
        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 run the task
        if (trySetRunning()) {
            if (tryCheckForInterruptionAndResolve()) {
                return;
            }

            final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
            // Call the startWork method on the main thread
            mWorkTaskExecutor.getMainThreadExecutor()
                    .execute(new Runnable() {
                        @Override
                        public void run(a) {
                            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); }}});// Run result listener
            final String workDescription = mWorkDescription;
            future.addListener(new Runnable() {
                @Override
                @SuppressLint("SyntheticAccessor")
                public void run(a) {
                    try {
                        
                        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(); }}Copy the code

How do task constraints work?

We have seen above that GreedyScheduler does not call startWork under constraints. When will startwork be called

// Implement the WorkConstraintsCallback interface
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class GreedyScheduler implements Scheduler.WorkConstraintsCallback{

    // Total processing of constraints
    private final WorkConstraintsTracker mWorkConstraintsTracker;
    // The constraint condition
    private final Set<WorkSpec> mConstrainedWorkSpecs = new HashSet<>();
 

    public GreedyScheduler(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor taskExecutor,
            @NonNull WorkManagerImpl workManagerImpl) {
        // the constructor initializes the constraint total processing
        mWorkConstraintsTracker = new WorkConstraintsTracker(context, taskExecutor, this);
       
    }

    
    // A callback that implements all constraints of the interface invokes Stratwork
    @Override
    public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
        for (String workSpecId : workSpecIds) {
            Logger.get().debug(
                    TAG,
                    String.format("Constraints met: Scheduling work ID %s", workSpecId)); mWorkManagerImpl.startWork(workSpecId); }}// Callbacks that implement all constraints of the interface that do not meet will call stopWork
    @Override
    public void onAllConstraintsNotMet(@NonNull List<String> workSpecIds) {
        for (String workSpecId : workSpecIds) {
            Logger.get().debug(TAG,
                    String.format("Constraints not met: Cancelling work ID %s", workSpecId)); mWorkManagerImpl.stopWork(workSpecId); }}}Copy the code

GreedyScheduler implements the WorkConstraintsCallback interface, which calls onAllConstraintsMet and startWork when all constraints are met

Five, the summary

1. The WorkManager implementation is the WorkManagerImpl, which is not initialized by getInstance. The WorkManagerInitializer is derived from contentProvider. The Initialize method is called in Oncreate to initialize the thread pool and scheduler
2. Startwork is called on the main thread and dowork is performed on the background thread pool
3. The task can be executed when it is started after the APP kills it because the information related to the task is written into the database and can be recovered after the APP is started
4. Task scheduler: When API >=23, the system generates SystemJobScheduler and schedules tasks with SystemJobService. When API < 23:00, reflection is used to obtain GcmScheduler. If not, SystemAlarmScheduler is used and SystemAlarmService is used to schedule tasks. The second scheduler implementation is GreedyScheduler
5. Task constraints
(1) Network constraints: When API>=24, NetworkStateCallback is used to monitor network connection status changes. When API < 24, BroadcastReceiver is used to monitor system broadcasts. CONNECTIVITY_ACTION is used to monitor network connection status changes
(2) Storage constraint: the constraint of device storage is determined by listening to broadcast ACTION_DEVICE_STORAGE_OK and ACTION_DEVICE_STORAGE_LOW
(3) Electric quantity constraint: The threshold of low electric quantity is 15%. Listen to ACTION_BATTERY_OKAY and ACTION_BATTERY_LOW to determine whether this constraint condition is met
(4) Charging constraints: 23 Determine charging and power failure by listening to broadcast ACTION_POWER_CONNECTED and ACTION_POWER_DISCONNECTED. 23 and above Determine the charging status of the BATTERY by monitoring ACTION_CHARGING and Action_ADEQUACY
6. Worker execution process (GreedyScheduler) :
(1) Worker is packaged as WorkRequest and conditions such as constraints are added
(2) Call WorkManagerImpl enQueue, WorkManagerImpl encapsulates itself and WorkRequest into WorkContinuationImpl, The enQueue method of WorkContinuationImpl is then called
(3) WorkContinuationImpl enQueue encapsulates itself as EnqueueRunnable, and then calls the background thread pool to execute EnqueueRunnable
(4) EnqueueRunnable’s run method stores itself to the database first, and then enables scheduleWorkInBackground based on whether it needs to be scheduled
(5) Two Schedulers Schedulers during background scheduling and WorkManager initialization, and then call schedule method;
(6) method of the schedule will be determined according to the task if there is a constraint is the constraint satisfied after immediately began to perform a task or to perform a task (WorkManagerImpl. The startwork)
(7) WorkManagerImpl. The startwork workSpecId and WorkManagerImpl encapsulated into StartWorkRunnable, then through task executor background tasks
The StartWorkRunnable run method executes the Processor. Startwork method
Startwork will wrap the WorkerWrapper according to the workId, and then execute the WorkerWrapper through the human executor
(10) The WorkerWrapper run method executes the runworker and, based on a series of task states, executes the worker.startworker on the main thread and the dowork method on the child thread inside the startWorker