WorkManager原理解析及兼容測試

WorkManager原理解析及兼容測試

前言:

  • WorkManager的基本功能和特性 能夠參考官方文檔,也能夠參考掘金上的中文翻譯,這裏就再也不累述android

  • Demo關鍵代碼參考api

    • 定義Worker,每次執行任務彈出系統通知,並打印log
@TargetApi(Build.VERSION_CODES.O)
    @NonNull
    @Override
    public Result doWork() {
        Data inputData = getInputData();
        LogHelper.logD("NotificationWorker#doWork, inputData:" + inputData.getKeyValueMap().toString());

        String text = inputData.getString("text");
     
        NotificationManager notificationManager = (NotificationManager)getApplicationContext().getSystemService(
            Context.NOTIFICATION_SERVICE);
        Notification.Builder builder = new Notification.Builder(getApplicationContext());
        Notification notification = builder
            .setTicker("WorkMgrNtf")
            .setContentTitle("WorkMgrNtf")
            .setContentText(text)
            .setSmallIcon(R.drawable.common_google_signin_btn_icon_dark)
            .build();
        notificationManager.notify(NTF_ID, notification);
        return Result.SUCCESS;
		}
複製代碼
    • 生成一個週期性WorkRequest用於按期執行Worker
Data inputData = new Data.Builder()
                    .putString("text", "PeriodicWorkRequest, ts:" + System.currentTimeMillis())
                    .build();
                Constraints constraint = new Constraints.Builder()
                    .setRequiredNetworkType(NetworkType.CONNECTED)
                    .build();
                long period = Math.max(5, Integer.parseInt(mEdtPeriod.getText().toString()));
                final WorkRequest workRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, period,
                    TimeUnit.MINUTES)
                    .setConstraints(constraint)
                    .setInputData(inputData)
                    .build();
                mPeriodRequestId = workRequest.getId();
                WorkManager.getInstance().enqueue(workRequest);
                WorkManager.getInstance().getStatusById(workRequest.getId())
                    .observeForever(new Observer<WorkStatus>() {
                        @Override
                        public void onChanged(@Nullable WorkStatus workStatus) {
                            LogHelper.logD(
                                "OnWorkStatusChanged, requestId:" + workRequest.getId() + ", status:" + workStatus);
                        }
                    });
複製代碼

核心功能及主流程源碼分析

WorkManager初始化

  • 業務層無需手動調用初始化代碼,apk構建過程當中會在androidManifest裏註冊了一個ContentProvider,以下:
<provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:exported="false"
            android:multiprocess="true"
            android:authorities="com.example.ali.workmgrdemo.workmanager-init"
            android:directBootAware="false" />
複製代碼
  • app進程初始化的時候會自動install這個Provider,執行onCreate方法,從而執行WorkManager的初始化邏輯:
