現場アプリ向け Kotlin WorkManager のバックグラウンド同期パターン
現場アプリ向けの Kotlin WorkManager バックグラウンド同期パターン:正しい作業タイプを選び、制約を設定し、指数バックオフを使い、ユーザーに見える進捗を示す方法。

現場・運用アプリにおける「信頼できる」バックグラウンド同期とは
現場や運用向けアプリでは、同期は「あると便利」ではなく仕事がデバイスから離れてチームのものになるための仕組みです。同期が失敗するとユーザーはすぐ気づきます:完了した作業がまだ「保留」のままに見えたり、写真が消えたり、同じレポートが二重にアップロードされて重複が発生したりします。
これらのアプリは一般的な消費者向けアプリより難しい条件で動きます。ネットワークは LTE、弱い Wi‑Fi、圏外を往復します。省電力モードはバックグラウンド作業をブロックします。アプリが殺され、OS が更新され、ルート途中で端末が再起動することもあります。信頼できる WorkManager 設定は、こうした状況をドラマなく乗り切れることが必要です。
信頼性が意味することは大まかに四つです:
- 最終的整合性:データは遅れて届くことがあっても、手作業で介入しなくても届く。
- 回復可能性:アップロード中にアプリが死んでも次の実行で安全に続けられる。
- 可観測性:ユーザーやサポートが何が起っているか、何が詰まっているかを把握できる。
- 非破壊性:再試行が重複や状態破損を起こさない。
「今すぐ実行」はユーザーがトリガーする小さな操作で、すぐ終わるべきもの(例:ユーザーがジョブを閉じる前に単一のステータス更新を送る)に使います。「待機」は写真アップロードやバッチ更新など重い作業向けで、バッテリーを消費したり悪いネットワークで失敗しやすい作業に適します。
例:検査員が地下で 12 枚の写真付きフォームを送信した場合、信頼できる同期はまず全てをローカルに保存し、キューに入れ、リアルな接続が得られたときに再送するので検査員が作業をやり直す必要はありません。
適切な WorkManager の構成要素を選ぶ
小さくて明確な単位の作業を選ぶことから始めてください。その決定は後の巧妙な再試行ロジックよりも信頼性に大きく影響します。
One-time と periodic の使い分け
OneTimeWorkRequest は、フォームが保存された、新しい写真の圧縮が終わった、ユーザーが同期をタップした、といった「何かが変わった」ために起こる作業に使います。制約を付けてすぐにエンキューし、デバイスが準備できたときに WorkManager が実行します。
PeriodicWorkRequest は更新チェックや夜間クリーンアップのような定期メンテナンス向けです。Periodic work は正確ではなく最小間隔があり、バッテリーやシステムのルールでズレるため、重要なアップロードの唯一の経路にしてはいけません。
実用的なパターンは「すぐ同期すべきものは One-time、定期はセーフティネット」にすることです。
Worker、CoroutineWorker、RxWorker の選び方
Kotlin で suspend 関数を使っているなら CoroutineWorker を優先してください。コードが短くなり、キャンセルが期待通りに動きます。
Worker は短いブロッキングコードに向いていますが、長時間ブロックしないよう注意が必要です。
RxWorker はアプリがすでに RxJava を多用している場合にのみ意味があります。余分な複雑さを避けましょう。
ステップをチェーンするか、フェーズを持つ一つのワーカーにするか?
ステップが独立して成功/失敗し、別々に再試行やログを取りたいならチェーンが良いです。ステップがデータを共有し、「全部まとめて」扱いたいなら一つのワーカーでフェーズを分ける方が良いでしょう。
簡単なルール:
- 異なる制約がある場合はチェーンする(例:Wi‑Fi のみのアップロード → 軽い API 呼び出し)。
- すべてを一つのトランザクションのように扱うなら一つのワーカーにする。
WorkManager は作業が永続化されプロセス終了や再起動を越えて生き残り、制約を尊重することを保証しますが、正確なタイミングや即時実行、ユーザーが強制停止した後の実行は保証しません。Android の現場アプリを作る際は(AppMaster から生成された Kotlin アプリも含め)遅延があっても安全な同期設計にしてください。
同期を安全にする:冪等、増分、再開可能
現場アプリでは作業が繰り返し実行されます。端末は回線を失い、OS はプロセスを殺し、ユーザーは何も起きないように見えると同期を二度押しします。背景同期が繰り返しに耐えられないと、重複や欠落、終わらない再試行が起きます。
まずはすべてのサーバー呼び出しが二度実行されても安全になるように設計しましょう。最も簡単なのはアイテムごとの冪等キー(例:ローカルレコードに保存する UUID)を付け、サーバー側で「同じリクエストは同じ結果」と扱う方法です。サーバーを変えられない場合は安定した自然キーと upsert エンドポイント、あるいはバージョン番号で古い更新を拒否するようにします。
ローカル状態は明示的に追跡し、ワーカーがクラッシュ後に推測しないようにします。単純な状態機械で十分なことが多いです:
- queued
- uploading
- uploaded
- needs-review
- failed-temporary
同期は増分にしてください。「すべて同期する」ではなく lastSuccessfulTimestamp やサーバー発行トークンのようなカーソルを保存し、小さなページ(例えば 20–100 件)ずつ読み取り、ローカルに完全にコミットできてからカーソルを進めます。小さいバッチはタイムアウトを減らし、進捗を見せやすくし、中断後に繰り返す作業量を制限します。
写真など大きなもののアップロードも再開可能にします。ファイル URI やアップロードメタデータを永続化し、サーバー確認を得てからのみアップロード済みとマークします。ワーカーが再起動したら最後の既知状態から続行します。
例:技術者が地下で 12 件のフォームを記入し 8 枚の写真を添付した場合、接続が戻ったらワーカーがバッチでアップロードし、各フォームに冪等キーを付け、各バッチが完全に成功した後で同期カーソルを進めます。途中でアプリが殺されても、再度ワーカーを実行すれば残りのキューを重複なく完了できます。
実際のデバイス条件に合った制約
制約はバックグラウンド同期がバッテリーを浪費したりデータ量を消費したり、最悪のタイミングで失敗したりするのを防ぐためのガードレールです。デスクの上での挙動ではなく、現場での端末の挙動を反映する制約が必要です。
まずはユーザーを守りつつも大多数の日に仕事が走るような最小セットから始めます。実用的なベースラインは:ネットワーク接続を要求、バッテリーが少ないときは避ける、ストレージが極端に少ないときは避ける。重い作業でなければ「充電中」を要求しない方が良い場合が多いです。多くの現場端末はシフト中に常時プラグインされていません。
制約を厳しくしすぎると「同期が一切走らない」というレポートの原因になります。unmetered、charging、battery not low をすべて要求すると理想的な瞬間を待つことになりほとんど実行されません。業務上データが今日必要な場合は、理想条件を待つより小さな作業を頻繁に走らせる方が良いことがあります。
キャプティブポータルは現実的な問題です:端末は接続していると報告するが、ユーザーがホテルや公共 Wi‑Fi のページで「Accept」を押さないとインターネットに出られないことがあります。WorkManager はその状態を確実には検出できないため、通常の失敗と扱い、素早くタイムアウトして再試行するようにします。可能であればリクエスト中に検出して簡単なアプリ内メッセージ(「Wi‑Fi に接続されていますがインターネットに接続できません」)を表示してください。
小さなペイロードと大きなペイロードで別々の制約を使うとアプリの応答性が保てます:
- 小さなペイロード(ステータス送信、フォームのメタデータ):任意のネットワーク、バッテリーが少ないときは避ける。
- 大きなペイロード(写真、ビデオ、地図パック):可能なら非課金ネットワーク(unmetered)を、時間に余裕があれば充電中を検討。
例:技術者が写真 2 枚付きのフォームを保存したら、フォーム本体はどの接続でも送信し、写真は Wi‑Fi かより良いタイミングまでキューに入れておきます。事務所側は仕事をすぐに見られ、端末はモバイルデータを背景で浪費しません。
ユーザーに嫌がられない指数バックオフの再試行
再試行はフィールドアプリを落ち着かせるか壊すかの分かれ目です。期待する失敗の種類に合ったバックオフ方針を選んでください。
ネットワークに対しては指数バックオフが安全なデフォルトです。接続が悪いときに短時間で何度も起こるのを防ぎ、サーバーやバッテリーへの負荷を減らします。線形バックオフは短時間の一時的な問題(不安定な VPN など)には合うことがありますが、弱い電波領域では過度にリトライしがちです。
失敗の種類に応じて再試行の判断を変えます。簡単なルールセット:
- ネットワークタイムアウト、5xx、DNS、接続なし:
Result.retry() - 認証切れ(401):一度トークン更新を試み、失敗したら停止してユーザーに再ログインを促す
- バリデーションエラーや 4xx(Bad Request):
Result.failure()を返し、サポート向けの明確なエラーを表示 - 既に送信済みで衝突(409):同期が冪等なら成功扱いにする
永久的なエラーが永遠にループしないよう上限を付けます。最大試行回数を設定し、それを超えたら静かに停止してユーザーに一つのアクション可能なメッセージを出します(繰り返し通知しない)。
試行回数が増えるに従って動作を変えることもできます。例えば 2 回失敗したら小さいバッチに切り替える、または大きなアップロードを次の成功までスキップする、などです。
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
30, TimeUnit.SECONDS
)
.build()
// in doWork()
if (runAttemptCount >= 5) return Result.failure()
return Result.retry()
これにより再試行は節度を保ちます:起床回数が少なく、ユーザーへの中断が減り、接続が回復したときの復旧が速くなります。
ユーザーに見える進捗:通知、フォアグラウンド作業、ステータス表示
フィールドアプリではユーザーが予期しないときに同期が走ることがよくあります:地下で、遅いネットワークで、ほとんどバッテリーがない状態など。同期がユーザーの待ち時間に影響する(アップロード、レポート送信、写真バッチ)場合は、見える化して分かりやすくしてください。短くて小さい更新はサイレントで良いですが、長い処理は正直に見せるべきです。
フォアグラウンド実行が必要なとき
作業が長時間かかる、時間的に重要、または明確にユーザーの操作に結びつく場合はフォアグラウンド実行を使います。現代の Android では大きなアップロードはフォアグラウンドでないと停止や遅延されることがあります。WorkManager では ForegroundInfo を返してシステムに進行中の通知を表示させます。
良い通知は三つの質問に答えるべきです:何が同期されているか、どの程度進んでいるか、どう中止するか。キャンセルアクションを明確にして、ユーザーが課金データや今すぐ端末を使いたい場合に中止できるようにします。
信頼できる進捗表示
進捗は曖昧なパーセンテージでなく、実際の単位に基づくべきです。setProgress で進捗を更新し、UI(またはステータス画面)で WorkInfo から読むようにします。
写真 12 枚とフォーム 3 件をアップロードしているなら「15 件中 5 件アップロード済み」のように表示し、残りと最後のエラーをサポート用に保持します。
進捗に含めるべき項目:
- 完了したアイテム数と残りのアイテム数
- 現在のステップ("写真をアップロード中"、"フォームを送信中"、"最終処理中")
- 最終成功同期時刻
- 最後のエラー(短くユーザー向けに)
- 目に見えるキャンセル/停止オプション
AppMaster で内部ツールを素早く作る場合でも同じルールを守ってください:ユーザーは同期を見て信頼するようになり、実際の作業と一致する表示であることが重要です。
ユニークワーク、タグ、重複同期の回避
重複する同期ジョブはバッテリーとモバイルデータを浪費し、サーバー側で競合を生みます。WorkManager はユニークワーク名とタグという二つの簡単なツールを提供してそれを防げます。
良いデフォルトは「同期」を一つのレーンとして扱うことです。アプリが起動するたび、ネットワーク変化が起きるたび、定期ジョブが走るたびに新しいジョブを積み上げるのではなく、同じユニークワーク名をエンキューします。これにより同期ストームを防げます。
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.addTag("sync")
.build()
WorkManager.getInstance(context)
.enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, request)
ポリシーの選び方が主な挙動の差です:
KEEP: 既に同期が走っているかキューにあれば新しい要求は無視します。多くの「今すぐ同期」ボタンや自動同期トリガーに向きます。REPLACE: 現在のものをキャンセルして新しく開始します。アカウントを切り替えた、プロジェクトを切り替えたなど入力が本当に変わった場合に使います。
タグは制御と可視化に便利です。sync のような安定したタグがあればキャンセル、ステータス照会、ログのフィルタが簡単になります。手動の「今すぐ同期」アクションでは既に仕事が走っているかをチェックして、別のワーカーを起動する代わりにユーザーへ明確なメッセージを出せます。
定期同期とオンデマンド同期は争わせないこと:
- 定期ジョブには
enqueueUniquePeriodicWork("sync_periodic", KEEP, ...)を使う - オンデマンドには
enqueueUniqueWork("sync", KEEP, ...)を使う - ワーカー内ではアップロードやダウンロードがなければすぐ抜けるようにし、定期実行を安価に保つ
- 必要なら定期ワーカーが同じ One-time のユニーク同期をエンキューして、実際の仕事は一箇所で行う
これらのパターンにより背景同期は予測可能になります:同時に一つの同期、キャンセルが簡単、観察も簡単。
ステップごとの実用的な同期パイプライン
信頼できる同期パイプラインは小さな状態機械として扱うと作りやすくなります:作業アイテムはまずローカルに置かれ、条件が揃ったときに WorkManager がそれらを前へ進めます。
出荷できるシンプルなパイプライン
-
ローカルの「キュー」テーブルから始める。再開に必要な最小限のメタデータを保存します:アイテム ID、タイプ(form, photo, note)、ステータス(pending, uploading, done)、試行回数、最後のエラー、ダウンロード用のカーソルやサーバー改訂情報。
-
ユーザーが「今すぐ同期」をタップしたら、現実に合った制約で
OneTimeWorkRequestをエンキューします。一般的な選択はネットワーク接続とバッテリーが少ないときは回避です。アップロードが重いなら充電中を要求します。 -
CoroutineWorkerを一つ実装し、アップロード、ダウンロード、調停(reconcile)といった明確なフェーズに分けます。各フェーズは増分で動かし、アップロードは pending のアイテムだけを処理、ダウンロードは最後のカーソル以降の変更だけを取り、競合は単純なルールで解決します(例:割り当てフィールドはサーバー優先、ローカル下書きはクライアント優先など)。 -
バックオフ付きの再試行を追加しますが、何を再試行するかは選別します。タイムアウトや 500 系は再試行、401 は速やかに失敗として UI に知らせます。
-
WorkInfoを監視して UI と通知を駆動します。"3/10 アップロード中" のように進捗を更新し、短い失敗メッセージで次の行動(再試行、サインイン、Wi‑Fi 接続)を示します。
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
キューをローカルに保ち、ワーカーフェーズを明確にすると、作業は中断、再開、そしてユーザーへ説明できるようになります。
よくあるミスと落とし穴(回避方法)
信頼できる同期が失敗する原因は開発時には無害に見える小さな選択ミスであることが多く、実機で簡単に破綻します。目標は可能な限り頻繁に実行することではなく、適切なタイミングで適切な作業を行い、実行できないときはきれいに止まることです。
気をつけるべき落とし穴
- 制約なしで大きなアップロードを行う。写真や大容量ペイロードを任意のネットワークと任意のバッテリーで送るとユーザーの不満を招きます。ネットワークやバッテリーの制約を付け、作業を小さな塊に分けてください。
- すべての失敗を永遠に再試行する。401、期限切れトークン、権限不足は一時的問題ではありません。恒久的失敗として扱い、再ログインなど明確なアクションを表示し、本当に一時的な問題(タイムアウトなど)のみ再試行してください。
- 意図せず重複を作る。ワーカーが二度走るとサーバーに二重作成が起きます。アイテムごとにクライアント生成の安定した ID を使い、サーバーが繰り返しを更新として扱うようにします。
- 近リアルタイムの必要があるのに periodic work に頼る。Periodic work はメンテナンス向けであり、ユーザー主導の即時同期には向きません。オンデマンド同期は One-time のユニークワークでエンキューしてください。
- 「100%」を早まって報告する。アップロード完了はデータが受理・整合されたこととは異なります。ステージ(queued, uploading, server confirmed)ごとに進捗を追い、サーバー確認後にのみ完了と示してください。
具体例:技術者がエレベーター内で写真 3 枚付きフォームを送信した場合、制約なしで即時にアップロードを始めるとアップロードが停滞し再試行が増え、再起動時にフォームが二重作成される恐れがあります。使えるネットワークで段階的にアップロードし、各フォームに安定 ID を付ければ一つの正しいサーバーレコードに落ち着きます。
出荷前のクイックチェックリスト
リリース前に、実際のフィールドユーザーが壊すであろう方法で同期をテストしてください:断続的な電波、バッテリー切れ、多数のタップ。開発機で問題ないものも、スケジューリングや再試行、ステータス報告がズレていると実機で失敗します。
少なくとも古いデバイスと新しいデバイスでこれらのチェックを行い、ログだけでなくユーザーが見る UI も確認してください。
- ネットワークなしから回復:通信オフで同期を開始し、後でオンにする。作業がキューに入って失敗状態で終わらず、重複せずに再開することを確認。
- デバイス再起動:同期途中で再起動し、アプリ再開後に作業が継続または再スケジュールされ、UI が「同期中」で固まらないことを確認。
- 低バッテリー・低ストレージ:省電力を有効にし、可能なら低バッテリー閾値以下にし、ストレージをほぼ満杯にしてジョブが適切に待機して再開することを確認。
- 繰り返しトリガー:同期ボタンを何度もタップ、あるいは複数画面からトリガーしても一つの論理的な同期だけが走ることを確認。
- 説明できるサーバー障害:500 やタイムアウト、認証エラーをシミュレートし、再試行がバックオフし上限で止まり、ユーザーに「再試行します」や「サインインしてください」といった明確なメッセージが出ることを確認。
どれかがアプリを不明瞭な状態にするならバグです。ユーザーは遅い同期は許しますが、データを失うことや何が起きているか分からないことは許しません。
例シナリオ:オフラインフォームと写真アップロード
技術者が現地で接続が弱い状態でサービスフォームを記入し 12 枚の写真を撮って Submit を押したとします。アプリはまず全てをローカルに保存します(例:ローカル DB にフォームのレコード 1 件と各写真ごとのレコード)。各レコードは PENDING、UPLOADING、DONE、FAILED のような明確な状態を持ちます。
Submit 時にアプリはユニークな同期ジョブをエンキューして、二度押しで重複が発生しないようにします。一般的には写真を先にアップロードして(重く遅い)、添付が確認されたらフォーム本体を送るような WorkManager のチェーンを使います。
同期は現実に即した条件が揃ったときだけ動きます。例えば接続あり、バッテリーが十分、ストレージが確保できる、といった条件です。地下でまだ電波がないならバックグラウンドで無駄にバッテリーを消費しません。
進捗は明確でユーザーフレンドリーです。アップロードはフォアグラウンドワークとして通知に「12 枚中 3 枚アップロード中」のように表示し、キャンセルができるようにします。キャンセルしたら作業を止め、残りは PENDING のまま次回再試行できます。
フラッピーなホットスポットに対しては再試行は丁寧に振る舞います:最初の失敗はすぐ再試行するかもしれませんが、失敗を重ねるごとに待ち時間を長くして(指数バックオフ)バッテリーやネットワークを浪費しないようにします。
運用側の利点は実用的です:アイテムが冪等でユニークにキューされていれば重複送信が減り、どの写真が失敗したか、なぜか、いつ再試行されるかが分かるため「送信済み」が実際に安全に同期された状態を意味します。
次のステップ:まず信頼性を出荷し、その後で同期範囲を拡張する
新しい同期機能を追加する前に「完了」の定義を明確にしてください。多くの現場アプリでの「完了」は「リクエストを送った」ことではなく「サーバーが受理し確認した」こと、かつ UI の状態が現実に一致することです。"Synced" と表示されたフォームはアプリ再起動後もそのままであるべきで、失敗したフォームは次に取るべき行動を示すべきです。
ユーザーやサポートが参照できる小さな信号セットを付けてアプリを信頼しやすくします。画面間で一貫して簡潔に保ってください:
- 最後の成功同期時刻
- 最後の同期エラー(短くユーザー向け)
- 保留中のアイテム数(例:3 件のフォーム、12 枚の写真)
- 現在の同期状態(Idle、Syncing、Needs attention)
可観測性を機能の一部として扱ってください。弱い接続下で何が起きているか分からない状況は現場での時間と労力を大きく浪費します。
バックエンドや管理ツールも一緒に作るなら同期契約を安定させやすくなります。AppMaster (appmaster.io) は本番対応のバックエンド、Web 管理パネル、ネイティブモバイルアプリを生成でき、モデルと認証を揃えたまま同期の難しい部分に集中できます。
最後に、小さなパイロットを実施してください。エンドツーエンドの同期スライス(例:「写真 1–2 枚付きの検査フォーム送信」)を一つ選び、制約、再試行、ユーザーに見える進捗が完全に動く状態で出荷します。そのスライスが退屈で予測可能になったら機能を一つずつ拡張してください。
よくある質問
デバイス上で作成された作業がまずローカルに保存され、ユーザーが手順を繰り返すことなく後で確実にアップロードされることを指します。アプリが強制終了されても再起動しても、弱いネットワーク下でも、データを失ったり重複を生じさせたりせずに同期が完了するべきです。
イベント発生(フォーム保存、写真追加、ユーザーの同期タップなど)に応じた単発の作業には OneTimeWorkRequest を使います。定期的なメンテナンス(更新チェックや夜間クリーンアップ)には PeriodicWorkRequest を使いますが、重要なアップロードだけをこれに頼るべきではありません。タイミングがずれ得るためです。
Kotlin を使い、suspend 関数で書いているなら CoroutineWorker を推奨します。コードが短くなり、キャンセルが期待通りに動きます。短いブロッキングコードなら Worker、アプリが RxJava 中心なら RxWorker を検討してください。
ステップごとに別々に成功/失敗させて個別に再試行やログを取りたい場合はチェーンが有効です。データを共有し一つのトランザクションのように扱いたい場合はフェーズを持つ単一ワーカーが良いでしょう。たとえば、Wi‑Fi 必須のアップロードと軽い API 呼び出しが別条件ならチェーン、すべてを一括で処理したいなら単一ワーカーにします。
各作成/更新リクエストは同じ操作が二度実行されても問題ないように設計します。簡単な方法は各アイテムに idempotency key(例:ローカル記録に保存した UUID)を付け、サーバー側で同一リクエストとして扱うことです。サーバーを変更できない場合は安定した自然キーや upsert エンドポイント、バージョン番号を使って古い更新を拒否する手もあります。
ローカルの状態を明示的に追跡してワーカーがクラッシュ後にも見当をつけられるようにします。たとえば queued、uploading、uploaded、needs-review、failed-temporary のような状態を持たせると良いです。写真など大きなアップロードはファイル URI やメタデータを保持し、サーバー確認後にのみ完了とマークします。これで再起動後も中断したところから再開できます。
現実的な保護としては、ネットワーク接続を要求し、バッテリーが少ないときは回避し、ストレージが極端に少ないときは待つ、といった最小限の制約から始めます。重い作業なら充電中を要求するのも選択肢ですが、多くの現場端末は勤務中に充電されない点に注意してください。unmetered や charging を要求し過ぎると同期がほとんど走らなくなります。
キャプティブポータル(接続はしているがインターネットに出られない状態)は現実問題としてよく起きます。WorkManager はそれを確実には検出できないので、通常の失敗として扱い、素早くタイムアウトさせて Result.retry() にして後で再試行します。できればリクエスト中に検出して「Wi‑Fi に接続されているがインターネットに接続できません」といった簡単なメッセージを出してください。
不安定なネットワークには指数バックオフが無難なデフォルトです。タイムアウトや 5xx、DNS エラーなどは Result.retry()、認証切れ(401)はトークンを一度更新してから失敗させユーザーに再ログインを促すなど、失敗のタイプに応じて処理を分けます。無限ループを避けるために最大試行回数を設定し、上限に達したら静かに停止してユーザーが取るべき次のアクションを示します。
トリガーが重なると同期の嵐になりがちなので、ユニークワーク名とタグで一つのレーンにまとめます。enqueueUniqueWork を KEEP にしておけば既に走っている同期があれば新しい要求を無視できます。長い処理やユーザーが関与する処理はフォアグラウンド実行にして通知で進捗とキャンセルを明示してください。


