WorkManagerの原理解析と互換性テスト

24119 ワード

WorkManagerの原理解析と互換性テスト


前言:

  • WorkManagerの基本的な機能と特性は、公式ドキュメントを参照することも、掘金上の中国語翻訳を参照することもできます.ここでは、
  • について説明しません.
  • Demoキーコード参照
  • ワークを定義し、タスクの実行ごとにシステム通知をポップアップし、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;
    		}
    
  • 定期的にWorker
  • を実行するための周期的なWorkRequestを生成する.
     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() {
                            @Override
                            public void onChanged(@Nullable WorkStatus workStatus) {
                                LogHelper.logD(
                                    "OnWorkStatusChanged, requestId:" + workRequest.getId() + ", status:" + workStatus);
                            }
                        });
    

    コア機能と主流プロセスソース分析


    WorkManager初期化

  • ビジネス・レイヤは、初期化コードを手動で呼び出す必要はありません.apk構築中にandroidManifestにContentProviderが登録されます.次のようにします.
  • "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実行プロセス

  • まず、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 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常駐のほか、もう1つの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 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データ構造


    こうそくじょうけんせいぎょ


    サポートされる制約

  • ネットワークタイプ制約(
  • )
  • バッテリステータス関連制約:
  • は充電中の場合のみ、
  • を実行することができる.
  • が低電力状態にある場合、
  • の実行を制限する.
  • は、ステータス依存制約を1つだけ格納します.
  • 利用可能な記憶領域が低い場合、
  • の実行は許可する.

    実装の原理

  • 各制約条件は、次の図に示すように、ConstraintControllerに対応します.
  • システムブロードキャストに基づいて実現される条件制約
  • 全体構造図
  • 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の非リアルタイム性のメッセージおよび命令
  • に用いる.
    転載先:https://juejin.im/post/5cd6779851882569217b9b72