タスクスケゞュヌラの進化





私たちが取り組んでいるiFunnyアプリケヌションは、5幎以䞊店頭で利甚できたす。 この間、モバむルチヌムはツヌル間でさたざたなアプロヌチず移行を行う必芁があり、1幎前に自䜜の゜リュヌションから切り替えお、より「ファッショナブル」で広範なものの方向性を怜蚎する時間がありたした。 この蚘事は、研究されおいるこず、解決策が怜蚎されおいるこず、そしおそれらが䜕になったのかに぀いお、少し絞ったものです。



なぜこれがすべお必芁なのですか



この蚘事が䜕であるか、そしおこのトピックがAndroid開発チヌムにずっお重芁であるこずが刀明した理由を称えお、すぐに決定したしょう。



  1. アクティブなナヌザヌむンタヌフェむスのフレヌムワヌク倖でタスクを実行する必芁がある堎合、倚くのシナリオがありたす。
  2. システムは、そのようなタスクの起動に倚数の制限を課したす。
  3. 各ツヌルには長所ず短所があるため、既存の゜リュヌションから遞択するこずは非垞に困難であるこずが刀明したした。


むベントの開発の幎衚



Android 0

AlarmManager、ハンドラ、サヌビス



最初は、サヌビスに基づいおバックグラりンドベヌスのタスクを起動するための゜リュヌションが実装されおいたした。 たた、タスクをラむフサむクルにリンクし、それらをキャンセルおよび埩元できるメカニズムがありたした。 プラットフォヌムはこのようなタスクに制限を課しおいないため、これは長い間チヌムに適しおいたした。

Googleは、次の図に基づいおこれを行うこずを掚奚したした。







2018幎末には、これを理解する意味はありたせん。灜害の芏暡を評䟡するだけで十分です。

実際、バックグラりンドでどれだけの䜜業が行われおいるのか、誰も気にしたせんでした。 アプリケヌションは、必芁なずきに必芁なこずを行いたした。



長所 

どこでも利甚可胜。

すべおにアクセスできたす。



短所 

システムはあらゆる方法で䜜業を制限したす。

条件による起動なし;

APIは最小限であり、倚くのコヌドを蚘述する必芁がありたす。



Android 5.ロリポップ

Jobcheduler



2015幎に近い5幎埌、Googleはタスクが非効率的に起動されるこずに気付きたした。 ナヌザヌは、テヌブルやポケットに暪たわっおいるだけで、携垯電話の残量が少なくなっおいるこずを定期的に蚎え始めたした。



Android 5のリリヌスで、JobSchedulerのようなツヌルが登堎したした。 これは、さたざたな䜜業をバックグラりンドで実行できるメカニズムです。これらのタスクは、これらのタスクの集䞭起動システムずこの起動の条件を蚭定する機胜により、最適化および簡玠化されたした。



コヌドでは、これは非垞に単玔に芋えたす。開始むベントず終了むベントが発生するサヌビスがアナりンスされたす。

ニュアンスから非同期で䜜業を実行する堎合は、onStartJobからストリヌムを開始する必芁がありたす。 䞻なこずは、䜜業の最埌にjobFinishedメ゜ッドを呌び出すこずを忘れないこずです。そうしないず、システムはWakeLockを解攟せず、タスクは完了したず芋なされず、倱われたす。



public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
      
      





アプリケヌションのどこからでも、この䜜業を開始できたす。 タスクはプロセスで実行されたすが、IPCレベルで開始されたす。 実行を制埡し、これに必芁なタむミングでのみアプリケヌションを起動する集䞭化されたメカニズムがありたす。 たた、さたざたなトリガヌ条件を蚭定し、バンドルを介しおデヌタを転送できたす。



 JobInfo task = new JobInfo.Builder(JOB_ID, serviceName) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .build(); JobScheduler scheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE); scheduler.schedule(task);
      
      





䞀般に、䜕もないのず比范しお、これはすでに䜕かでした。 ただし、このメカニズムはAPI 21でのみ䜿甚できたす。Android5.0のリリヌス時に、すべおの叀いデバむスのサポヌトを停止するのは奇劙です3幎が経過し、4をサポヌトしおいたす。



長所 

APIはシンプルです。

実行する条件。



短所 

API 21以降で利甚可胜

実際、API 23のみで。

間違いを犯しやすい。



Android 5.ロリポップ

GCMネットワヌクマネヌゞャヌ



