💮

Androidでのバックグランドタスクの扱い方

2022/11/20に公開

概要

Androidではバックグラウンドスレッドで実行するための仕組みが多数用意されています。仕組みの使い方を調べていると他の仕組みへの言及があり、結局のところ何を使えばよいのか分からなくなりました。そこで、それぞれの仕組みで行える事を整理し、どの様な場合に何を使うのかまとめます。

確認環境

  • サンプルプロジェクト
  • 実行環境
    • Android Emulator
      • API 23: Android 6.0 | x84_64
      • API 31: Android 12.0 Google Play | x84_64
      • API 33: Android Tiramisu Google Play | x84_64

参考情報

以下の文書の内容を主に参照しています。

前置き

関連する仕組み

まずは、参考情報を調べていると登場する仕組みの一覧です。Javaのみに関係する仕組みは除外しています。

  • Kotlin Coroutine
  • WorkManager
    • OneTime
      • Foreground
    • Periodic
      • Foreground
  • Service
    • Start Service
      • Foreground
    • Bound Service
  • AlermManager
  • DownloadManager
  • JobScheduler
  • FirebaseJobDispatcher
  • GcmNetworkManager

仕組みの中には機能面での分類が存在するものもあります。例えば、WorkManagerにはOneTimeとPeriodicの2種類があり、更にOneTimeとPeriodicはそれぞれForeground指定の有り/無しに分けられます。

この一覧に挙げた1つずつの仕組みについて、以降で確認していきます。

整理する際の観点

仕組みで行える事を整理するにあたって、確認しておきたい事柄をまとめておきます。

  • 開始タイミング
    • 即時
      • 実行可能になり次第、可能な限り早く開始する。
    • 遅延
      • 指定時間経過後に開始する。
      • 指定時間を短くすれば即時も可能である。
  • 永続性
    • アプリ終了(kill)後も継続されるか?
      • Overview画面でスワイプすることによりアプリを終了して確認します。
    • システム再起動後も継続されるか?
      • adb reboot によりシステムを再起動して確認します。
    • 待機中(遅延実行時)に終了しても継続されるか?
    • 実行中に終了しても継続されるか?
    • 遅延&長時間の実行では永続性を持つことを推奨する。
      (参照:Approaches to background work)
  • 実行時間
    • アプリ終了(kill)後の継続可能時間は?
      • Kotlin Coroutine で20分程の処理を実行して確認します。
  • キャンセル
    • 待機中(遅延実行時)
      • タスクの実行を待っている間はキャンセル可能か?
    • 実行中
      • タスクを実行している最中はキャンセル可能か?
  • キャンセル方法
    • キャンセル対象を指定するための方法には何があるか?
  • 入力
    • 実行開始時のパラメータ
      • 実行開始時にタスク外からデータを渡す方法はあるか?
    • 実行中の入力
      • 実行中にタスク外からデータを渡す方法はあるか?
  • 出力
    • 実行中の出力
      • 実行中にタスク外にデータを渡す方法はあるか?
    • 実行完了時の戻り値
      • 実行完了時に戻り値でタスク外にデータを渡せるか?
  • 入出力データの制限
    • 入出力で渡すデータの内容に制限はあるか?

ここに挙げた以外の部分でも特徴的な機能があれば記載しておきます。全ての機能を網羅するわけではありません。また、方法の有無については、仕組みとして方法を備えているかを意味しています。アプリ開発者が拡張を行えば可能になるものもあると思われます。

解説

Kotlin Coroutine

Kotlin Coroutine は他の仕組みと並べて比較すべきではないかもしれません。なぜなら、他の仕組みの実装で Kotlin Coroutine を使用していることがあるからです。Kotlin Coroutine は Activity, Service, Worker(WorkManager) の実装で使用します。ここでは、Activity で Kotlin Coroutie を使用する想定で話しを進めます。

開始タイミング

  • 即時

永続性

  • アプリ終了後も残るが、Activity 終了前にキャンセルした方が良い。
    • 諸々がリークする原因になるため。

