0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kotlin Coroutineのdelay()後にToneGenerator.release()を書いてはいけない理由

0
Posted at

環境

  • OS: Android 12+
  • 言語: Kotlin 1.9
  • ライブラリ: kotlinx.coroutines 1.7

問題

アプリが約40分使うと音が出なくなる。再起動で直るが、また40分後に再発する。
WebSocket・STT・TTSのロジックは生きているのに、オーディオだけが無音になる。

原因

ToneGenerator.release()delay() の後に書いていた。

// NG: CancellationException が投げられると release() に到達しない
private suspend fun playBeep() {
    try {
        val toneGen = ToneGenerator(AudioManager.STREAM_MUSIC, 80)
        toneGen.startTone(ToneGenerator.TONE_PROP_BEEP2, 150)
        delay(200) // ← Coroutineキャンセル時に CancellationException を投げる
        toneGen.release() // ← ここに到達しない → リーク
    } catch (e: Exception) {
        // CancellationException も Exception のサブクラスなのでここで捕捉される
        // release() が呼ばれないまま関数が終了する
    }
}

delay() はただのスリープではなく suspend 関数。Coroutine がキャンセルされると CancellationException を投げる。catch (e: Exception) で捕捉されるため、delay() の後に書いたコードは実行されない。

10分間隔で動く機能が毎回 2〜4個の ToneGenerator をリークさせ、40分後(4サイクル)に Android のオーディオリソース上限に到達して全音声が無音になっていた。

解決策

try-finally に移す。finallyCancellationException が投げられても必ず実行される。

// OK: finally で確実に release する
private suspend fun playBeep() {
    var toneGen: ToneGenerator? = null // try の外で宣言する(finally からも参照できるように)
    try {
        toneGen = ToneGenerator(AudioManager.STREAM_MUSIC, 80)
        toneGen.startTone(ToneGenerator.TONE_PROP_BEEP2, 150)
        delay(200) // CancellationException が来ても…
    } catch (e: Exception) {
        // エラーハンドリング
    } finally {
        toneGen?.release() // …finally は必ず実行される
    }
}

変更は各箇所4行、計8行。40分で止まるバグが完全に解消した。

注意点

  • ToneGeneratorCloseable を実装していないuse {} は使えない。try-finally で手動解放するしかない
  • 上限超過時に例外が出ないToneGenerator のコンストラクタは失敗しても例外をスローせず、startTone() が単に無音になるだけ。logcat にも何も出ない
  • AudioTrack 同時生成上限はデバイス依存(目安: 32〜64本)。TTSなどが常時数本使っているため、実効的な空きはさらに少ない

ルール化

delay() の後にリソース解放を書いてはいけない。必ず try-finally で囲む

Thread.sleep() であれば InterruptedException でコンパイルエラーになるが、Kotlin の delay() は見た目がシンプルすぎて例外を投げる事実を忘れやすい。長時間稼働するアプリでは特に注意が必要。

参考

全体像はnoteに → https://note.com/yamashita_aidev/n/na527d8e4ba8b

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?