美文网首页
jetpack的workmanage简单原理分析

jetpack的workmanage简单原理分析

作者: nich | 来源:发表于2022-06-07 14:17 被阅读0次

workmanage使用场景

非及时性的执行任务或者定时性重复任务,要求及时性/时效性不高的比如上传日志什么的。Google说WorkManager是保证你的任务一定会执行的(国内手机不一定慎重)

简单使用

class MyWorkManage(context:Context ,workerParams: WorkerParameters):Worker(context,workerParams) {

    override fun doWork(): Result {
        
        return Result.success()
    }

}

val constraint = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
        val request = OneTimeWorkRequest.Builder(MyWorkManage::class.java).setConstraints(constraint).build()
        WorkManager.getInstance().enqueue(request)

简单看WorkManager.getInstance().enqueue(request)
1.workmanager.getinstance()

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

           return sDefaultInstance;
       }
   }

   /**
    * Initializes the singleton instance of {@link WorkManagerImpl}.  You should only do this if
    * you want to use a custom {@link Configuration} object and have disabled
    * WorkManagerInitializer.
    *
    * @param context A {@link Context} object for configuration purposes. Internally, this class
    *                will call {@link Context#getApplicationContext()}, so you may safely pass in
    *                any Context without risking a memory leak.
    * @param configuration The {@link Configuration} for used to set up WorkManager.
    *
    * @hide
    */
   @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
   public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
       synchronized (sLock) {
           if (sDelegatedInstance != null && sDefaultInstance != null) {
               throw new IllegalStateException("WorkManager is already initialized.  Did you "
                       + "try to initialize it manually without disabling "
                       + "WorkManagerInitializer? See "
                       + "WorkManager#initialize(Context, Configuration) or the class level"
                       + "Javadoc for more information.");
           }

           if (sDelegatedInstance == null) {
               context = context.getApplicationContext();
               if (sDefaultInstance == null) {
                   sDefaultInstance = new WorkManagerImpl(
                           context,
                           configuration,
                           new WorkManagerTaskExecutor());
               }
               sDelegatedInstance = sDefaultInstance;
           }
       }
   }

看到sDelegatedInstance,sDefaultInstance都为null这两个变量只有在initialize方法中可以初始化,那什么时候调用的呢,打开apk包下面的androidmanifest

<provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:exported="false"
            android:multiprocess="true"
            android:authorities="com.example.day.workmanager-init"
            android:directBootAware="false" />

看到provider里面有个WorkManagerInitializer点击进去

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkManagerInitializer extends ContentProvider {
    @Override
    public boolean onCreate() {
        // Initialize WorkManager with the default configuration.
        WorkManager.initialize(getContext(), new Configuration.Builder().build());
        return true;
    }

在oncreat里面初始化调用
WorkManager.initialize(getContext(), new Configuration.Builder().build());
点击进去就会看到初始化
再看上文initialize()方法里面的
sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor());

查看初始化方法
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            boolean useTestDatabase) {

        Context applicationContext = context.getApplicationContext();
        WorkDatabase database = WorkDatabase.create(applicationContext, useTestDatabase);
        Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
        List<Scheduler> schedulers = createSchedulers(applicationContext);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
    }

*WorkDatabase:room数据库
* schedulers:调度器
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public @NonNull List<Scheduler> createSchedulers(Context context) {
        return Arrays.asList(
                Schedulers.createBestAvailableBackgroundScheduler(context, this),
                new GreedyScheduler(context, this));
    }
 *processor:创建processor下面会说
 *internalInit:这个方法里面主要看
 // Checks for app force stops.
     mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));

2.查看.enqueue(request)
一路狂下去走

 @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 runnable = new EnqueueRunnable(this);
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
workmanager里面的线程池执行后台线程,主要看EnqueueRunnable里面 run方法

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

主要看scheduleWorkInBackground

  @VisibleForTesting
    public void scheduleWorkInBackground() {
        WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
        Schedulers.schedule(
                workManager.getConfiguration(),
                workManager.getWorkDatabase(),
                workManager.getSchedulers());
    }

 public static void schedule(
            @NonNull Configuration configuration,
            @NonNull WorkDatabase workDatabase,
            List<Scheduler> schedulers) {
        if (schedulers == null || schedulers.size() == 0) {
            return;
        }

        WorkSpecDao workSpecDao = workDatabase.workSpecDao();
        List<WorkSpec> eligibleWorkSpecs;

        workDatabase.beginTransaction();
        try {
            eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(
                    configuration.getMaxSchedulerLimit());
            if (eligibleWorkSpecs != null && eligibleWorkSpecs.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 : eligibleWorkSpecs) {
//记录状态id时间到数据库里面去
                    workSpecDao.markWorkSpecScheduled(workSpec.id, now);
                }
            }
            workDatabase.setTransactionSuccessful();
        } finally {
            workDatabase.endTransaction();
        }

        if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
            WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
            // Delegate to the underlying scheduler.
            for (Scheduler scheduler : schedulers) {
           //调度器开始调度
                scheduler.schedule(eligibleWorkSpecsArray);
            }
        }
    }