実行時間

  • 特に制限はない。
  • Activity で使用するときは短時間での使用が主となる。

キャンセル

  • 可能

キャンセル方法

  • Job 指定
  • CoroutineScope 指定
    • ViewModelScope, LifecycleScope を使用すると便利。

入出力は様々な方法で柔軟に行えます。

WorkManager

Worker の実装方法にはいくつかありますが、CoroutineWorker を拡張した実装により確認を行っています。他の実装方法では異なる点があるかもしれません。

開始タイミング

  • 遅延
    • setInitialDelay で遅延時間を指定する。
    • デバイス状態による制約を満たさない時は遅延して実行する。

永続性

  • 待機中と実行中のいずれも
    • アプリ終了後も継続する。
    • システム再起動後も継続する。

実行時間

  • API 31/33
    • 完走(20分)
      • setForeground しなくても中断されない。
  • API 23
    • 10分まで継続

キャンセル

  • 待機中と実行中のいずれも可能

キャンセル方法

  • WorkRequest の ID 指定
  • 一意処理で付けた名前(uniqueWorkName)指定
  • タグ指定

入力

  • 実行開始時のパラメータ
    • setInputData で Data オブジェクトを渡す。
  • 実行中の入力
    • なし?

出力

  • 実行中の出力
    • setProgress で Data オブジェクトを渡す。
    • WorkInfo の getProgress で受け取る。
  • 実行完了時の戻り値
    • Result オブジェクト で Data オブジェクトを渡す。
    • WorkInfo の getOutputData で受け取る。

入出力データの制限

  • boolean, byte, int, long, float, double, String
  • 上記の配列
  • 直列化した際に MAX_DATA_BYTES(10240 = 10KiB) 以下である必要あり
    • 違反すると IllegalStateException
  • (参照:Dataクラスリファレンス)

デバイス状態による制約

  • 全ての制約が満たされた場合のみ処理を実行する機能を持つ
  • 制約が満たされなくなると処理を停止する
  • 再び制約が満たされると処理を再試行する
  • 指定可能な制約(Constraints)
    • NetworkType
      • CONNECTED: 有効なネットワーク接続が必要
      • METERED: 従量制ネットワーク接続が必要
      • NOT_REQUIRED: ネットワーク接続は不要
      • NOT_ROAMING: 非ローミングネットワーク接続が必要
      • TEMPORARILY_UNMETERED: 通常は従量制だが一時的に従量制ではないネットワーク接続が必要
      • UNMETERED: 定額制ネットワーク接続が必要
    • BatteryNotLow
      • true: 電池残量低下モードだと実行しない
    • RequiresCharging
      • true: 充電中のみ実行する
    • DeviceIdle
      • true: デバイスがアイドル状態でのみ実行する
    • StorageNotLow
      • true: 保存容量が少なすぎると実行しない
  • 制約(Constraints)は Builder で構築する。

再試行

  • 実行中から ENQUEUED 状態(待機中)に戻す機能を持つ。
    • doWork の戻り値 を Result.retry() にすることで行われる。
  • BackoffCriteria を指定できる。
    • バックオフポリシー(BackoffPolicy)
    • バックオフ遅延(delayTime + TimeUnit or Duration)
      • 初回実行後に再試行まで待機する最小時間(指定時間以上待機する)
        • デフォルト は WorkRequest.DEfAULT_BACKOFF_DELAY_MILLIS(30秒)
      • 指定可能な範囲は
        • WorkRequest.MIN_BACKOFF_MILLIS(10秒)以上
        • WorkRequest.MAX_BACKOFF_MILLIS(5時間)以下
      • 厳密な時間ではなく、数秒長く待つ場合がある
      • (参照:setBackoffCriteriaメソッドリファレンス)