@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;
		}
		......
}
複製代碼
  • 最終會執行靜態方法WorkManagerImpl#initialize,實例化單例
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        synchronized (sLock) {
            if (sDelegatedInstance == null) {
                context = context.getApplicationContext();
                if (sDefaultInstance == null) {
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor());
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }
複製代碼

WorkRequest執行流程

OneTimeWorkRequest執行流程

wrokmanager_flow (1).png

  • 首先,Demo裏直接訪問的WorkManager#getInstance,返回的是一個WorkManagerImpl實例,而WorkManagerImpl的實例能夠委派給外部去構造,只不過加了RestrictTo.Scope.LIBRARY_GROUP,業務層無法替換。
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static void setDelegate(WorkManagerImpl delegate) {
        synchronized (sLock) {
            sDelegatedInstance = delegate;
        }
    }
複製代碼
  • 而後,WorkManagerImpl的主要任務是部分db數據讀操做和線程調度,真正的任務操做都封裝在具體的Runnable中,譬如:StartWorkRunnable,StopWorkRunnable,EnqueueRunnable,而這些Runnable所有都在同一個backThread中執行
  • 任務調度以前,會先寫入db持久化,而且判斷是否有父任務沒有執行(鏈式執行,上圖未體現)
  • 知足執行條件,會再次從db讀取全部知足條件的WorkSpec
  • 選擇合適的調度器(取決於系統版本,後面會詳細說明)執行週期任務,好比上圖中的SystemJobScheduler,這裏咱們重點關注一次性任務的執行流程
  • 對於一次性任務,會經過GreedyScheuler當即執行
  • 最終會生成一個WorkerWrapper(實現了Runnable接口),在backThread實例化Worker(咱們自定義的NotificationWorker),調用doWork執行業務代碼。

PeriodicWorkRequest執行流程

內置的線程池

public class WorkManagerTaskExecutor implements TaskExecutor {

    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());

    private final Executor mMainThreadExecutor = new Executor() {
        @Override
        public void execute(@NonNull Runnable command) {
            postToMainThread(command);
        }
    };

    // Avoiding synthetic accessor.
    volatile Thread mCurrentBackgroundExecutorThread;
    private final ThreadFactory mBackgroundThreadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(@NonNull Runnable r) {
            // Delegate to the default factory, but keep track of the current thread being used.
            Thread thread = Executors.defaultThreadFactory().newThread(r);
            mCurrentBackgroundExecutorThread = thread;
            return thread;
        }
    };

    private final ExecutorService mBackgroundExecutor =
            Executors.newSingleThreadExecutor(mBackgroundThreadFactory);

    @Override
    public void postToMainThread(Runnable r) {
        mMainThreadHandler.post(r);
    }

    @Override
    public Executor getMainThreadExecutor() {
        return mMainThreadExecutor;
    }

    @Override
    public void executeOnBackgroundThread(Runnable r) {
        mBackgroundExecutor.execute(r);
    }

    @Override
    public Executor getBackgroundExecutor() {
        return mBackgroundExecutor;
    }

    @NonNull
    @Override
    public Thread getBackgroundExecutorThread() {
        return mCurrentBackgroundExecutorThread;
    }
}
複製代碼
  • 能夠看到,mBackgroundExecutor是一個單線程池,至於爲何要用單線程來執行worker任務,從上面添加任務的流程能夠推測緣由以下:
    • 涉及DB操做,必須在非UI線程執行任務
    • 保證任務執行的前後順序
    • 避免DB多線程讀寫操做引發數據記錄不一致

Scheduler任務調度器

Scheduler列表

  • 首先,任務調度器是一個size固定爲2的列表:
public @NonNull List<Scheduler> getSchedulers() {
        // Initialized at construction time. So no need to synchronize.
        if (mSchedulers == null) {
            mSchedulers = Arrays.asList(
                    Schedulers.createBestAvailableBackgroundScheduler(mContext, this),
                    new GreedyScheduler(mContext, this));
        }
        return mSchedulers;
    }
複製代碼
  • GreedyScheduler常駐,主要用來執行一次性任務
  • 除了GreedySchefuler常駐以外,另外一個Scheduler會根據條件(系統版本,是否安裝了PlayService)選擇最合適的:
static @NonNull Scheduler createBestAvailableBackgroundScheduler(
            @NonNull Context context,
            @NonNull WorkManagerImpl workManager) {

        Scheduler scheduler;
        boolean enableFirebaseJobService = false;
        boolean enableSystemAlarmService = false;

        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
            scheduler = new SystemJobScheduler(context, workManager);
            setComponentEnabled(context, SystemJobService.class, true);
            Logger.debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
        } else {
            try {
                scheduler = tryCreateFirebaseJobScheduler(context);
                enableFirebaseJobService = true;
                Logger.debug(TAG, "Created FirebaseJobScheduler");
            } catch (Exception e) {
                // Also catches the exception thrown if Play Services was not found on the device.
                scheduler = new SystemAlarmScheduler(context);
                enableSystemAlarmService = true;
                Logger.debug(TAG, "Created SystemAlarmScheduler");
            }
        }

        try {
            Class firebaseJobServiceClass = Class.forName(FIREBASE_JOB_SERVICE_CLASSNAME);
            setComponentEnabled(context, firebaseJobServiceClass, enableFirebaseJobService);
        } catch (ClassNotFoundException e) {
            // Do nothing.
        }

        setComponentEnabled(context, SystemAlarmService.class, enableSystemAlarmService);

        return scheduler;
    }