主要看 scheduler.schedule(eligibleWorkSpecsArray);

 @Override
    public void schedule(WorkSpec... workSpecs) {
        registerExecutionListenerIfNeeded();

        // Keep track of the list of new WorkSpecs whose constraints need to be tracked.
        // Add them to the known list of constrained WorkSpecs and call replace() on
        // WorkConstraintsTracker. That way we only need to synchronize on the part where we
        // are updating mConstrainedWorkSpecs.
        List<WorkSpec> constrainedWorkSpecs = new ArrayList<>();
        List<String> constrainedWorkSpecIds = new ArrayList<>();
        for (WorkSpec workSpec: workSpecs) {
            if (workSpec.state == WorkInfo.State.ENQUEUED
                    && !workSpec.isPeriodic()
                    && workSpec.initialDelay == 0L
                    && !workSpec.isBackedOff()) {
                if (workSpec.hasConstraints()) {
                    // Exclude content URI triggers - we don't know how to handle them here so the
                    // background scheduler should take care of them.
                    if (Build.VERSION.SDK_INT < 24
                            || !workSpec.constraints.hasContentUriTriggers()) {
                        constrainedWorkSpecs.add(workSpec);
                        constrainedWorkSpecIds.add(workSpec.id);
                    }
                } else {
                    Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
                    mWorkManagerImpl.startWork(workSpec.id);
                }
            }
        }

        // onExecuted() which is called on the main thread also modifies the list of mConstrained
        // WorkSpecs. Therefore we need to lock here.
        synchronized (mLock) {
            if (!constrainedWorkSpecs.isEmpty()) {
                Logger.get().debug(TAG, String.format("Starting tracking for [%s]",
                        TextUtils.join(",", constrainedWorkSpecIds)));
                mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
                mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
            }
        }
    }
里面主要看mWorkManagerImpl.startWork(workSpec.id);

  @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public void startWork(String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras) {
        mWorkTaskExecutor
                .executeOnBackgroundThread(
                        new StartWorkRunnable(this, workSpecId, runtimeExtras));
    }

 public StartWorkRunnable(
            WorkManagerImpl workManagerImpl,
            String workSpecId,
            WorkerParameters.RuntimeExtras runtimeExtras) {
        mWorkManagerImpl = workManagerImpl;
        mWorkSpecId = workSpecId;
        mRuntimeExtras = runtimeExtras;
    }

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

 public boolean startWork(String id, WorkerParameters.RuntimeExtras runtimeExtras) {
        WorkerWrapper workWrapper;
        synchronized (mLock) {
            // Work may get triggered multiple times if they have passing constraints
            // and new work with those constraints are added.
            if (mEnqueuedWorkMap.containsKey(id)) {
                Logger.get().debug(
                        TAG,
                        String.format("Work %s is already enqueued for processing", id));
                return false;
            }

            workWrapper =
                    new WorkerWrapper.Builder(
                            mAppContext,
                            mConfiguration,
                            mWorkTaskExecutor,
                            mWorkDatabase,
                            id)
                            .withSchedulers(mSchedulers)
                            .withRuntimeExtras(runtimeExtras)
                            .build();
            ListenableFuture<Boolean> future = workWrapper.getFuture();
            future.addListener(
                    new FutureListener(this, id, future),
                    mWorkTaskExecutor.getMainThreadExecutor());
            mEnqueuedWorkMap.put(id, workWrapper);
        }
        mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
        Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
        return true;
    }
最后通过processor开始工作

然后看workwrapper里面的run方法
        final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
            // Call mWorker.startWork() on the main thread.
            mWorkTaskExecutor.getMainThreadExecutor()
                    .execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Logger.get().debug(TAG, String.format("Starting work for %s",
                                        mWorkSpec.workerClassName));
                                mInnerFuture = mWorker.startWork();
                                future.setFuture(mInnerFuture);
                            } catch (Throwable e) {
                                future.setException(e);
                            }

                        }
                    });

可以看到mworker.startwork方法
  @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);
                }

            }
        });
        return mFuture;
    }
}
最后执行我们自己的dowork方法了