一意処理

  • WorkRequest に名前(uniqueWorkName)を付けて重複した要求を排除する機能を持つ。
  • 競合解決ポリシー(ExistingWorkPolicy/ExistingPeriodicWorkPolicy)
    • REPLACE
      • 新しい処理で既存の処理を置き換える
      • 既存の処理はキャンセルする
    • KEEP
      • 既存の処理を保持する
      • 新しい処理は無視する
    • APPEND(Periodicでは不可)
      • 新しい処理を既存の処理の最後に追加(処理を連結する)
      • 既存の処理が CANCELLED, FAILED の場合、新しい処理も CANCELLED, FAILED になる
    • APPEND_OR_REPLACE(Periodicでは不可)
      • 基本は APPEND と同じ
      • 既存の処理が CANCELLED, FAILED の場合でも新しい処理を行う

作業の監視

  • WorkManager の getWorkInfo 系メソッドで WorkInfo を取得することができる。
  • 検索パラメータの種類
    • WorkRequest の ID
    • 一意処理で付けた名前(uniqueWorkName)
    • タグ
    • WorkQuery(Builderで構築)
      • (id1 OR ...) AND (uniqueWorkName1 OR ...) AND (tag1 OR ...) AND (state1 OR ...) 形式の検索ができる。
  • 戻り値は WorkInfo の変化を監視できるオブジェクト
    • ListenableFuture
    • LiveData

別プロセスでの実行

WorkManager は機能が豊富で、大体の場合に推奨されています。

文中、作業と処理は大体同じ意味と思っても大丈夫だと思われます。できるだけ公式文書の日本語訳をそのまま使っています。作業も、処理も Worker が実行する事柄を指している点は同じです。

OneTime

OneTimeWorkRequest を使った場合だけに使える機能です。基本的な部分は WorkManager で共通しています。

優先処理(expedited work)

  • ユーザーにとって重要な処理を直ちに開始する機能を持つ。
  • 即時実行しか行えない。
    • setInitialDelay は使用できない。
  • 優先処理としての実行時間は有限であり、優先処理の割り当てに制限がある。
  • 割り当て制限を受けた時(優先処理として実行できない時)の動作を選択する。
    • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
      • 通常の処理として実行される。
    • OutOfQuotaPolicy.DROP_WORK_REQUEST
      • 要求が取り消される。
  • Android 11 API 30以下のみ、Foreground Serivce を実行することがある。
    • Worker の getForegroundInfoを 実装する必要がある
      • 実装しないとクラッシュする可能性あり

    WorkManager は Android 12 より前のプラットフォーム バージョンでフォアグラウンド サービスを実行することがあります。
    下位互換性とフォアグラウンドサービスより

作業チェーン(chaining work, chained tasks)

  • WorkRequest を順次もしくは並行に実行する機能を持つ。
  • 並行実行
    • beginWith/beginUniqueWork/then の引数に WorkRequest のリストを渡すと並行実行になる。
    • combine の引数に WorkContinuation のリストを渡すと並行実行になる。
  • 順次実行
    • WorkContinuation が順次作業を表す。
      • beginWith/beginUniqueWork/then/combine の戻り値が WorkContinuation である。
    • then で繋げて順番を定める。
  • 前作業の戻り値 Data を次作業の実行開始時のパラメータにできる。
    • 並行作業後の場合は InputMerger で Data 併合する。
      • OverwritingInputMerger
        • 各キーに対して値は1つになる。
        • 重複する場合は最後の出力が残る(実行順不定)。
      • ArrayCreationgInputMerger
        • 各キーに対して値は配列になる。
      • InputMerger はカスタムで作成できる。

Periodic

PeriodicWorkRequest を使った場合だけに使える機能です。基本的な部分は WorkManager で共通しています。

定期実行

  • 指定した時間間隔で実行する機能を持つ。
    • 時間間隔(repeatInterval)は15分以上でなければならない。
  • 時間間隔内での実行タイミングを指定できる。
    • 実行タイミングを指定する時間間隔(flexInterval)は5分以上でなければならない。

Foreground

Worker の実装にて setForeground を呼び出した時に有効になる機能です。通知(Notification)を表示することで実行時間を延ばします。

