JobDispatcherとは
firebase/firebase-jobdispatcher-android
APILevel 21(Lollipop)で追加されたJobScheduler
の代替ライブラリです。完全に忘却の彼方でしたが、Google IO 2016で紹介されていたみたいです。
JobScheduler
の代替であることからわかるようにタスクスケジューラとして利用するものです。
JobDispatcher
のバックエンドはGooglePlayServices
のGCMNetworkManager
に依存してます。一応実装が抽象化されていますが、現状はGCMNetworkManager
のラッパーと考えて良さそうです。
本稿ではJobScheduler
の話はしませんが、JobDispatcher
はJobScheduler
と同じインターフェースではないということに注意してください。
そもそも何故生まれたか
Android 7.0では、android.net.conn.CONNECTIVITY_CHANGE
を始めとするいくつかのブロードキャストについて、BroadcastReceiver.register()
以外の方法でブロードキャストを受信できなくなります。(Manifestに書いたやつはダメということ)
android.net.conn.CONNECTIVITY_CHANGE
はネットワーク状態の変更を通知してくれますが、この条件が雑すぎたことで送信頻度が高いため、システムがバックグラウンドで多くのアプリを起動する(BroadcastReceiverが起動される=アプリが起動する)ことに繋がっていました。
多くのアプリは、「通信できるようになれば、xxする」という目的に使われているはずですが、「通信可能|不可能状態への変化」「Wi-Fi|モバイル回線切り替え」など、様々な条件で飛ぶようになっているので、やべえよやべえよ…ってことのようです。
できること
現状、githubにあるドキュメントが雑なので、GCMNetworkManager
のドキュメントも合わせて参考にしたほうがいいかもしれないです。
ただ、これらの機能がすべてJobDispatcher
で提供されているのかは調べ切れてないです。
- 1回または繰り返しのタスクスケジュールを提供
- エラー時の再試行及びバックオフのサポート
- 端末状態の条件付与 (ネットワーク状況、Wi-Fi only、充電状態限定)
- ジョブの永続性 (端末再起動、アプリアップデートなどに対応)
特に1は、場合によってはAlarmManager
いらねえんじゃねえかなとか思ったりしました。
利用
dependencies
にcom.google.android.gms:play-services-gcm
を含んでいるか否かでわかれます。要するに、単品とplay-services-gcm
バンドル版があるということです。
単品
compile 'com.firebase:firebase-jobdispatcher:{latestVersion}'
バンドル版
compile 'com.firebase:firebase-jobdispatcher-with-gcm-dep:{latestVersion}'
実装
JobDispatcher
を利用するために必要な実装は、主に2つに分かれています。
- JobServiceの実装
- Jobの構築
JobServiceの実装
JobService
は名前の通り、AndroidのService
のことです。
JobDispatcher
で、JobService
とSimpleJobService
というクラスが提供されているので、どちらかを継承しましょう。
public class MyJobService extends SimpleJobService {
@Override
public int onRunJob(JobParameters job) {
// any
}
}
JobService
とSimpleJobService
の違いですが、コード見た感じだとService
とIntentService
のようなものです。SimpleJobService
は専用スレッドでonRunJob()
メソッドが呼び出されます。
JobService
を用意したら、忘れずにManifestに追加しましょう。1
<application>
…
<service
android:name=".MyJobService"
android:exported="false">
<intent-filter>
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
</intent-filter>
</service>
</application>
Jobの構築
ジョブの実行条件はJob
インスタンスを構築して行います。Job
の構築にはFirebaseJobDispatcher
を使います。
FirebaseJobDispatcher
の実装は抽象化されており、Driver
インターフェースでDelegateできます。ただ、プリセットとして実装されているのはGooglePlayDriver
しかないため、基本おまじないになります。
FirebaseJobDispatcher dispathcer = new FirebaseJobDispatcher(new GooglePlayDriver(context));
FirebaseJobDispatcher
から、Job
のBuilderを生成できます。
Job.Builder jobBuilder = dispatcher.newJobBuilder()
.setService(MyJobService.class)
.setTag("myJob")
.setTrigger(Trigger.NOW)
.setReplaceCurrent(true);
メソッド | 用途 | 備考 |
---|---|---|
setService(Class) |
起動するServiceの指定 | |
setTag(String) |
ジョブを識別するIDを設定する | 100文字以内 |
setTrigger(JobTrigger) |
着火猶予の指定 | 30秒未満は30秒になる(?) |
setLifetime(int) |
永続性の指定 | 端末を再起動しても持続するかどうか |
setRecurring(boolean) |
繰り返しの設定 | Trigger.NOWでは使えない |
setConstraints(int...) |
端末状態条件の指定 | |
setRetryStrategy(RetryStrategy) |
再試行間隔のバックオフアルゴリズムの指定 | |
setReplaceCurrent(boolean) |
既存の同tagのジョブに対して上書きを行うかどうか | |
setExtras(Bundle) |
タスクに渡す任意のBundle |
Jobを構築したら、あとは登録するだけです。
dispatcher.schedule(jobBuilder.build());
ちなみにJobの取り消しはtagで行います。
dispatcher.cancel(tag);
定期的に繰り返す
Jobを繰り返すのは、setRecurring(true)
とするだけですが、このときsetTrigger()
には、ExecutionWindowTrigger
を指定する必要があります。
FirebaseJobDispatcher dispathcer = new FirebaseJobDispatcher(new GooglePlayDriver(context));
Job.Builder jobBuilder = newJobDispatcher(this).newJobBuilder()
.setTag("myPeriodicJob")
.setService(MyJobService.class)
.setRecurring(true)
.setTrigger(Trigger.executionWindow(60, 120));
dispatcher.schedule(jobBuilder.build());
Trigger.executionWindow(60, 120)
この記述の場合、60~120秒程度のスパンで、ジョブを繰り返し実行するという指定になります。
即座に実行する
Trigger.NOW
という指定を行うと、直近で1回だけ行うタスクを実行するジョブを作れます。しかし、これにも30秒制限があるようで、実際には即座に実行されず30秒程あとに実行されます。
つまり、即時実行のジョブは作れないということになります。
Trigger.NOW
の遅延を1秒に変更したよ!っていうPRがマージされていたので、早くて30秒後ではなくなったかもしれないです
ジョブの保留
ネットワーク状態や端末状態を条件にした場合(setConstraints()
)、条件を満たしていないと、時間になってもジョブが実行されず保留されます。保留されたジョブは条件を満たしたときに実行されます。
参考
firebase/firebase-jobdispatcher-android
Implementing GCM Network Manager on Android | Google Developers
-
カスタム
IntentFilter
を書いているとデフォでexported=true
になってセキュリティ的にあうあうあーってなるので注意してください ↩