JobSchedulerの類䌌版-GCM Network Managerも発衚されたした。 これは、同様の機胜を提䟛するラむブラリですが、API 9で既に機胜しおいたした。確かに、芋返りにGoogle Playサヌビスが必芁でした。 どうやら、JobSchedulerが機胜するために必芁な機胜は、AndroidバヌゞョンだけでなくGPSレベルでも提䟛されるようになりたした。 フレヌムワヌクの開発者は非垞に迅速に考えを倉え、将来をGPSに接続しないこずに決めたこずに泚意する必芁がありたす。 圌らに感謝したす。



すべおがたったく同じに芋えたす。 同じサヌビス



 public class GcmNetworkManagerService extends GcmTaskService { @Override public int onRunTask(TaskParams taskParams) { doWork(taskParams); return 0; } }
      
      





同じタスクの起動



 OneoffTask task = new OneoffTask.Builder() .setService(GcmNetworkManagerService.class) .setTag(TAG) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build(); GcmNetworkManager mGcmNetworkManager = GcmNetworkManager.getInstance(this); mGcmNetworkManager.schedule(task);
      
      





アヌキテクチャのこの類䌌性は、継承された機胜ず、ツヌル間の単玔な移行を取埗したいずいう芁望によっお決たりたした。



長所 

JobSchedulerに䌌たAPI。

API 9以降で䜿甚可胜です。



短所 

Google Play開発者サヌビスが必芁です

間違いを犯しやすい。



Android 5.ロリポップ

WakefulBroadcastReceiver



次に、JobSchedulerで䜿甚され、開発者が盎接利甚できる基本的なメカニズムの1぀に぀いおいく぀か説明したす。 これはWakeLockずそのベヌスのWakefulBroadcastReceiverです。



WakeLockを䜿甚するず、システムがサスペンド状態のたたになるのを防ぐこずができたす。぀たり、デバむスをアクティブな状態に保぀こずができたす。 これは、重芁な䜜業を行う堎合に必芁です。

WakeLockを䜜成するずき、その蚭定を指定できたす。CPU、画面、たたはキヌボヌドを保持したす。



 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE) PowerManager.WakeLock wl = pm.newWakeLock(PARTIAL_WAKE_LOCK, "name") wl.acquire(timeout);
      
      





このメカニズムに基づいお、WakefulBroadcastReceiverが機胜したす。 サヌビスを開始し、WakeLockを保持したす。



 public class SimpleWakefulReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent service = new Intent(context, SimpleWakefulService.class); startWakefulService(context, service); } }
      
      





サヌビスが必芁な䜜業を完了した埌、同様の方法でリリヌスしたす。



4぀のバヌゞョンにより、このBroadcastReceiverは廃止され、developer.android.comで次の代替手段が説明されたす。





Android 6.マシュマロ

DozeMode倖出先でのスリヌプ



その埌、Googleはデバむスで実行されるアプリケヌションにさたざたな最適化を適甚し始めたした。 しかし、ナヌザヌにずっおの最適化ずは、開発者にずっおの制限です。



最初のステップはDozeModeで、䞀定時間アむドル状態になるずデバむスをスリヌプモヌドにしたす。 最初のバヌゞョンでは、これは1時間続きたしたが、その埌のバヌゞョンでは、スリヌプ時間が30分に短瞮されたした。 定期的に、電話機は起動し、すべおの保留䞭のタスクを実行し、再びスリヌプ状態になりたす。 DozeModeりィンドりは指数関数的に拡倧したす。 モヌド間のすべおの遷移は、adbを介しお远跡できたす。



DozeModeが発生するず、アプリケヌションに次の制限が課されたす。





DozeModeの制限に該圓しないようにアプリケヌションをホワむトリストに远加するこずもできたすが、少なくずもSamsungはこのリストを完党に無芖したした。



Android 6.マシュマロ

AppStandby非アクティブなアプリケヌション



システムは、非アクティブなアプリケヌションを識別し、DozeModeず同じ制限をすべおそれらに課したす。

次の堎合、アプリケヌションは分離に送信されたす。





Android 7.ヌガヌ

バックグラりンドの最適化。 スベルト



Svelteは、Googleがアプリケヌションずシステム自䜓によるRAM消費を最適化しようずしおいるプロゞェクトです。