複製代碼
* 若是apiLevel>=23,選擇SystemJobScheduler,內部基於JobScheculer實現任務調度,包括週期任務,約束條件等
* 若是apiLevel<23
	* 優先構造FirebaseJobService
	* 若是系統沒有安裝PlayService,會拋出異常,這時候選擇SystemAlarmScheduler,其內部是基於AlarmManager實現週期任務
複製代碼

週期任務Scheduler

  • SystemAlarmScheduler
    • 利用AlarmManager生成定時任務,核心代碼以下:
if (!workSpec.hasConstraints()) {
                Logger.debug(TAG, String.format("Setting up Alarms for %s", workSpecId));
                Alarms.setAlarm(mContext, dispatcher.getWorkManager(), workSpecId, triggerAt);
            } else {
                // Schedule an alarm irrespective of whether all constraints matched.
                Logger.debug(TAG,
                        String.format("Opportunistically setting an alarm for %s", workSpecId));
                Alarms.setAlarm(
                        mContext,
                        dispatcher.getWorkManager(),
                        workSpecId,
                        triggerAt);
複製代碼
  • SystemJobScheduler
    • Job的約束條件配置
JobInfo.Builder builder = new JobInfo.Builder(jobId, mWorkServiceComponent)
                .setRequiredNetworkType(jobInfoNetworkType)
                .setRequiresCharging(constraints.requiresCharging())
                .setRequiresDeviceIdle(constraints.requiresDeviceIdle())
                .setExtras(extras);
複製代碼
    • Job的週期參數配置
if (workSpec.isPeriodic()) {
            if (Build.VERSION.SDK_INT >= 24) {
                builder.setPeriodic(workSpec.intervalDuration, workSpec.flexDuration);
            } else {
                Logger.debug(TAG,
                        "Flex duration is currently not supported before API 24. Ignoring.");
                builder.setPeriodic(workSpec.intervalDuration);
            }
        } 
複製代碼
  • FirebaseJobService
    • Job的約束條件配置
private int[] getConstraints(WorkSpec workSpec) {
        Constraints constraints = workSpec.constraints;
        List<Integer> mConstraints = new ArrayList<>();

        if (Build.VERSION.SDK_INT >= 23 && constraints.requiresDeviceIdle()) {
            mConstraints.add(Constraint.DEVICE_IDLE);
        }

        if (constraints.requiresCharging()) {
            mConstraints.add(Constraint.DEVICE_CHARGING);
        }

        if (constraints.requiresBatteryNotLow()) {
            Logger.warning(TAG,
                    "Battery Not Low is not a supported constraint "
                            + "with FirebaseJobDispatcher");
        }

        if (constraints.requiresStorageNotLow()) {
            Logger.warning(TAG, "Storage Not Low is not a supported constraint "
                    + "with FirebaseJobDispatcher");
        }

        switch (constraints.getRequiredNetworkType()) {
            case NOT_REQUIRED: {
                // Don't add a constraint. break; } case CONNECTED: { mConstraints.add(Constraint.ON_ANY_NETWORK); break; } case UNMETERED: { mConstraints.add(Constraint.ON_UNMETERED_NETWORK); break; } case NOT_ROAMING: { Logger.warning(TAG, "Not Roaming Network is not a supported constraint with " + "FirebaseJobDispatcher. Falling back to Any Network constraint."); mConstraints.add(Constraint.ON_ANY_NETWORK); break; } case METERED: { Logger.warning(TAG, "Metered Network is not a supported constraint with " + "FirebaseJobDispatcher. Falling back to Any Network constraint."); mConstraints.add(Constraint.ON_ANY_NETWORK); break; } } return toIntArray(mConstraints); } 複製代碼
    • Job的週期參數配置
private void setExecutionTrigger(Job.Builder builder, WorkSpec workSpec) {
        if (Build.VERSION.SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
            builder.setTrigger(createContentUriTriggers(workSpec));
        } else if (workSpec.isPeriodic()) {
            builder.setTrigger(createPeriodicTrigger(workSpec));
            builder.setRecurring(true);
        } else {
            builder.setTrigger(Trigger.NOW);
        }
    }
複製代碼

Worker數據結構

約束條件控制

支持的約束條件

  • 網絡類型約束,包括如下幾種類型:
    undefined
  • 電池狀態相關約束,包括:
    • 只有正在充電狀態下,才能執行
    • 處於低電量狀態下,限制執行
  • 存儲狀態相關約束,只有一個:
    • 可用存儲空間偏低的狀況下,不容許執行

實現原理

  • 每一種約束條件都對應一個ConstraintController,以下圖所示:
    undefined
  • 基於系統廣播實現的條件約束
    • 總體結構圖 bash

      workmanager_constraints.png

      • ConstraintController:
        • 持有全部須要約束的WorkSpec
        • 持有ConstaintTracker實例(單例,不一樣約束類型對應不一樣實例)
        • 實現了ConstraintListener接口,註冊到ConstraintTracker監聽約束狀態的變化
      • ConstraintTracker:
        • 用於實時追蹤約束條件的狀態,每一種約束條件都實現了本身的派生類
        • 只有當ConstraintController接收到須要約束的WorkSpec,纔會調用startTracking,從而開始註冊動態廣播,監聽對應的系統狀態(網絡,電量等)
    • 添加一個網絡條件約束,流程分析網絡

兼容性測試

  • 測試樣本
    • 測試機: | 機器編號 | 機器型號 | 系統版本 | | -------- | -------- | -------- | | A | Meizu | 5.1 | | B | Google Pixel | 7.0 | | C | Google Pixel | 9.0 |
    • 測試指標:
      • 在worker線程,是否支持生成系統通知
      • 在worker線程,是否支持發起網絡請求
      • 週期任務在進程被強殺後來是否依然可以自動執行
  • 測試數據
    • ABC均支持生成系統通知以及發起網絡請求
    • 週期任務的執行結果:
      • 測試機A
10-11 11:28:46.031 3030-3129/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
複製代碼
強殺以後任務沒有執行,重啓app以後,連續執行多個任務(具體第一個任務間隔約150min,任務週期爲15min,恰好累計10個任務未執行)
複製代碼
10-11 14:02:59.727 9889-9950/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:04.878 9889-9953/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:09.974 9889-9956/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:15.093 9889-9959/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:20.208 9889-9950/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:25.334 9889-9953/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:30.406 9889-9956/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:35.489 9889-9959/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:40.665 9889-9950/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:43.777 9889-9953/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
複製代碼
  • * 測試機B
    複製代碼
2018-10-10 22:08:47.614 15986-16054/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 22:26:36.969 15986-16287/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 22:56:17.391 16769-16797/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 23:10:18.087 17082-17104/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 23:23:42.589 17082-17290/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
複製代碼
  • *  測試機C 
    複製代碼
2018-10-13 07:37:02.576 16995-17018/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
2018-10-13 07:52:02.524 18022-18053/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
2018-10-13 08:07:02.582 18554-18584/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
2018-10-13 08:22:01.989 18554-19170/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
複製代碼
  • 結論分析
    • 在worker線程,支持生成系統通知欄及發起網絡請求
    • 週期任務在app進程非存活狀態,可否順利執行存在版本依賴
      • 對於apilevel<23(6.0)的機器,沒有辦法保證週期任務的順利執行
      • 對於apilevel>=23的機器,能夠很好地保證週期任務的順利執行
  • 兼容風險
    • 對於apilevel>=23的非原生系統機器,JobScheculer是否依然能夠保證週期任務的順利執行,須要更多的測試數據證實

推薦的使用場景及注意事項

  • 用於創建自輪詢通道,週期獲取輪詢消息,用來push非實時性的消息及指令
相關文章
相關標籤/搜索