Tutorials

【解決済み】input dispatching timed outエラーの原因とAndroidアプリでの直し方

「input dispatching timed out」とはどんな症状か

Androidアプリを開発・運用していると、クラッシュレポートやlogcatに次のようなログが記録されることがあります。

ANR in com.example.myapp
Reason: Input dispatching timed out (xxx/xxx, Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500ms ago. Wait queue length: 2. Wait queue head age: 5467ms.)

この「input dispatching timed out」は、Androidシステムが「アプリが応答していない(ANR: Application Not Responding)」と判断したときに記録されるエラーです。ユーザーには画面上に「アプリが応答していません」というダイアログが表示され、「アプリを閉じる」か「待機」を選ばされます。

Google Playのバイタル指標においても、このANRレートはアプリの検索順位や掲載状況に影響する重要指標です。ユーザー体験だけでなく、ストアのビジビリティにも直結するため、早期に対処することが重要です。

なぜ「input dispatching timed out」が起きるのか

Androidは、タップやスワイプなどの入力イベントをメインスレッド(UIスレッド)が受け取り、処理する設計になっています。このメインスレッドが5秒以内に入力イベントへ応答できなかったとき、システムがANRを発生させます。

主な原因は次の3パターンです。

原因1: メインスレッドでの重い処理

ファイルI/O、データベースへの同期アクセス、ネットワーク通信など、時間のかかる処理をメインスレッドで直接実行しているケースです。これは最も頻繁に発生する原因です。

// 悪い例: メインスレッドでネットワーク通信を実行
fun fetchData() {
    val result = URL("https://api.example.com/data").readText() // ANRの原因
    textView.text = result
}

原因2: ロック(同期ブロック)による待機

別スレッドが長時間保持しているロックをメインスレッドが待ち続けている状態です。synchronizedブロックやReentrantLockを使っている箇所で発生しやすいデッドロック・ロック競合が典型例です。

原因3: 同期的なBinder呼び出し

ContentProviderやシステムサービスへの同期的なIPC(プロセス間通信)呼び出しが、相手プロセスの応答遅延により長時間ブロックされるケースです。サードパーティSDKの初期化処理に含まれていることもあります。

「input dispatching timed out」の解決手順

ステップ1: ANRトレースログを取得・解析する

まず原因を特定します。ANR発生時、Androidシステムはスレッドのスタックトレースを/data/anr/traces.txtに記録します。以下のコマンドで取得できます。

# デバイスからトレースファイルを取得
adb pull /data/anr/traces.txt

# または bugreport ごと取得
adb bugreport bugreport.zip

取得したファイルの中で「main」スレッドのスタックトレースを確認します。SUSPENDEDWAITING状態になっているメインスレッドと、その原因スタックフレームを特定してください。

"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x72e3a5a0 self=0xb40000744de40e30
  | sysTid=12345 nice=-10 cgrp=top-app sched=0/0 handle=0x7551ed54f8
  at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native method)
  at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(...)

上記の例ではメインスレッドでSQLiteへのアクセスが発生していることが分かります。

ステップ2: StrictModeで開発中に検出する

本番に出る前に問題を発見するため、StrictModeApplication.onCreate()またはActivity.onCreate()に設定します。

if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(
        StrictMode.ThreadPolicy.Builder()
            .detectAll()       // ディスクI/O、ネットワーク通信などを検出
            .penaltyLog()      // logcatに警告を出力
            .penaltyDeath()    // デバッグ中はクラッシュさせて確実に気付く
            .build()
    )
    StrictMode.setVmPolicy(
        StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .penaltyLog()
            .build()
    )
}

StrictModeはデバッグビルド限定で有効にし、リリースビルドでは除外することを徹底してください。

ステップ3: 重い処理をバックグラウンドスレッドに移す

Kotlin Coroutineを使うのが現代のAndroid開発における最もシンプルな解決策です。

// 良い例: Coroutineでバックグラウンド実行
class MyViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            val result = withContext(Dispatchers.IO) {
                // I/Oはバックグラウンドで実行
                URL("https://api.example.com/data").readText()
            }
            // UIの更新はメインスレッドで実行
            _uiState.value = result
        }
    }
}

処理の種類に応じてDispatcherを使い分けます。

  • Dispatchers.IO: ファイルI/O、ネットワーク通信、データベースアクセス
  • Dispatchers.Default: CPUを多く使う計算処理、リストのソートや変換
  • Dispatchers.Main: UIの更新(メインスレッドのみ)

ステップ4: Application.onCreate()の最適化

アプリ起動直後に複数のSDKを初期化している場合、Application.onCreate()の処理が重くなりANRを引き起こすことがあります。

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // 起動直後に必要なSDKのみ初期化
        analyticsSDK.init(this)

        // 遅延して問題ないSDKはバックグラウンドで初期化
        lifecycleScope.launch(Dispatchers.IO) {
            heavySDK.init(this@MyApplication)
        }
    }
}

ステップ5: Google Playコンソールで本番のANRを監視する

Google Playコンソールの「Android バイタル」では、実際のユーザー端末で発生したANRを確認できます。「ANRとクラッシュ」メニューから「input dispatching timed out」のスタックトレースを確認し、再現条件を特定してください。

それでも解決しない場合の対処法

サードパーティライブラリが原因の場合

React Native、Flutter、Defoldなどのクロスプラットフォームフレームワークや、広告・計測系SDKがANRを引き起こすことがあります。各ライブラリのGitHub Issuesで「input dispatching timed out」を検索し、既知の問題と修正バージョンを確認してください。可能であれば最新バージョンにアップデートします。

デッドロックが疑われる場合

ANRトレースにMONITOR状態のスレッドが複数見られる場合、デッドロックを疑います。synchronizedブロックの保持順序を統一するか、java.util.concurrentLock実装のタイムアウト付きロック取得(tryLock(timeout))を使って回避します。

ContentProviderへのアクセスが原因の場合

ContentProviderへのクエリをメインスレッドで呼んでいる場合、CursorLoaderRoomのSuspend関数を使ってバックグラウンド化します。Roomの場合はDAOのメソッドをsuspend funにするだけで自動的にI/Oスレッドで実行されます。

// Room DAOの例
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    suspend fun getAllUsers(): List<User>  // suspendにするだけでOK
}

まとめ: 再発防止のためのチェックリスト

「input dispatching timed out」の根本原因はほぼ常にメインスレッドのブロックです。以下のチェックリストを開発フローに組み込むことで再発を防げます。

  1. StrictModeをデバッグビルドに常時有効化する — メインスレッドでのI/Oを即座に検出
  2. ネットワーク・DB・ファイル操作はすべてCoroutine(Dispatchers.IO)で実行する — ルール化することで混入を防ぐ
  3. Application.onCreate()の処理時間を計測する — 重い初期化は遅延起動に切り替える
  4. Google Playバイタルを定期的に確認する — 本番ANRを早期に検知
  5. サードパーティSDKのバージョンを最新に保つ — ライブラリ起因のANRを防ぐ

「input dispatching timed out」は原因が特定できれば、ほぼ確実に修正できるエラーです。ANRトレースログを起点に、メインスレッドで何が起きているかを丁寧に追っていけば、必ず解決の糸口が見つかります。本記事の手順を参考に、ぜひ問題を解消してください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です