【ITニュース解説】Fix Android WebView onShowFileChooser Overlapping Callback
2026年10月21日に「Dev.to」が公開したITニュース「Fix Android WebView onShowFileChooser Overlapping Callback」について初心者にもわかりやすく解説しています。
ITニュース概要
Android WebViewのファイル選択で、ユーザーが連続して操作するとアプリがクラッシュする問題の解決策。新しいファイル選択の前に必ず古い処理をキャンセルし、新しいコールバックを割り当てる。キャンセルやエラー時も同様に処理することで、アプリのフリーズやメモリリークを防ぎ、安定動作を保つことが重要だ。
ITニュース解説
Androidアプリケーション開発において、WebViewはウェブコンテンツをアプリ内に表示するための非常に便利なコンポーネントだ。多くのアプリで、ウェブページからファイルをアップロードする機能が求められることがある。例えば、プロフィール写真の変更やドキュメントのアップロードなどだ。このファイル選択の処理は、AndroidのWebViewが提供するonShowFileChooserというコールバックを通じて実現される。しかし、この機能の実装を誤ると、アプリが突然クラッシュしたり、ユーザーインターフェースがフリーズしたりといった深刻な問題が発生する可能性がある。特に、ユーザーがファイルを素早く選択しようとしたり、複数のファイル選択操作を続けて行ったりする「ラピッドユーザーアクション」の状況で、そのリスクが高まる。
具体的に何が問題を引き起こすのかを考えてみよう。onShowFileChooserが呼び出されるたびに、WebViewはファイル選択の結果を受け取るための新しいValueCallbackという参照をアプリに渡してくる。これは、システムが提供するファイルピッカー(ファイル選択画面)を表示するための準備のようなものだ。ユーザーがファイルを選択し終わると、このValueCallbackを通じて、選択されたファイルのURI(場所を示す情報)がWebViewエンジンに返される。しかし、もしユーザーが最初のファイル選択を完了させないまま、別のアップロードボタンをタップして二度目のファイル選択を開始した場合、最初のValueCallbackはまだ「応答待ち」の状態のままで残ってしまう。
WebViewの内部では、最初のファイル選択要求に対応するために、一つのスレッドが結果を待機している。アプリがこの待機中のコールバックに対して何も通知しないまま、新しいコールバックを単に上書きしてしまうと、WebViewのスレッドは永遠に結果が来ない応答を待ち続けることになる。これは、アプリケーションのUI(ユーザーインターフェース)スレッドをブロックし、画面がフリーズする原因となる。さらに悪いことに、これらの「置き去りにされた」コールバック参照は、ネイティブのメモリ(ヒープメモリ)を消費し続け、徐々にメモリリークを引き起こす。結果として、アプリ全体のパフォーマンスが低下し、最終的にはクラッシュにつながることもある。
このような問題を避けるための最も重要な対策は、「常に既存のコールバックを明示的に解決してから、新しいコールバックを受け入れる」ことだ。具体的には、新しいファイル選択を開始する前に、現在アクティブなfilePathCallback(現在のファイル選択のValueCallbackを保持している変数)に対してonReceiveValue(null)を呼び出す必要がある。
このonReceiveValue(null)の呼び出しは、WebViewエンジンに対して「以前のファイル選択操作はキャンセルされたので、もう結果を待つ必要はない」と明確に伝える信号となる。これにより、WebViewは待機中のスレッドと関連するリソースを即座に解放し、メモリリークやフリーズの原因を取り除くことができる。この手順は、たとえユーザーが意図的にキャンセルしなくても、新しいファイル選択を開始するたびに必ず実行すべき「お掃除」のような処理だと理解しておくと良いだろう。
ファイル選択の結果を受け取るためには、AndroidのActivityResultLauncherという仕組みを利用するのが現代的な方法だ。これは、以前のstartActivityForResultメソッドに代わる、より柔軟で安全なAPIだ。ActivityResultLauncherを設定し、ファイルピッカーからの結果を待ち受ける。
重要なのは、ユーザーがシステムファイルピッカーダイアログを「キャンセル」した場合の処理だ。この場合、結果コードはActivity.RESULT_CANCELEDとして返される。アプリはこの結果を受け取ったら、待機中のfilePathCallbackに対してやはりonReceiveValue(null)を呼び出し、WebViewスレッドを解放しなければならない。これを怠ると、WebViewはキャンセルされたにもかかわらず、永遠にファイルURIを待ち続けてしまう。
さらに、デバイスにファイルを扱う適切なアプリケーションがない場合、ActivityNotFoundExceptionというエラーが発生することがある。このような起動失敗のシナリオも考慮し、try-catchブロックで例外を捕捉し、同様にonReceiveValue(null)を呼び出してWebViewスレッドをクリーンアップする必要がある。これにより、不測の事態が発生してもアプリ全体がクラッシュすることなく、堅牢な動作を維持できる。これらの例外処理は、ユーザーがカスタムのファイルマネージャーを使用している場合や、特定の環境下で発生する可能性があるため、非常に重要だ。
複数のWebViewインスタンスを同時に動かす場合や、ユーザーがアプリ内で頻繁に画面を切り替えるような場合、アクティブなコールバックを正確に管理することは一層重要になる。そのため、filePathCallbackのようなコールバック参照は、アプリケーション全体で一元的に管理できる場所に置くべきだ。例えば、シングルトンパターンを利用した専用のクラス、またはAndroidのViewModelやFragmentのライフサイクルを考慮したコントローラークラスに集約すると良いだろう。これにより、複数の箇所からコールバックが設定されたり、意図せず古いコールバックが残存したりするのを防ぎ、メモリリークや競合バグを防ぐことができる。
また、Androidアプリでは、画面の回転、ダークモードへの切り替え、あるいはシステムによるメモリ不足のためのプロセス終了など、さまざまな要因で「アクティビティの再作成」が発生する。この際、ValueCallbackのような複雑なランタイムオブジェクトを、savedInstanceState(保存されたインスタンスの状態)にシリアライズ(保存可能な形式に変換)して保存しようとしてはならない。ValueCallbackは、内部的にネイティブコードへの参照やUI要素へのバインディングを含んでおり、これらを安全にシリアライズすることはできないからだ。
アクティビティが再作成されると、以前のアクティビティインスタンスに関連付けられていたコールバック参照は「孤立」してしまう。もし、再作成されたアクティビティが、この死んだ参照を使って操作しようとすると、メモリリークや未定義の動作を引き起こす可能性がある。したがって、アクティビティが破棄される際には、ペンディング中のファイル選択操作はきっぱりとキャンセルし、再作成されたアクティビティは「クリーンな状態」から開始するように設計するのが正しいアプローチだ。これにより、古いビュー参照との意図しない相互作用を防ぎ、アプリの安定性を保つことができる。
Android WebViewでファイル選択機能(onShowFileChooser)を実装する際には、ユーザーの素早い操作や、システムのキャンセル・エラーといったあらゆる状況を想定した堅牢な設計が求められる。特に、新しいファイル選択を開始する前に既存のコールバックをonReceiveValue(null)で明示的にキャンセルすること、ActivityResultLauncherを適切に利用して結果を処理すること、キャンセル時やエラー発生時にも同様にコールバックを解放すること、そしてコールバック参照を一元的に管理し、アクティビティの再作成時には保存しないこと、これらの原則を守ることが極めて重要だ。これらの対策を講じることで、アプリのクラッシュやフリーズを防ぎ、ユーザーに安定した快適な体験を提供できるようになるだろう。システムエンジニアを目指す上で、このようなライフサイクル管理と堅牢なエラーハンドリングは非常に重要なスキルとなるため、しっかりと理解しておくことを推奨する。