はじめに
Android 1.5の頃からアプリを作っていると、非同期APIは何度も主役交代してきました。AsyncTask、Loader、Rx、Coroutines。道具は進化しましたが、障害対応の現場で見る事故の根本原因は驚くほど似ています。
本記事では、各時代で実際に踏みがちな地雷を振り返りながら、2026年時点でも有効だった実務ルールに絞って整理します。ポイントは「どのAPIを使うか」より「キャンセル・責務・エラーハンドリングをどう設計するか」です。
AsyncTask時代の地雷
AsyncTaskの時代は、とにかく画面回転とライフサイクルの相性で苦しみました。最も多かったのは、非同期処理中に画面が破棄され、結果をUIへ反映しようとしてクラッシュする問題です。
典型的な事故
- Activity参照を内部クラスで握り続ける
- 画面回転後に古いインスタンスへコールバックしてアプリがクラッシュ
- キャンセルを呼んでも中断されず、二重更新が起きる
学んだこと
「処理がバックグラウンドで動ける」ことと「UIを安全に変更できる」ことは別問題です。UIのライフサイクルとの接続点を設計しない限り、どんな非同期APIでも事故は起きます。
Loader時代の地雷
Loaderはライフサイクル連携を前進させましたが、今度は責務の分散が問題になりました。データ取得ロジックがFragmentやActivityに寄りすぎると、見通しが急激に悪化します。
典型的な事故
- LoaderCallbacksに業務ロジックが混ざり、テストが難しい
- 画面ごとに似たLoader実装が増殖し、修正漏れが発生
- キャッシュ戦略が画面単位でバラバラになる
学んだこと
ライフサイクルに強い仕組みだけでは足りません。責務をUI層から分離し、ユースケース単位で再利用できる構造が必要です。
ReactiveX時代の地雷
ReactiveX(Rx)は表現力が高く、複雑な非同期フローを綺麗に書けました。ただし、チームで運用するとDispose漏れとScheduler設計のばらつきが頻発しました。
典型的な事故
subscribe()が散在して購読ライフサイクルを追えないCompositeDisposableの解放漏れで画面離脱後も処理継続observeOn/subscribeOnの設計が統一されずデバッグ困難
学んだこと
Rxの難しさは演算子ではなく運用ルールです。購読開始・終了の責務を定型化しないと、実装者依存の品質になります。
Coroutines時代の地雷
Coroutinesは読みやすくなりましたが、地雷が消えたわけではありません。特に初期導入時は「軽く書ける」ことが裏目に出ます。
典型的な事故
GlobalScopeの使用でライフサイクル外に処理が残るlaunchとasyncを混在させ、例外伝播が意図通りにならないwithContextの乱用で責務境界が曖昧になる
学んだこと
Coroutinesは「正しくキャンセルが伝播する設計」を作って初めて安全になります。書きやすさは設計品質の代替にはなりません。
非同期処理の難しさ
どのような形で非同期処理をするにせよ、以下のことが地雷の要因になります。
- 非同期処理のキャンセルタイミングについてきちんとした考慮がされていない
- UI層とデータ層の非同期責務が混ざっている
- エラー分類とリトライ方針がユースケースごとに不統一
この3つを放置すると、どのAPIでも「たまに落ちる」「再現しづらい」不具合の原因になります。
まとめ
非同期APIは進化し続けていますが、地雷の本体は道具ではなく設計です。私が長年の移行で得た結論は次の3点です。
- 非同期処理のキャンセル戦略を決める
- UI層から非同期責務を剥がし、境界を固定する
- スレッド境界とオブジェクトの参照関係を意識する
「新しいAPIに移行したのに不具合が減らない」と感じるチームでは、非同期処理の設計を見直すことが大切です。