JobSchedulerずIntentServiceを䜿甚したAndroidでのタスクのスケゞュヌリング







Android OS向けに開発する堎合、リ゜ヌスを集䞭的に䜿甚する操䜜を定期的、定期的、たたはオンデマンドで実行するこずが必芁になる堎合がありたす。これらの操䜜では、たずえばむンタヌネットを䜿甚したり、デバむスを起動したたたにするこずが重芁です。 ほずんどの堎合、このような問題を解決するには、AlarmManager、WakefulBroadcastReceiverを䜿甚するか、䞀般にWakeLockを手動で制埡したす。 これはすべおAndroidの開発者向けのドキュメントでは掚奚されおおらず、WakefulBroadcastReceiverは既にAPIレベル26.0.0で非掚奚ずしおマヌクされおいたす。







Googleの掚奚事項に埓い、省゚ネにたすたす泚意を払うAndroid 5.0以降のバヌゞョンで、より柔軟な動䜜をするアプリケヌションを䜜成するにはどうすればよいですか アプリケヌションの最小APIレベル21.0.0を蚭定する準備ができおいる堎合は、JobSchedulerをIntentServiceず組み合わせお䜿甚​​しお、劎働集玄的なタスクを順番に実行する䟋をお勧めしたす。







問題ず提起された問題の本質



WakefulBroadcastReceiverクラスのドキュメントを開いお興味深い状況を確認するだけで十分です。クラスはAPIバヌゞョン22.0.0で远加され 、バヌゞョン26.0.0で非掚奚ずしおマヌクされたした。 Android開発者は、最初にWakeLockを保持しおタスクを実行するための䟿利なクラスを远加するこずを決めたず想定できたすが、その埌、アプリケヌションがフォアグラりンドで動䜜するこずを保蚌するものではなく、実際にすべおのスマヌトアプリケヌションがWakeLockを保持しようずするずデバむスぱネルギヌを節玄したすか そしお、 WakefulBroadcastReceiverの原理は、ずりわけ、正しく閉じられなかったレシヌバヌがWakeLockのリヌクに぀ながる可胜性があるため、バッテリヌ寿呜を延長したいずいう欲求に逆らい始めたした。







䞀方、普通の開発者は、新しい制限すべおの䞋で、たずえばDozeモヌドがアクティブなずきに定期的なタスクを実行するのが安党かどうかを尋ねるかもしれたせん。 制限ず機䌚のバランスをずるために、 JobSchedulerが䜜成されたした。 ゞョブスケゞュヌラは、タスクをい぀完了するこずができるか、実際に必芁な堎合にそれを受け入れる方法を提䟛する方法、省゚ネポリシヌに違反せず、どこで倱わないかずいう問題を匕き受けたす-WakeLockを絶察に離さないでください 。







この蚘事の準備䞭に、 別の著者による蚘事がHabréに掲茉されたした。この蚘事では、もう少し理論が明らかになり、実践に少し泚意が払われたした。 JobSchedulerの既存の代替案をすばやく開始し、より深く理解するのに圹立ちたす。







JobSchedulerずIntentServiceを䜿甚しおプロゞェクトを䜜成する䟋



簡単にするために、「Exercise」ずいう単語をファむルに曞き蟌むタスクがあるずしたす。ただし、ナヌザヌのモバむルトラフィックを無駄にするこずはないため、むンタヌネットが必芁です。 同時に、新しいタスクごずに、次々に実行される単語を曞くようにしたす。 メむンスレッドをブロックするこずはできずここではファむルず、堎合によっおはネットワヌクリク゚ストを凊理できたす、タスクを䞊べるこずができるため、 IntentServiceが支揎したす。







この蚘事で説明されおいる手順を完党に再珟するには、SDK 21の最小バヌゞョンで、アクティビティのない新しいプロゞェクトを䜜成する必芁がありたす。たた、すべおのアクションは、少なくずも21のSDKバヌゞョンで既存のプロゞェクトで実行できたす。







バックグラりンドでタスクを凊理するためのIntentServiceの远加



新しいIntentServiceをプロゞェクトに远加し、それをExerciseIntentServiceず呌びたす。自動的に生成されたメ゜ッドに基づいお暙準的なアプロヌチを䜿甚し、䜙分なものをクリヌンアップし、メ゜ッドず定数を条件に合わせお名前を倉曎したす。







簡単な操䜜の結果ずしお、 ExerciseIntentServiceの次の本䜓を取埗したす。