実行時間

  • API 23
    • 完走(20分)
    • 10分以上でも継続する。

Android 12 API 31では、実装上の注意点があります。

注意: setForeground() は Android 12 でランタイム例外をスローする可能性があり、開始が制限されている場合に例外をスローすることがあります。
下位互換性とフォアグラウンドサービス

文書上に書かれている仕様では、10分以上の処理を行うときは Foreground にする必要がある様ですが、実際に動かしてみると Foreground にしなくても10分以上継続できる場合があります。しかし、10分以上の長時間にわたる処理では Foreground した方が確実でしょう。

Service

Service 自体はバックグラウンドスレッドではなくメインスレッドで実行されます。しかし、実装する際にはメインスレッドをブロックしない様にバックグランドスレッドで実行する必要があります。本記事では、バックグラウンドスレッドで実行する方法として Kotlin Coroutine を使用して確認しています。

Service を実行する際の制限を1つだけ記しておきます。Service を実行する場合には明示的インテントを使用する必要があります。

注意:アプリの安全性を保つため、Service を開始するときは常に明示的インテントを使用し、サービスでインテント フィルタを宣言しないようにしてください。
マニフェストでサービスを宣言するより

Start Service

startService もしくは startForegroundService で Service を起動した場合の動作です。起動後に Service の onCreate と onStartCommand が呼ばれます。起動済みだと onStartCommand のみが呼ばれます。Service を停止(キャンセル)すると onDestroy が呼ばれます。Kotlin Coroutine で実装するにあたっては、onDestroy で処理をキャンセルしています。

実行タイミング

  • 即時

永続性

  • アプリ終了後も継続する。
    • onDestroy は呼ばれない。
  • デバイス再起動後に継続しない。

実行時間

  • 完走(20分)
    • startForeground しなくても中断されない。

キャンセル

  • 可能

キャンセル方法

  • Service で stopSelf を呼び出す。
  • Service 外から Intent を指定して stopService を呼び出す。

入力

  • 実行開始時のパラメータ
    • Intent の Extra で渡す。
    • onStartCommand のパラメータ Intent の Extra で受け取る。
  • 実行中の入力
    • Intent の Extra で渡す。
    • onStartCommand のパラメータ Intent の Extra で受け取る。

出力

  • 実行中の出力
    • なし?
  • 実行完了時の戻り値
    • なし

入出力データの制限

  • boolean, byte, short, int, long, float, double, char, String, CharSequence, Parcelable
  • 上記の配列
  • Serializable
  • Bundle
  • (参照:Intentクラスリファレンス)

Foreground

Android 8.0 API 26以上では、アプリがフォアグラウンドで動作していない限り(バックグラウンドで動作しているとき?) Service の起動に制限があります。処理が5秒以上実行される可能性がある場合、startForegroundService で Service を起動し、5秒以内に startForeground を呼び出して Foreground Service として起動する必要があります。

注:アプリが API レベル 26 以上を対象としている場合、
...
サービスが作成されたら、サービスは 5 秒以内にその startForeground() メソッドを呼び出す必要があります。
サービスを開始するより

また、Android 12 API 31以上では、バックグラウンドで動作しているとき少数の特殊なケースを除いて Foreground Service を起動できなくなっています。

Android 12 をターゲットとするアプリは、バックグラウンドで動作しているとき、少数の特殊なケースを除いて、フォアグラウンド サービスを起動できなくなりました。
フォアグラウンド サービスの起動に関する制限

バックグラウンドから起動する際には制限が多く WorkManager の使用が推奨されています。フォアグラウンドから起動する際には制限はありません。

なお、Foreground Service は WorkManager の実装として使われています。

内部的には、WorkManager がフォアグラウンド サービスを管理、実行して WorkRequest を実行し、同時に設定可能な通知を表示します。
長時間実行ワーカーをサポートするより

Bound Service

