【ITニュース解説】One way to solve the Screen Rotation problem while dealing with Android Asynctask

2025年09月03日に「Dev.to」が公開したITニュース「One way to solve the Screen Rotation problem while dealing with Android Asynctask」について初心者にもわかりやすいように丁寧に解説しています。

作成日: 更新日:

ITニュース概要

Androidアプリでバックグラウンド処理中に画面を回転させると、画面が再生成されエラーになることがある。これは処理と画面の連携が切れるためだ。Activityの代わりに状態を保持する設定をしたFragmentを使うことで、この問題を解決できる。(118文字)

ITニュース解説

Androidアプリケーション開発において、画面の向きを変えるといったデバイスの設定変更は、アプリの動作に大きな影響を与える。特に、メインの画面表示を担う「アクティビティ」というコンポーネントは、ユーザーがスマートフォンを縦向きから横向きに回転させると、内部的に一度破棄され、新しいインスタンスとして再作成されるというライフサイクルを持つ。これは、異なる画面サイズやアスペクト比に対応するために、UIが適切に再構築されるように設計されたAndroidの基本的な仕組みである。

しかし、このアクティビティの再作成が、時間のかかるバックグラウンド処理と組み合わさると問題が発生することがある。例えば、インターネットから画像をダウンロードするような処理は、ユーザーインターフェース(UI)の応答性を保つために、メインのUIスレッドとは別のスレッドで実行するのが一般的だ。Androidには、そのような非同期処理を簡単に実装できる「AsyncTask(非同期タスク)」という便利なクラスが提供されている。AsyncTaskはバックグラウンドで処理を行い、その進捗をUIに通知したり、処理完了後に結果をUIに反映させたりできる。

このAsyncTaskがアクティビティから起動され、バックグラウンドで処理が進行している最中に、ユーザーが画面を回転させると、前述のライフサイクルによりAsyncTaskを起動した元のアクティビティは破棄されてしまう。そして新しいアクティビティが再作成されるが、バックグラウンドで実行中のAsyncTaskは、依然として古いアクティビティへの参照を保持している状態になる。結果として、AsyncTaskは処理完了後に古いアクティビティのUI(例えば、処理の進行状況を示すProgressDialogなど)を更新しようとするが、そのアクティビティはすでに存在しないため、参照が見つからず、アプリケーションが「WindowLeaked」や「IllegalArgumentException」といった実行時エラーでクラッシュしてしまうのだ。

記事に示された最初のコード例は、まさにこの問題に直面している。画像をダウンロードする DownloadImageAsyncTask を起動する MainActivity のコードでは、ダウンロード開始時に ProgressDialog を表示し、完了後にそれを閉じたり、画像を表示したりする。しかし、ダウンロード中に画面を回転させると、ログには「Activity has leaked window...」や「View=... not attached to window manager」といったエラーメッセージが表示される。これは、AsyncTaskが、すでに破棄された古いアクティビティに紐づくダイアログを表示し続けたり、閉じようとしたりした結果、発生したものである。

この問題を回避する一時的な方法として、アクティビティが一時停止するライフサイクルメソッドである onPause の中で、バックグラウンドタスクをキャンセルし、 ProgressDialog を強制的に閉じるという方法も考えられる。しかし、これは処理が中断されてしまうため、ユーザーはまた最初からやり直す必要があり、理想的な解決策とは言えない。

そこで推奨される解決策が、「Fragment(フラグメント)」というコンポーネントの活用である。Fragmentは、アクティビティの一部として動作する、自己完結型のUIモジュールであり、自身も独立したライフサイクルを持つ。このFragmentを適切に使用することで、画面回転によるアクティビティの再作成後も、バックグラウンド処理の状態を維持し、UIへの接続を継続できる。

具体的には、AsyncTaskを起動するFragmentの onCreate メソッド内で setRetainInstance(true) を呼び出すことが非常に重要だ。この設定を有効にすると、Fragmentは画面回転時にも自身のインスタンス(オブジェクト)を破棄せずに保持し続ける。これにより、アクティビティが再作成されても、Fragment自体はそのまま生き残り、バックグラウンドで実行中のAsyncTaskは常に有効なFragmentのインスタンスに接続されたままでいられる。したがって、AsyncTaskが処理を完了した後、新しいアクティビティに接続されたFragmentを通じて、問題なくUIを更新できるようになる。

記事に示されている MainActivityFragment のコードは、このアプローチを示している。setRetainInstance(true) の呼び出しにより、Fragmentは画面回転を乗り越えて状態を保持する。また、Fragmentがアクティビティから切り離される onDetach メソッドでは、以前表示されていた ProgressDialog を適切に閉じる処理が追加されている。これは、たとえFragmentインスタンスが保持されても、ダイアログ自体はアクティビティのコンテキストに依存するため、古いアクティビティが破棄される前に片付ける必要があるからだ。さらに、isScreenRotated というフラグを用いて、画面回転中にUI更新を一時的に抑制し、回転が完了して新しいアクティビティに接続された後に適切なメッセージを表示するなど、よりきめ細やかな制御も可能にしている。

このFragmentを用いた解決策を導入すると、メインのアクティビティのコードは大幅にシンプルになる。アクティビティは、UIの全体的な枠組みを提供するコンテナとしての役割に集中し、具体的なUI要素やバックグラウンド処理のロジックはFragmentが管理するようになる。この方法は、Android開発において、設定変更に対する堅牢性を高め、ユーザー体験を損なうことなく、バックグラウンド処理とUIの整合性を維持するための効果的なプラクティスである。