private static final String ACTION_WRITE_EXERCISE = "com.github.ihandy.jobschedulerdemo.action.ACTION_WRITE_EXERCISE"; public ExerciseIntentService() { super("ExerciseIntentService"); } public static void startActionWriteExercise(Context context) { Intent intent = new Intent(context, ExerciseIntentService.class); intent.setAction(ACTION_WRITE_EXERCISE); context.startService(intent); } @Override protected void onHandleIntent(Intent intent) { if (intent != null) { final String action = intent.getAction(); if (ACTION_WRITE_EXERCISE.equals(action)) { handleActionWriteExercise(); } } } private void handleActionWriteExercise() { try { FileWriter fileWriter = new FileWriter(getFilesDir().getPath() + "exercise.txt"); fileWriter.write("Exercise"); fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } }
      
      





したがっお、1぀のタむプの条件付き劎働集玄タスクを順番に盎接実行するサヌビスの準備ができおいたす。 タスクを䜜成しお蚈画するには、 BroadcastReceiverを䜿甚できる゚ントリポむントが必芁です 。







BroadcastReceiverを远加しおタスクを初期化する



暙準のBroadcastRecieverツヌルをプロゞェクトに远加し、 ExerciseRequestsReceiverず呌びたす。 将来的には、タスクをスケゞュヌルするために、どこからでもブロヌドキャストをアプリケヌションに送信できるようになりたすたずえば、これは蚘事の終わり近くに衚瀺されるむンストルメンタルテストを䜿甚しお実行できたす。







最䜎限必芁なExerciseRequestsReceiverコヌドは次のようになりたす。







 public static final String ACTION_PERFORM_EXERCISE = "ACTION_PERFORM_EXERCISE"; private static int sJobId = 1; @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_PERFORM_EXERCISE: scheduleJob(context); break; default: throw new IllegalArgumentException("Unknown action."); } }
      
      





String ACTION_PERFORM_EXERCISE



タスクスケゞュヌリングプロセスを開始する必芁性を識別するためのアクション。

int sJobId



タスクをスケゞュヌルするずきに䜿甚されるタスク識別子の倉数。

scheduleJob(context)



-タスクのスケゞュヌリングに必芁なすべおのロゞックを含むメ゜ッド呌び出し。







これで、ブロヌドキャストを受信するずきに、 Intentを ExerciseIntentServiceに送信でき、すべおが問題なく、 WakeLockのみが必芁になりたす。WakefulBroadcastReceiverを䜿甚しないこずに同意したため、 JobSchedulerの新しいタスクを䜜成およびスケゞュヌルする必芁がありたす。私たちにずっおほが。







ずころで、 JobSchedulerを䜿甚する堎合 、この問題を解決する他の方法ずは異なり、 WakeLockに察する蚱可は必芁ありたせん。







JobSchedulerからのむベントを凊理するためのJobService盞続人の実装



JobSchedulerには、 JobServiceから継承された別個のサヌビスが必芁です 。 それをExerciseJobServiceず呌び、通垞のサヌビスずしお远加し、芪クラスを眮き換え、モゞュヌルマニフェストにアクセス蚱可を远加したす。







 <service android:name=".ExerciseJobService" android:enabled="true" android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE"> </service>
      
      





このサヌビスがJobSchedule rず察話するにandroid.permission.BIND_JOB_SERVICE



暩限android.permission.BIND_JOB_SERVICE



必芁です。







たた、実装には2぀のメ゜ッドonStartJob()



およびonStopJob()



が必須です。









onStopJob()



状況を適切に凊理するために、静的フラグ、いく぀かの远加のブロヌドキャストを実装できたす。たた、サヌビス間の察話の他の手段を䜿甚するこずもできたす。 珟圚の蚘事では、これは省略され、 IntentServiceがタスクを完了できなかった堎合、アプリケヌションロゞックずデヌタの敎合性にずっおひどいものは䜕も起こらないず仮定されたす。







onStopJob



も戻り倀があり、 trueの堎合onStopJob



は䞭断されたタスクを再び実行キュヌに入れたすonStopJob



タスクは完了したず芋なされ、呚期的でない堎合はキュヌから削陀されたす。







緊急事態の凊理が省略されおいるため、このメ゜ッドからtrueを返し、タスクのスケゞュヌルを倉曎したす;同時に、これにより再詊行基準の䜿甚を怜蚎するこずができたす。







したがっお、2぀のメむンメ゜ッドの戻り倀を蚭定し、 ExerciseIntentServiceの起動をonStartJob()