bindService で Service を起動した場合の動作です。起動後に Service の onCreate と onBind が呼ばれます。起動済みだとこれらは呼ばれません。全てのバインドが解除(キャンセル)されると onDestroy が呼ばれます。Kotlin Coroutine で実装するにあたっては、onDestroy で処理をキャンセルしています。

実行タイミング

  • 即時

永続性

  • アプリ終了後は継続しない。
    • onDestroy が呼ばれる。
    • 他アプリからバインドされている場合は除く。
  • デバイス再起動後に継続しない。

実行時間

  • バインドされている限り継続する。

キャンセル

  • 可能

キャンセル方法

  • ServiceConnection を指定して unbindService を呼び出す。

入力

  • 実行開始時のパラメータ
    • Intent の Extra で渡す。
    • onBind のパラメータ Intent の Extra で受け取る。
  • 実行中の入力
    • Binder で定義したメソッドのパラメータで渡す。
    • Message オブジェクトで渡す。

出力

  • 実行中の出力
    • Binder で定義したメソッドの戻り値で渡す。
    • Message オブジェクトで渡す。
  • 実行完了時の戻り値
    • なし

入出力データの制限

  • Intent Extraの制限はStart Service参照
  • Binderクラスを拡張する場合(同一プロセス内)
    • 制限なし
  • Messangerクラスを使用する場合(プロセス間通信可)
    • Bundle

Bound Service はクライアント・サーバ型インターフェイスを提供するために使えます。プロセス間通信も可能です。

バインドされたサービスは、コンポーネントがサービスを操作したり、リクエストを送信したり、結果を受信したり、さらにはプロセス間通信(IPC)でもそれを行えるクライアントサーバー型インターフェースを提供したりします。
サービスの概要より

AlermManager

AlarmManager は指定した時間に Service や BroadcastReceiver を起動する機能を持ちます。実際に処理を行うのは Service や BroadcastReceiver です。Doze モードなどのバッテリー節約機能が有効になっている場合でも起動することができます。

実行タイミング

  • 遅延
  • 時間を指定する際に精度を決められる。
    • 指定時間に起動する。
      • setAlarmClock
      • setExactAndAllowWhileIdle
      • setExact
    • 指定時間の範囲内に起動する。
      • setWindow
    • 指定時間以降に起動する。
      • set
      • setAndAllowWhileIdle
      • setInexactRepeating

永続性

実行時間

  • 完走(20分)

キャンセル

  • 待機中は可能
  • 実行中は不可

キャンセル方法

  • PendingIntent で指定する。
    • PendingIntent の同一性は Intent#filterEquals で比較する。
    • PendingIntent.FLAG_ONE_SHOT を指定して作成された PendingIntent の場合はキャンセルできないと書かれているがキャンセルできた。

      注: PendingIntent が PendingIntent.FLAG_ONE_SHOT で作成された場合はキャンセルできません。
      反復アラームを設定するより

入力

  • 実行開始時のパラメータ
    • Intent の Extra で渡す。
    • onStartCommand のパラメータ Intent の Extra で受け取る。
  • 実行中の入力
    • なし

出力

  • 実行中の出力
    • なし?
  • 実行完了時の戻り値
    • なし

入出力データの制限

  • Start Service の入出力データの制限を参照

バッテリー節約制限無視

  • setAlarmClock
    • 低電力モードを終了して起動できる。

      The system identifies these alarms as the most critical ones and leaves low-power modes if necessary to deliver the alarms.
      Set an exact alarmより

  • setExactAndAllowWhileIdle
    • バッテリー節約が有効でも起動できる。

      Invoke an alarm at a nearly precise time in the future, even if battery-saving measures are in effect.
      Set an exact alarmより

スリープ解除

  • type 引数に *_WAKEUP を指定するとスリープを解除して起動できる。

    Both types have a "wakeup" version, which says to wake up the device's CPU if the screen is off.
    Choose an alarm typeより

繰り返し

  • setInexactRepeating を使用すると繰り返し起動できる。