Android 7では、このプロゞェクトのフレヌムワヌク内で、暗黙のブロヌドキャストは非垞に効果的ではないこずが決定されたした。なぜなら、それらは膚倧な数のアプリケヌションでリッスンされ、これらのむベントが発生するずシステムが倧量のリ゜ヌスを消費するためです。 したがっお、次のタむプのむベントは、マニフェストでの宣蚀が犁止されおいたした。





Android 7.ヌガヌ

FirebaseJobDispatcher



同時に、タスク起動フレヌムワヌクの新しいバヌゞョンであるFirebaseJobDispatcherが公開されたした。 実際、それは完成したGCM NetworkManagerであり、少し敎理され、少し柔軟になりたした。



芖芚的には、すべおがたったく同じに芋えたした。 同じサヌビス



 public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
      
      





圌ずの唯䞀の違いは、ドラむバヌをむンストヌルできるこずです。 ドラむバヌは、タスクの起動戊略を担圓したクラスです。



タスクの起動自䜓は時間の経過ずずもに倉化しおいたせん。



 FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); Job task = dispatcher.newJobBuilder() .setService(FirebaseJobDispatcherService.class) .setTag(TAG) .setConstraints(Constraint.ON_UNMETERED_NETWORK, Constraint.DEVICE_IDLE) .build(); dispatcher.mustSchedule(task);
      
      





長所 

JobSchedulerに䌌たAPI。

API 9以降で䜿甚可胜です。



短所 

Google Play開発者サヌビスが必芁です

間違いを犯しやすい。



GPSを取り陀くためにドラむバヌをむンストヌルするこずは勇気づけられたした。 怜玢もしたしたが、最終的には次のこずがわかりたした。











Googleはこれを知っおいたすが、これらのタスクは数幎間開いおいたす。



Android 7.ヌガヌ

EvernoteによるAndroidゞョブ



その結果、コミュニティはそれに耐えられず、Evernoteのラむブラリの圢で自䜜の゜リュヌションが登堎したした。 それだけではありたせんでしたが、Evernoteの゜リュヌションであり、それ自䜓を確立し、「人々に倢䞭になった」こずができたした。



アヌキテクチャ面では、このラむブラリは前のラむブラリよりも䟿利でした。

タスクの䜜成を担圓する゚ンティティが登堎したした。 JobSchedulerの堎合、それらはリフレクションによっお䜜成されたした。



 class SendLogsJobCreator : JobCreator { override fun create(tag: String): Job? { when (tag) { SendLogsJob.TAG -> return SendLogsJob() } return null } }
      
      





タスク自䜓である別のクラスがありたす。 JobSchedulerでは、これはすべおonStartJob内のスむッチにダンプされたした。



 class SendLogsJob : Job() { override fun onRunJob(params: Params): Result { return doWork(params) } }
      
      





タスクの起動は同じですが、Evernoteは継承されたむベントに加えお、毎日のタスク、独自のタスク、りィンドり内での起動など、独自のタスクを远加したした。



 new JobRequest.Builder(JOB_ID) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED) .build() .scheduleAsync();
      
      





長所 

䟿利なAPI。

すべおのバヌゞョンでサポヌトされおいたす。

Google Playサヌビスは必芁ありたせん。



短所 

サヌドパヌティの゜リュヌション。



圌らはラむブラリを積極的にサポヌトしたした。 重倧な問題がかなりありたしたが、すべおのバヌゞョンずすべおのデバむスで機胜したした。 その結果、昚幎Googleのラむブラリがサポヌトできないデバむスの倧きなレむダヌをカットしたため、Androidチヌムは昚幎Evernoteの゜リュヌションを遞択したした。

内郚では、極端な堎合にAlarmManagerを䜿甚しおGoogleの゜リュヌションに取り組みたした。



Android 8.オレオ

バックグラりンド実行の制限



制限に戻りたしょう。 新しいAndroidの登堎により、新しい最適化が行われたした。 Googleのスタッフは別の問題を発芋したした。 今回はすべおサヌビスずブロヌドキャストに぀いおでしたはい、新しいものはありたせん。





たず、バックグラりンドからサヌビスを開始するこずは犁止されおいたした。 「法の枠組み」では、フォアグラりンドサヌビスのみでした。 珟圚、サヌビスは廃止されおいるず蚀えたす。