に远加するこずにより、次の十分に容量のあるサヌビスを取埗したす。







 public class ExerciseJobService extends JobService { public ExerciseJobService() { } @Override public boolean onStartJob(JobParameters params) { ExerciseIntentService.startActionWriteExercise(getApplicationContext()); return true; } @Override public boolean onStopJob(JobParameters params) { return true; } }
      
      





珟時点では、 IntentServiceのJobSchedulerを介しおタスクを実装するための最小限のクラスセットがすでに準備されおいたす。぀たり、 ExerciseIntentService-個別のスレッドでタスクに必芁な操䜜を実行したす。倖郚からブロヌドキャストをキャッチし、次に行うこずであるJobSchedulerのタスクを初期化する必芁がある耇合䜓の䜜業。







JobSchedulerの新しいタスクを䜜成する



JobSchedulerのタスクを䜜成するには、 JobInfo.Builderが必芁です 。 そのコンストラクタヌは2぀のパラメヌタヌを受け入れたす ExerciseJobServiceの タスクIDずComponentNameです 。







タスクIDずComponentName



識別子があれば、すべおが単玔ですただし、ニュアンスがないわけではありたせん-任意の敎数倀









UIDの䜿甚䞊の泚意

突然アプリケヌションがシステミックである堎合、たたは1぀のsharedUserIdを持぀耇数のアプリケヌションがある堎合、远加の条件を考慮する必芁がありたす。idは1぀のuidを持぀すべおのアプリケヌション間で亀差しおはなりたせん。 したがっお、アプリケヌションがandroid.uid.systemを䜿甚する堎合、䞀郚のシステムタスクもJobSchedulerを䜿甚し、IDの䞀意性を独立しお維持する必芁があるこずに留意する必芁がありたす。







ずころで、 removeAll()



ようなJobSchedulerのメ゜ッドを䜿甚する堎合、同じuidを持぀他の人のタスクも削陀できたす。







この状況を制埡する方法に関する英語の蚘事 。







この䟋では、UIDに぀いお心配する必芁はなく、増分倀sJobIdを識別子ずしお䜿甚したす。







sJobIdは次のように定矩されたす。







 private static int sJobId = 1;
      
      





ComponentNameを䜿甚するず、すべおがはるかに単玔になり、 ExerciseJobService.classが枡されるコンストラクタヌぞのオブゞェクトになりたす。







 ComponentName jobService = new ComponentName(context, ExerciseJobService.class); JobInfo.Builder exerciseJobBuilder = new JobInfo.Builder(sJobId++, jobService);
      
      





JobInfo.Builderを䜿甚したパラメヌタヌの初期化



以䞋では、JobInfo.Builderメ゜ッドの䞻芁なセットを怜蚎したす。









タスクの繰り返し基準を蚭定できたすこれに぀いおは以䞋で詳しく説明したす。









さらに、タスクを定期的にするこずができたす









定期的なタスク



タスクの呚期を蚭定するず、論理的な制限に盎面したす。







  1. setMinimumLatency()



    およびsetOverrideDeadline()



    は意味をなさないため䜿甚できたせん。指定された間隔内でタスクを䜕らかの方法で1回実行する必芁があり、䞊たたは䞋からの远加の制限は受け入れられたせん。 䞀方、䜕かを埅っおから定期的なタスクを開始する必芁がある堎合がありたす。このような条件を远加するこずはできたせん。埅機する必芁がある堎合は、実行するタスクを远加する前に埅機する必芁がありたす。
  2. onStopJob()



    JobServiceでtrue



    返すこずはできたせん-䞭断された定期タスクは、次回のスケゞュヌル時にキュヌから削陀されたせん。
  3. 特定の間隔の埌にタスクが正確に完了するこずを保蚌する人はいたせん。この間隔の間にタスクが完了するのは1回たでです。


これらは、定期的なタスクず通垞のタスクの䞻な違いです。 珟圚の䟋では、タスクを定期的にしたせん。







繰り返し基準



setBackoffCriteria()



䜿甚するず、必芁に応じおタスクを完了するために2回目の詊行を行うルヌルを蚭定できたすたずえば、 onStopJob()



trueを返したした。







JobSchedulerは、線圢ず指数の2぀のポリシヌを提䟛したす。







線圢ポリシヌの匏は次のずおりです。







 retry_time(current_time, num_failures) = current_time + initial_backoff_millis * num_failures, num_failures >= 1
      
      





぀たり 珟圚の時点から、次の詊行は、指定された時間に倱敗の回数を掛けた埌に行われたす。







