Edited at

今更ながらJobSchedulerを使うメモ

Android 8.0以前のバージョンでは、AlarmServiceで定期処理をスケジューリングするのが普通だったんじゃないかと思いますが、


アプリがフォアグラウンドにある場合、そのアプリはフォアグラウンド サービスとバックグラウンド サービスの両方を制限なく作成して実行できます。

...

そのウィンドウの終了時に、アプリは アイドル状態 であると見なされます。 システムはこの時点で、アプリがサービスの Service.stopSelf() メソッドを呼び出したかのように、アプリのバックグラウンド サービスを停止します。


ってことでまあAlarmServiceで定期処理できなくなったってことです(今更)。


多くの場合、アプリではバックグラウンド サービスを JobScheduler ジョブに置き換えることができます。


https://developer.android.com/about/versions/oreo/background.html?hl=ja

JobSchedulerとな(使い方分かってない)」ってなったわけですが、実はこれ、Android 5.0からあったんですね(知らなんだ)

まあそんな話はさておき、、


JobSchedulerとは

今更感ある話ですが、JobSchedulerは、アプリで行う必要があるバックグラウンドでの処理(Job)をなるべくユーザーエクスペリエンスに影響がないように実行しよう、というサービスです。

具体的には、ユーザーが他のこと(たとえばゲーム)を同じ端末上で行っていた場合、バックグラウンドで重い処理をするとパフォーマンスに影響するかもしれないので、ユーザーが端末を使用していないときに処理を行ったりということが簡単に実現できるということになります。


サンプル

Google samplesにサンプルが上がっていますが、なんとなく使いにくいところがあったのでちょっとカスタマイズしたものをこちらに用意しました。


Try

まず、android.app.job.JobServiceを継承したクラスを作ります。onStartJob(JobParameters params)onStopJob(JobParameters params)をオーバーライドする必要があります。onStartJobの中に実行したい処理を書いていく形になります。onStartJob内でもネットワークの通信は別のスレッドで行う必要があります。

また、Jobと付いているもののServiceなので、Manifestに登録する必要があります。


AndroidManifest.xml

<service

android:name=".service.MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />

android:permission="android.permission.BIND_JOB_SERVICE"というのがないとIllegalArgmentExceptionで死にます。

android:exported="true"がGoogleのサンプルにはありましたが、APIドキュメントにもexportしろとは書いていないですし、私が試した範囲では問題なく動作したので必要ないと思います。

Jobを登録するには以下のようにします。

JobInfo info = new JobInfo.Builder(1, new ComponentName(context, MyJobService.class))

.build();
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(info);

これだけです。簡単ですね!

JobInfo.Builderに分かりにくい項目があったのでメモします。


  • setPeriodic(long intervalMillis)

    JobをintervalMillisごとに繰り返します。この指定が15分未満だと強制的に15分にされるので注意が必要です。
    また、setOverrideDeadlineとは両立できません。両方指定すると見事にクラッシュします。


1日1回Jobを実行する場合はどういう実装にすればよいかという話

AlarmManagerを使用する場合はBOOT_COMPLETEDBroadcastReceiverでつらまえていたと思いますが、JonInfoにsetPersistedというのがあるのでこれをtrueにすればOKです。Jobが消えるというのは自分で明示的にキャンセルした場合とアプリをアップデートしたときになるので、MY_PACKAGE_REPLACEDで再びスケジュールしてやればいいです。

その上で、setPeriodic(1000 * 60 * 60 * 24)を設定すれば1日1回実行できます。


追記

どうやらAndroid N からはsetPeriodic(long intervalMillis, long flexMillis)を使う必要があるようです。flexMillisには遅れてもいい時間をミリ秒単位で渡すことになっています。

その際、flexMillsはJobInfo.getMinFlexMillis()で取得される値かintervalMillsの5%の時間のいずれか高い方よりも大きい値にする必要があります。https://developer.android.com/reference/android/app/job/JobInfo.Builder.html#setPeriodic(long,%20long)


追記

JobSchedulerやAlarmManagerを素で使おうとするとAPIレベルの差を吸収できるようにしなければならないので面倒くさいです。GoogleがWorkManagerというものを出してくれているので、こちらを使うようにしていくといいんじゃないかと思います。定期処理周りの互換性を確保してくれるライブラリとして、evernote/android-job などもありますが、「新しいプロジェクトはWorkManagerを使ってね。このライブラリは将来的に非推奨にするよ」と言っています。