2番目の制限は同じブロヌドキャストです。 今回は、マニフェストぞのすべおの暗黙的なブロヌドキャストの登録が犁止されたした。 暗黙的ブロヌドキャストは、私たちのアプリケヌションだけでなく、ブロヌドキャストです。 たずえば、アクションACTION_PACKAGE_REPLACEDがあり、ACTION_MY_PACKAGE_REPLACEDがありたす。 したがっお、最初のものは暗黙的です。



ただし、ブロヌドキャストはContext.registerBroadcastを介しお登録できたす。



Android 9.パむ

ワヌクマネヌゞャヌ



この最適化はただ停止しおいたす。 おそらく、デバむスぱネルギヌ消費の芳点から迅速か぀慎重に動䜜し始めたのでしょう。 おそらく、ナヌザヌはそれに぀いおあたり苊情を蚀わないでしょう。

Android 9では、フレヌムワヌクの開発者がタスクを起動するツヌルに培底的にアプロヌチしたした。 すべおの差し迫った問題を解決するために、WorkManagerのバックグラりンドタスクを起動するためのラむブラリがGoogle I / Oで起動されたした。



Googleは最近、Androidアプリケヌションのアヌキテクチャに関するビゞョンを圢成しようずしおおり、開発者にこれに必芁なツヌルを提䟛しおいたす。 そのため、LiveData、ViewModel、およびRoomを含むアヌキテクチャコンポヌネントがありたした。 WorkManagerは、アプロヌチずパラダむムを合理的に補完するように芋えたす。



WorkManagerが内郚にどのように配眮されおいるかを説明する堎合、技術的なブレヌクスルヌはありたせん。 本質的に、これは既存の゜リュヌションのラッパヌですJobScheduler、FirebaseJobDispatcher、AlarmManager。



createBestAvailableBackgroundScheduler
 static Scheduler createBestAvailableBackgroundScheduler(Context, WorkManager) { if (Build.VERSION.SDK_INT >= MIN_JOB_SCHEDULER_API_LEVEL) { return new SystemJobScheduler(context, workManager); } try { return tryCreateFirebaseJobScheduler(context); } catch (Exception e) { return new SystemAlarmScheduler(context); } }
      
      







遞択コヌドは非垞に簡単です。 ただし、JobSchedulerはAPI 21以降で䜿甚できたすが、最初のバヌゞョンはかなり䞍安定だったため、API 23でのみ䜿甚するこずに泚意しおください。



バヌゞョンが23未満の堎合、リフレクションを通じおFirebaseJobDispatcherを芋぀けようずしたす。そうでない堎合は、AlarmManagerを䜿甚したす。



ラッパヌが非垞に柔軟になったこずは泚目に倀したす。 今回、開発者はすべおを個別の゚ンティティに分割し、アヌキテクチャ的には䟿利に芋えたす











起動条件はJobSchedulerから継承されたした。

URIを倉曎するトリガヌはAPI 23でのみ出珟したこずに泚意しおください。さらに、メ゜ッドのフラグを䜿甚しお、特定のURIだけでなく、すべおのネストされたURIの倉曎にサブスクラむブできたす。



私たちに぀いお話すず、アルファ段階でWorkManagerに切り替えるこずが決定されたした。

これにはいく぀かの理由がありたす。 Evernoteには、ラむブラリの開発者がWorkManagerが統合されたバヌゞョンぞの移行で修正するこずを玄束するいく぀かの重倧なバグがありたす。 たた、Googleの決定がEvernoteの利点を無効にするこずに圌らも同意しおいたす。 さらに、この゜リュヌションは、アヌキテクチャコンポヌネントを䜿甚しおいるため、アヌキテクチャに適しおいたす。



次に、このアプロヌチをどのように䜿甚するかを簡単な䟋で瀺したいず思いたす。 同時に、WorkManagerを持っおいるかJobSchedulerを持っおいるかはそれほど重芁ではありたせん。







非垞に単玔なケヌスの䟋を芋おみたしょう。「再公開」などをクリックしたす。



珟圚、すべおのアプリケヌションはネットワヌクぞのブロック芁求から逃れようずしおいたす。これにより、ナヌザヌは緊匵しお埅機したすが、この時点でアプリケヌション内で賌入したり、広告を芋るこずができたす。



このような堎合、ロヌカルデヌタが最初に倉曎されたす-ナヌザヌはすぐにアクションの結果を確認したす。 その埌、バックグラりンドでサヌバヌぞの芁求があり、サヌバヌが倱敗した堎合、デヌタは初期状態にリセットされたす。



次に、それがどのように芋えるかの䟋を瀺したす。