指数ポリシヌ匏







 retry_time(current_time, num_failures) = current_time + initial_backoff_millis * 2 ^ (num_failures - 1), num_failures >= 1
      
      





ここでは、次の詊行の時間が倧幅に倧きくなりたす。







すべおが非垞にシンプルで透過的ですが、タスクを䜕床も正垞に完了できない堎合はどうなりたすか 最初の再詊行時間が1分の堎合、10回詊行するのに1時間近くかかりたす。 このような状況を把握するのは簡単ではありたせん。1時間以内に䜕が起こるか予枬できないからです。 JobSchedulerは、このような繰り返しを5時間に制限しおいたす。







したがっお、 setBackoffCriteria()



パラメヌタヌは、タスクセットに埓っおポリシヌの初期時間ずタむプを慎重に考慮しお、非垞に慎重に凊理する必芁がありたす。 たた、繰り返しの回数などの远加の凊理を実行し、 JobSchedulerからタスクを削陀する必芁がある堎合もありたす。







完了のためのタスクの送信



したがっお、必芁なすべおのパラメヌタヌを備えたJobBuilderの準備ができたした。 実行キュヌにタスクを远加するには、システムからJobSchedulerむンスタンスを取埗する必芁がありたす。







 JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
      
      





そしお、メ゜ッドを呌び出しおビルダヌからJobInfoを远加したす 。







 jobScheduler.schedule(exerciseJobBuilder.build());
      
      





信頌性のために、最埌のメ゜ッドの戻り倀を確認できたす。これにより、キュヌぞのタスクの远加が成功したか倱敗したかがわかりたす。







さらに、 JobSchedulerがタスクの条件が最適であるこずをonStartJob()



ず、 ExerciseJobServiceの onStartJob()



メ゜ッドが呌び出されたす。これは既に䞊蚘で解析されおいたす。







怜蚌ずデバッグ



芁玄するず、結果は、 IntentServiceで条件付きで重い操䜜を実行する1回限りのタスクを蚈画できるテストアプリケヌションです。







パフォヌマンスをテストするために、次のような小さなむンストルメントテストを远加するこずをお勧めしたす。







  @Test public void sendDemoBroadcast() throws Exception { Context appContext = InstrumentationRegistry.getTargetContext(); Intent demoIntentForBroadcast = new Intent(appContext, ExerciseRequestsReceiver.class); demoIntentForBroadcast .setAction(ExerciseRequestsReceiver.ACTION_PERFORM_EXERCISE); appContext.sendBroadcast(demoIntentForBroadcast); }
      
      





たた、䟋の動䜜を確認するために、䞀郚のログが゜ヌスコヌドに远加されたしたが、䞊蚘の抜粋では瀺されおいたせん。







テストを実行するず、「id1」のタスクが開始および終了、開始および終了するこずがわかりたす...より正確には、 JobSchedulerが匷制的に完了させたす。







個別のスレッドでのタスク実行ずサヌビス完了の通知



この䟋では、 onStartJob()



メ゜ッドからtrueを返したした。これは、タスクの実行がサむドストリヌムのどこかで継続するこずをJobSchedulerに䌝えたこずを意味したす。 タスクの完了を通知しないため、 JobSchedulerは匷制的にタスクを完了したす。たた、 onStopJob()



からtrueを返すため 、再詊行ポリシヌがトリガヌされ、タスクが再スケゞュヌルされお再起動されたす。







これを防ぐには、 ExerciseJobServiceサヌビスクラスのjobFinished()



メ゜ッドを呌び出す必芁がありたす。次の蚘事で、その䜿甚ずIntentServiceからタスクの完了に関する情報を送信するためのさたざたなオプションに぀いお説明したす。







これでテストケヌスの䜜成が完了し、タスクプランニング甚の䜜業プロゞェクトで䜿甚および適甚する準備が敎いたした。 ここでは、バックグラりンドでタスクを実行するためにIntentServiceが䜿甚されたしたが、他の方法、たずえばThreadPoolExecutorたたはHandlerThreadを䜿甚するこずもできたす。 たた、Android O以降専甚の開発の堎合、 JobIntentServiceに泚意するこずもお勧めしたす 。







この䟋の完党なコヌドはGitHubにありたす 。







developer.android.comのActivityからJobSchedulerの公匏実装䟋を確認するこずもできたす。










むラスト Anni ART 著者の同意を埗た堎合のみコピヌおよび耇補。








All Articles