无约束任务简单总结:
1.workmanager执行enqueue()后会创建workcontinuationImpl对象执行enqueue()方法

  1. WorkContinuationImpl持有的EnqueueRunnable对象将任务添加到db,
    并交给Schedulers调度器去调度。
    3.调度器经过一系列判断会调用workmanager的startwork方法
    4.workmanager持有的startworkrunnable对象会交给process处理执行startwork
    5.processor会创建一个workwrapper对象,通过它调用worker的startwork方法,然后调用自己写的startwork方法

有约束条件的任务执行

在apk里面的androidmanifest里面注册了一系列广播

        <service
            android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
            android:enabled="@ref/0x7f040004"
            android:exported="false"
            android:directBootAware="false" />

        <service
            android:name="androidx.work.impl.background.systemjob.SystemJobService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:enabled="@ref/0x7f040005"
            android:exported="true"
            android:directBootAware="false" />

        <receiver
            android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
            android:enabled="true"
            android:exported="false"
            android:directBootAware="false" />

        <receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
            android:enabled="false"
            android:exported="false"
            android:directBootAware="false">

            <intent-filter>

                <action
                    android:name="android.intent.action.ACTION_POWER_CONNECTED" />

                <action
                    android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
            </intent-filter>
        </receiver>

点击进去可以看到

abstract class ConstraintProxy extends BroadcastReceiver {
    private static final String TAG = Logger.tagWithPrefix("ConstraintProxy");

    @Override
    public void onReceive(Context context, Intent intent) {
        Logger.get().debug(TAG, String.format("onReceive : %s", intent));
        Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
        context.startService(constraintChangedIntent);
    }

    /**
     * Proxy for Battery Not Low constraint
     */
    public static class BatteryNotLowProxy extends ConstraintProxy {
    }

    /**
     * Proxy for Battery Charging constraint
     */
    public static class BatteryChargingProxy extends ConstraintProxy {
    }
统一继承了constraintproxy下面看
 Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
        context.startService(constraintChangedIntent);
这个方法点击
  static Intent createConstraintsChangedIntent(@NonNull Context context) {
        Intent intent = new Intent(context, SystemAlarmService.class);
        intent.setAction(ACTION_CONSTRAINTS_CHANGED);
        return intent;
    }

  @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        if (intent != null) {
            mDispatcher.add(intent, startId);
        }
        // If the service were to crash, we want all unacknowledged Intents to get redelivered.
        return Service.START_REDELIVER_INTENT;
    }

注意这个ACTION_CONSTRAINTS_CHANGED

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        if (intent != null) {
            mDispatcher.add(intent, startId);
        }
        // If the service were to crash, we want all unacknowledged Intents to get redelivered.
        return Service.START_REDELIVER_INTENT;
    }

点击 mDispatcher.add(intent, startId);


 @MainThread
    public boolean add(@NonNull final Intent intent, final int startId) {
        Logger.get().debug(TAG, String.format("Adding command %s (%s)", intent, startId));
        assertMainThread();
        String action = intent.getAction();
        if (TextUtils.isEmpty(action)) {
            Logger.get().warning(TAG, "Unknown command. Ignoring");
            return false;
        }

        // If we have a constraints changed intent in the queue don't add a second one. We are
        // treating this intent as special because every time a worker with constraints is complete
        // it kicks off an update for constraint proxies.
        if (CommandHandler.ACTION_CONSTRAINTS_CHANGED.equals(action)
                && hasIntentWithAction(CommandHandler.ACTION_CONSTRAINTS_CHANGED)) {
            return false;
        }

        intent.putExtra(KEY_START_ID, startId);
        synchronized (mIntents) {
            boolean hasCommands = !mIntents.isEmpty();
            mIntents.add(intent);
            if (!hasCommands) {
                // Only call processCommand if this is the first command.
                // The call to dequeueAndCheckForCompletion will process the remaining commands
                // in the order that they were added.
                processCommand();
            }
        }
        return true;
    }
主要看processCommand();
点击进去主要看
mCommandHandler.onHandleIntent(mCurrentIntent, startId, SystemAlarmDispatcher.this);

@WorkerThread
    void onHandleIntent(
            @NonNull Intent intent,
            int startId,
            @NonNull SystemAlarmDispatcher dispatcher) {

        String action = intent.getAction();

        if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
            handleConstraintsChanged(intent, startId, dispatcher);
        } else if (ACTION_RESCHEDULE.equals(action)) {
            handleReschedule(intent, startId, dispatcher);
        } else {
            Bundle extras = intent.getExtras();
            if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
                Logger.get().error(TAG,
                        String.format("Invalid request for %s, requires %s.",
                                action,
                                KEY_WORKSPEC_ID));
            } else {
                if (ACTION_SCHEDULE_WORK.equals(action)) {
                    handleScheduleWorkIntent(intent, startId, dispatcher);
                } else if (ACTION_DELAY_MET.equals(action)) {
                    handleDelayMet(intent, startId, dispatcher);
                } else if (ACTION_STOP_WORK.equals(action)) {
                    handleStopWork(intent, startId, dispatcher);
                } else if (ACTION_EXECUTION_COMPLETED.equals(action)) {
                    handleExecutionCompleted(intent, startId, dispatcher);
                } else {
                    Logger.get().warning(TAG, String.format("Ignoring intent %s", intent));
                }
            }
        }
    }