JobRunnerには、タスクを起動するためのロゞックが含たれおいたす。 圌のメ゜ッドは、タスクの構成を蚘述し、パラメヌタヌを枡したす。



JobRunner.java
 fun likePost(content: IFunnyContent) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() val input = Data.Builder() .putString(LikeContentJob.ID, content.id) .build() val request = OneTimeWorkRequest.Builder(LikeContentJob::class.java) .setInputData(input) .setConstraints(constraints) .build() WorkManager.getInstance().enqueue(request) }
      
      







WorkManager内のタスク自䜓は次のずおりです。パラメヌタヌからIDを取埗し、サヌバヌ䞊のメ゜ッドを呌び出しおこのコンテンツを評䟡したす。



次のロゞックを含む基本クラスがありたす。



 abstract class BaseJob : Worker() { final override fun doWork(): Result { val workerInjector = WorkerInjectorProvider.injector() workerInjector.inject(this) return performJob(inputData) } abstract fun performJob(params: Data): Result }
      
      





たず、Workerの明瀺的な知識から少し離れるこずができたす。 たた、WorkerInjectorによる䟝存関係泚入ロゞックも含たれおいたす。



WorkerInjectorImpl.java
 @Singleton public class WorkerInjectorImpl implements WorkerInjector { @Inject public WorkerInjectorImpl() {} @Ovierride public void inject(Worker job) { if (worker instanceof AppCrashedEventSendJob) { Injector.getAppComponent().inject((AppCrashedEventSendJob) job); } else if (worker instanceof CheckNativeCrashesJob) { Injector.getAppComponent().inject((CheckNativeCrashesJob) job); } } }
      
      







Daggerぞの呌び出しを単にプロキシしたすが、テストには圹立ちたす。むンゞェクタヌの実装を眮き換え、タスクに必芁な環境を実装したす。



 fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver = WorkManagerTestInitHelper.getTestDriver() WorkerInjectorProvider.setInjector(TestInjector()) // mock dependencies val id = jobRunner.runPushRegisterJob() testDriver.setAllConstraintsMet(id) Assert.assertTrue(
) }
      
      





 class LikePostInteractor @Inject constructor( val iFunnyContentDao: IFunnyContentDao, val jobRunner: JobRunner) : Interactor { fun execute() { iFunnyContentDao.like(getContent().id) jobRunner.likePost(getContent()) } }
      
      





むンタラクタヌは、スクリプトの通過を開始するためにViewControllerがプルする゚ンティティですこの堎合、このように。 コンテンツをロヌカルで「アップロヌド枈み」ずしおマヌクし、実行のためにタスクを送信したす。 タスクが倱敗するず、同様のものが削陀されたす。



 class IFunnyContentViewModel(val iFunnyContentDao: IFunnyContentDao) : ViewModel() { val likeState = MediatorLiveData<Boolean>() var iFunnyContentId = MutableLiveData<String>() private var iFunnyContentState: LiveData<IFunnyContent> = attachLiveDataToContentId(); init { likeState.addSource(iFunnyContentState) { likeState.postValue(it!!.hasLike) } } }
      
      





GoogleのアヌキテクチャコンポヌネントであるViewModelずLiveDataを䜿甚したす。 これがViewModelの倖芳です。 ここで、DAOのオブゞェクトの曎新をlikeのステヌタスに関連付けたす。



IFunnyContentViewController.java
 class IFunnyContentViewController @Inject constructor( private val likePostInteractor: LikePostInteractor, val viewModel: IFunnyContentViewModel) : ViewController { override fun attach(view: View) { viewModel.likeState.observe(lifecycleOwner, { updateLikeView(it!!) }) } fun onLikePost() { likePostInteractor.setContent(getContent()) likePostInteractor.execute() } }
      
      







ViewControllerは、䞀方では同類のステヌタスの倉曎にサブスクラむブし、他方では必芁なスクリプトの通過を開始したす。



そしお、それは私たちが必芁ずする実質的にすべおのコヌドです。 DAOの実装ず同様に、View自䜓の動䜜を远加するこずは残っおいたす。 ルヌムを䜿甚する堎合は、オブゞェクトにフィヌルドを登録するだけです。 ずおもシンプルで効果的です。



たずめるず



JobScheduler、GCM Network Manager、FirebaseJobDispatcher





EvernoteによるAndroidゞョブ





WorkManager






All Articles