【Jetpack】WorkManager
【JetPack】WorkManager
轉載自享學課堂-derry,非常感謝derry的講解
WorkManager有什么用:
一:確保重要的后臺任務,一定會被執行,后臺任務(例如:非及時性的 (請求服務器 及時性) 上傳,下載,同步數據 等)
二:內部對電量進行了優化,不需要我們去處理電量優化了
三:API 14 到 最新版本,都可以使用WorkManager來管你你的后臺任務
四:注意:WorkManager不能做保活操作
五:調度,管理,執行的后臺任務的場景,通常是是可延遲的后臺任務
WorkManager概述
你的任務不可能總是在前臺,但是還要確保你的那些重要任務執行,你們就可以放置 在后臺執行,那么WorkManager就能夠展現萬丈光芒了
WorkManger是Android Jetpack提供執行后臺任務管理的組件,它適用于需要保證 系統即使應用程序退出也會運行的任務,WorkManager API可以輕松指定可延遲的 異步任務以及何時運行它們,這些API允許您創建任務并將其交給WorkManager立 即運行或在適當的時間運行。
WorkManager根據設備API級別和應用程序狀態等因素選擇適當的方式來運行任務。如果WorkManager在應用程序運行時執行您的任務之一,WorkManager可以 在您應用程序進程的新線程中運行您的任務。如果您的應用程序未運行, WorkManager會選擇一種合適的方式來安排后臺任務 - 具體取決于設備API級別和包含的依賴項,WorkManager可能會使用 JobScheduler,Firebase JobDispatcher或 AlarmManager
?
WorkManager的各個角色
Worker:可以這樣理解,指定需要執行的任務,可以成為Workder類的子類,在實現的方法中,就可以執行任務邏輯了
WorkRequest:可以這樣理解,執行一項單一的任務
第一點:必須知道 WorkRequest對象必須指定Work執行的任務
第二點:需要知道 WorkRequest都有一個自動生成的唯一ID,可以使用ID執行取消 排隊任務 或 獲取任務狀態等操作
第三點:需要知道 WorkRequest是一個抽象的類;系統默認實現子類 OneTimeWorkRequest或PeriodicWorkRequest
第四點:需要知道 WorkRequest.Builder創建WorkRequest對象;相應的子類:OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder
第五點:需要知道 Constraints:指定對任務運行時間的限制(任務約束);使用 Constraints.Builder創建Constraints對象 ,并傳遞給WorkRequest.Builder
?
WorkManager的簡述:
WorkManager:排隊和管理工作請求;將WorkRequest 對象傳遞WorkManager的 任務隊列(注意:如果未指定任何約束, WorkManager立即運行任務)
WorkStatus的簡述:
WorkStatus:包含有關特定任務的信息;可以使用LiveData保存 WorkStatus對象,監聽任務狀態;如LiveData
分享使用文章
WorkManager的基本使用
官方文檔
WorkManage源碼分析中
?
為什么workmanager可以在app殺死之后還可以繼續工作,那是因為,這是由android系統級別的服務開啟的,數據存儲在room數據庫里,由系統輪詢,反射執行代碼塊,權限很大
?
1.初始化工作:
WorkManager.getInstance(this) // 各種初始化的工作?
2.加入隊列,觸發后臺任務工作:
.enqueue(oneTimeWorkRequest2); // 加入隊列,觸發后臺任務工作一、進入getInstance 初始化
WorkManagerImpl @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {synchronized (sLock) {WorkManagerImpl instance = getInstance();if (instance == null) {Context appContext = context.getApplicationContext();if (appContext instanceof Configuration.Provider) {initialize(appContext,((Configuration.Provider) appContext).getWorkManagerConfiguration());instance = getInstance(appContext);} else {throw new IllegalStateException("WorkManager is not initialized properly. You "+ "have explicitly disabled WorkManagerInitializer in your manifest, "+ "have not manually called WorkManager#initialize at this point, and "+ "your Application does not implement Configuration.Provider.");}}return instance;}}第一個if不會進來,為什么呢,因為這是第二次執行,APK清單文件里面(第一次)執行 ,此時我們打開apk查看清單文件發現
<providerandroid:name="androidx.work.impl.WorkManagerInitializer"android:exported="false"android:multiprocess="true"android:authorities="com.derry.workmanagersimple.workmanager-init"android:directBootAware="false" />在這里實例化,同樣走到了上面初始化的方法,初始化的時候,最終返回workmanagerImpl的實現類
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class WorkManagerInitializer extends ContentProvider {@Overridepublic boolean onCreate() {// Initialize WorkManager with the default configuration.WorkManager.initialize(getContext(), new Configuration.Builder().build());return true;} }現在返回到初始化的代碼的第二個if里面,new WorkManagerImpl
最終會走到一個方法,這里初始化了room的數據庫,作為持久性保存的地方,以及其他配置信息
@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()));List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor);Processor processor = new Processor(context,configuration,workTaskExecutor,database,schedulers);internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);}private void internalInit(@NonNull Context context,@NonNull Configuration configuration,@NonNull TaskExecutor workTaskExecutor,@NonNull WorkDatabase workDatabase,@NonNull List<Scheduler> schedulers,@NonNull Processor processor) {context = context.getApplicationContext();mContext = context;mConfiguration = configuration;mWorkTaskExecutor = workTaskExecutor;mWorkDatabase = workDatabase;mSchedulers = schedulers;mProcessor = processor;mPreferenceUtils = new PreferenceUtils(workDatabase);mForceStopRunnableCompleted = false;// Checks for app force stops.mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));}其中有一個貪婪執行器,埋下伏筆
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)@NonNullpublic List<Scheduler> createSchedulers(@NonNull Context context,@NonNull TaskExecutor taskExecutor) {return Arrays.asList(Schedulers.createBestAvailableBackgroundScheduler(context, this),// Specify the task executor directly here as this happens before internalInit.// GreedyScheduler creates ConstraintTrackers and controllers eagerly.new GreedyScheduler(context, taskExecutor, this));}返回來,在代碼里getInstance的時候,是第二次初始化,就只是獲取了對應的workmanagerImpl類,單例的
初始化環節結束
二、執行流程
首先上圖,整體的類uml關系圖
?
首先進入enque
workmanagerImpl
@Override@NonNullpublic 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
@Overridepublic @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 來看一下做了什么
@Overridepublic 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));}}@VisibleForTestingpublic void scheduleWorkInBackground() {WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();Schedulers.schedule( //獲取配置的信息,開啟執行調度workManager.getConfiguration(),workManager.getWorkDatabase(),workManager.getSchedulers());}來看一下schedule,保存了所有的manager,使用了貪婪執行器GreedyScheduler,添加
public static void schedule(@NonNull Configuration configuration,@NonNull WorkDatabase workDatabase,List<Scheduler> schedulers) {if (schedulers == null || schedulers.size() == 0) {return;} //遍歷。將任務同步到數據庫WorkSpecDao workSpecDao = workDatabase.workSpecDao();List<WorkSpec> eligibleWorkSpecs;//開啟事務。可以回滾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) {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) { //這里對應之前的貪婪執行器GreedySchedulerscheduler.schedule(eligibleWorkSpecsArray);}}}GreedyScheduler
@Overridepublic void schedule(@NonNull WorkSpec... workSpecs) {...registerExecutionListenerIfNeeded();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()) {//有約束的執行流程if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {// Ignore requests that have an idle mode constraint.Logger.get().debug(TAG,String.format("Ignoring WorkSpec %s, Requires device idle.",workSpec));} else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {// Ignore requests that have content uri triggers.Logger.get().debug(TAG,String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",workSpec));} else {constrainedWorkSpecs.add(workSpec);constrainedWorkSpecIds.add(workSpec.id);}} else { //無約束條件的執行流程Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));mWorkManagerImpl.startWork(workSpec.id);}}}synchronized (mLock) {if (!constrainedWorkSpecs.isEmpty()) {Logger.get().debug(TAG, String.format("Starting tracking for [%s]",TextUtils.join(",", constrainedWorkSpecIds)));mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);}}}startwork在impl里
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)public void startWork(@NonNull String workSpecId,@Nullable WorkerParameters.RuntimeExtras runtimeExtras) { //拿到線程池,將任務丟給線程池執行mWorkTaskExecutor.executeOnBackgroundThread(new StartWorkRunnable(this, workSpecId, runtimeExtras));}具體看一下這個線程
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class StartWorkRunnable implements Runnable {private WorkManagerImpl mWorkManagerImpl;private String mWorkSpecId;private WorkerParameters.RuntimeExtras mRuntimeExtras;public StartWorkRunnable(WorkManagerImpl workManagerImpl,String workSpecId,WorkerParameters.RuntimeExtras runtimeExtras) {mWorkManagerImpl = workManagerImpl;mWorkSpecId = workSpecId;mRuntimeExtras = runtimeExtras;}@Overridepublic void run() {mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);} }Processor
public boolean startWork(@NonNull String id,@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {WorkerWrapper workWrapper;... //將任務包裹起來,交給線程池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;}WorkerWrapper
@WorkerThread@Overridepublic void run() {mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);mWorkDescription = createWorkDescription(mTags);runWorker();}?
private void runWorker() {...// Call mWorker.startWork() on the main thread.mWorkTaskExecutor.getMainThreadExecutor().execute(new Runnable() {@Overridepublic 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);}}});...}Worker
doWork就是我們實現的work類的實現方法,因為步驟都是異步操作,所以可以執行睡眠等操作
@Overridepublic final @NonNull ListenableFuture<Result> startWork() {mFuture = SettableFuture.create();getBackgroundExecutor().execute(new Runnable() {@Overridepublic void run() {try {Result result = doWork(); //doWorkmFuture.set(result);} catch (Throwable throwable) {mFuture.setException(throwable);}}});return mFuture;}@WorkerThreadpublic abstract @NonNull Result doWork();?
至此,主線流程打通
三、一些疑問
1、為什么飛行模式到網絡連接打開就能收到work的啟動
應用殺掉以后,系統服務還在。會有‘輪詢機’會讀取room數據庫,apk的清單文件,已經把所有的信息保存進去了
當網絡打開的時候,接收信息的是誰呢,
ConstraintProxy
abstract class ConstraintProxy extends BroadcastReceiver {private static final String TAG = Logger.tagWithPrefix("ConstraintProxy");@Overridepublic void onReceive(Context context, Intent intent) {Logger.get().debug(TAG, String.format("onReceive : %s", intent));Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);context.startService(constraintChangedIntent);} }?
CommandHandler
static Intent createConstraintsChangedIntent(@NonNull Context context) {Intent intent = new Intent(context, SystemAlarmService.class);intent.setAction(ACTION_CONSTRAINTS_CHANGED);return intent;}?
SystemAlarmService系統鬧鐘分發器
intent == createConstraintsChangedIntent返回 ? ? ? ? ?
startId == ACTION_CONSTRAINTS_CHANGED
mDispatcher.add(intent, startId);
SystemAlarmDispatcher
processCommand(); 執行此命令?
@MainThreadpublic 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;}?
@MainThread@SuppressWarnings("FutureReturnValueIgnored")private void processCommand() {assertMainThread();... //電池優化final PowerManager.WakeLock wakeLock = WakeLocks.newWakeLock(mContext,String.format("%s (%s)", action, startId));try {Logger.get().debug(TAG, String.format("Acquiring operation wake lock (%s) %s",action,wakeLock));wakeLock.acquire(); //注意這里mCommandHandler.onHandleIntent(mCurrentIntent, startId,.....} finally {processCommandLock.release();}}注意這里走到之前定義的字段ACTION_CONSTRAINTS_CHANGED
目的是更換標記將ACTION_CONSTRAINTS_CHANGED 換標記 ACTION_DELAY_MET
void onHandleIntent(@NonNull Intent intent,int startId,@NonNull SystemAlarmDispatcher dispatcher) {String action = intent.getAction();if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {handleConstraintsChanged(intent, startId, dispatcher);} else if (ACTION_RESCHEDULE.equals(action)) {handleReschedule(intent, startId, dispatcher);} else {Bundle extras = intent.getExtras();if (!hasKeys(extras, KEY_WORKSPEC_ID)) {Logger.get().error(TAG,String.format("Invalid request for %s, requires %s.",action,KEY_WORKSPEC_ID));} else {if (ACTION_SCHEDULE_WORK.equals(action)) {handleScheduleWorkIntent(intent, startId, dispatcher);} else if (ACTION_DELAY_MET.equals(action)) {handleDelayMet(intent, startId, dispatcher);} else if (ACTION_STOP_WORK.equals(action)) {handleStopWork(intent, dispatcher);} else if (ACTION_EXECUTION_COMPLETED.equals(action)) {handleExecutionCompleted(intent, startId);} else {Logger.get().warning(TAG, String.format("Ignoring intent %s", intent));}}}}接下來就會執行,目的是為了做執行操作,會重新添加add,所以上面的if會執行好幾次
else if (ACTION_DELAY_MET.equals(action)) {handleDelayMet(intent, startId, dispatcher);}這個執行操作一點一點往里點,最終會點到startwork,調用到我們自己的work實現類里,完成喚醒操作
public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {......synchronized (mLock) {if (mCurrentState == STATE_INITIAL) {mCurrentState = STATE_START_REQUESTED;Logger.get().debug(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId));// Constraints met, schedule execution// Not using WorkManagerImpl#startWork() here because we need to know if the// processor actually enqueued the work here. //銜接到了之前的starrkworkboolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);if (isEnqueued) {// setup timers to enforce quotas on workers that have// been enqueuedmDispatcher.getWorkTimer().startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this); ......}}?
WorkManager是干嘛用的?
答:處理非及時任務,舉例子:每天同步一次數據到服務器,這種類似的需求,不是及時執行,但是又保證會執行的非及時任務。
WorkManager是怎么保證,當我把APP殺掉后呢?
答:記錄用戶的所有信息并全部保存到數據庫,而并非保存在內存中,這樣做的好處,就是持久性保存記錄,所有APP被殺掉后 依然可以獲取所有任務信息。
你研究過WorKManager源碼,任務是怎么保證一定執行的呀?
答:Android操作系統會在系統級別服務中,來判斷用戶的約束條件,當約束條件滿足時就會執行任務,但是觸發檢測是采用廣播的形式處理的,例如:網絡連接成功 就觸發...。
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的【Jetpack】WorkManager的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么现在的智能手机,都被设计成不可更换
- 下一篇: 订阅内容解码失败(非base64码)_【