一意処理

  • 同じ PendingIntent を指定すると前の要求が取り消される。

    同じペンディング インテントを使用する 2 つ目のアラームを設定すると、そのアラームで元のアラームが置き換えられます。
    反復アラームを設定するより

  • PendingIntent 作成時に指定する flag では動作は変わらない。
    • PendingIntent.FLAG_ONE_SHOT を指定しても動作が同じである。

なお、AlarmManager は WorkManager の実装として使われています。

WorkManager は、デバイス API レベルなどの要素に基づいて、処理を実行する適切な方法を選択します(アプリのプロセスのスレッドで直接実行するか、JobScheduler、FirebaseJobDispatcher、あるいは AlarmManager を使用します)。
定額制の接続で実行するようにネットワーク ジョブをスケジューリングするより

DownloadManager

HTTP ダウンロードを行う機能を提供します。

実行タイミング

  • 即時

永続性

  • 失敗後、ネットワーク接続の変更、システム再起動後に再試行する。

実行時間

  • 時間の制限はない?
    • デバイスによってはダウンロードサイズに制限がある場合はある。

キャンセル

  • 可能

キャンセル方法

  • ID で指定する。

JobScheduler

WorkManager での代替が推奨されています。

WorkManager API は、FirebaseJobDispatcher、GcmNetworkManager、Job Scheduler など、以前の Android バックグラウンド スケジューリング API の代替策として推奨されています。
非推奨 API の置き換え

なお、JobScheduler は WorkManager の実装として使われています。

WorkManager は、デバイス API レベルなどの要素に基づいて、処理を実行する適切な方法を選択します(アプリのプロセスのスレッドで直接実行するか、JobScheduler、FirebaseJobDispatcher、あるいは AlarmManager を使用します)。
定額制の接続で実行するようにネットワーク ジョブをスケジューリングするより

FirebaseJobDispatcher

WorkManager での代替が推奨されています。(参照:FirebaseJobDispatcher から WorkManager への移行)

WorkManager API は、FirebaseJobDispatcher、GcmNetworkManager、Job Scheduler など、以前の Android バックグラウンド スケジューリング API の代替策として推奨されています。
非推奨 API の置き換え

なお、FirebaseJobDispatcher は WorkManager の実装として使われています。

WorkManager は、デバイス API レベルなどの要素に基づいて、処理を実行する適切な方法を選択します(アプリのプロセスのスレッドで直接実行するか、JobScheduler、FirebaseJobDispatcher、あるいは AlarmManager を使用します)。
定額制の接続で実行するようにネットワーク ジョブをスケジューリングするより

GcmNetworkManager

WorkManager での代替が推奨されています。(参照:GcmNetworkManager から WorkManagerへの移行)

WorkManager API は、FirebaseJobDispatcher、GcmNetworkManager、Job Scheduler など、以前の Android バックグラウンド スケジューリング API の代替策として推奨されています。
非推奨 API の置き換え

まとめ

Android でバックグラウンドタスクを扱う場合には、まずは WorkManager を検討すれば良さそうです。WorkManager で対応できない場合にのみ他の選択肢を検討します。WorkManager は機能が豊富で柔軟性もあるため大体のことは対応できそうです。HTTP 通信でダウンロードを行うだけであれば DownloadManager を使うと簡単に実現できるかもしれません。

実行中のバックグラウンドタスクと双方向通信を行いたい場合には Bound Service が適切だと思われます。アプリ内だけではなくアプリ間での通信も可能です。Bound Service だけが実行中の入出力をサポートしています。

実行中のバックグラウンドタスクにコマンド送信を行いたい場合には Start Service が適切だと思われます。アプリ終了後も継続するので、アプリ終了後に通知バーから操作するタスクの実行ができます。

バッテリー節約機能(Dozeモード、スリープなど)が有効になっている場合でも決められた時間に実行したい場合には AlarmManager が適切だと思われます。但し、バッテリーに悪影響を与えるため、使用は必要最小限に抑える必要があります。

整理してみた結論としては、ひとまず WorkManager を使いましょう。

Discussion