因为之前我们看到的是ACTION_CONSTRAINTS_CHANGED
所以主要看  handleConstraintsChanged(intent, startId, dispatcher);
点击进去主要看
 ConstraintsCommandHandler changedCommandHandler =
                new ConstraintsCommandHandler(mContext, startId, dispatcher);
        changedCommandHandler.handleConstraintsChanged();

接下来看
 @WorkerThread
    void handleConstraintsChanged() {
        List<WorkSpec> candidates = mDispatcher.getWorkManager().getWorkDatabase()
                .workSpecDao()
                .getScheduledWork();

        // Update constraint proxy to potentially disable proxies for previously
        // completed WorkSpecs.
        ConstraintProxy.updateAll(mContext, candidates);

        // This needs to be done to populate matching WorkSpec ids in every constraint controller.
        mWorkConstraintsTracker.replace(candidates);

        List<WorkSpec> eligibleWorkSpecs = new ArrayList<>(candidates.size());
        // Filter candidates should have already been scheduled.
        long now = System.currentTimeMillis();
        for (WorkSpec workSpec : candidates) {
            String workSpecId = workSpec.id;
            long triggerAt = workSpec.calculateNextRunTime();
            if (now >= triggerAt && (!workSpec.hasConstraints()
                    || mWorkConstraintsTracker.areAllConstraintsMet(workSpecId))) {
                eligibleWorkSpecs.add(workSpec);
            }
        }

        for (WorkSpec workSpec : eligibleWorkSpecs) {
            String workSpecId = workSpec.id;
            Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
            Logger.get().debug(TAG, String.format(
                    "Creating a delay_met command for workSpec with id (%s)", workSpecId));
            mDispatcher.postOnMainThread(
                    new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));
        }

        mWorkConstraintsTracker.reset();
    }

最主要是
 Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
            Logger.get().debug(TAG, String.format(
                    "Creating a delay_met command for workSpec with id (%s)", workSpecId));
            mDispatcher.postOnMainThread(
                    new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));


创建了一个action为ACTION_DELAY_MET
然后放进addrunnable中
 static class AddRunnable implements Runnable {
        private final SystemAlarmDispatcher mDispatcher;
        private final Intent mIntent;
        private final int mStartId;

        AddRunnable(@NonNull SystemAlarmDispatcher dispatcher,
                @NonNull Intent intent,
                int startId) {
            mDispatcher = dispatcher;
            mIntent = intent;
            mStartId = startId;
        }

        @Override
        public void run() {
            mDispatcher.add(mIntent, mStartId);
        }
    }
又执行了add方法点击进去执行onAllConstraintsMet(Collections.singletonList(mWorkSpecId));可以看到执行了 
@Override
    public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
        // WorkConstraintsTracker will call onAllConstraintsMet with list of workSpecs whose
        // constraints are met. Ensure the workSpecId we are interested is part of the list
        // before we call Processor#startWork().
        if (!workSpecIds.contains(mWorkSpecId)) {
            return;
        }

        Logger.get().debug(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId));
        // Constraints met, schedule execution

        // Not using WorkManagerImpl#startWork() here because we need to know if the processor
        // actually enqueued the work here.
        // TODO(rahulrav@) Once WorkManagerImpl provides a callback for acknowledging if
        // work was enqueued, call WorkManagerImpl#startWork().
        boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);

        if (isEnqueued) {
            // setup timers to enforce quotas on workers that have
            // been enqueued
            mDispatcher.getWorkTimer()
                    .startTimer(mWorkSpecId, CommandHandler.WORK_PROCESSING_TIME_IN_MS, this);
        } else {
            // if we did not actually enqueue the work, it was enqueued before
            // cleanUp and pretend this never happened.
            cleanUp();
        }
    }
可以看出执行了startwork方法

总结起来就是一个大广播里面包含一系列低电量空闲什么的广播,当促发的时候就会促发ACTION_CONSTRAINTS_CHANGED,然后查询room数据库然后去执行startwork

相关文章

网友评论

      本文标题:jetpack的workmanage简单原理分析

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