View not attached to window manager
例外の発生状況
ProgressDialogをAsyncTaskの中で安易に使用すると、
java.lang.IllegalArgumentException: View not attahced to window manager
という例外が発生することがある。再現する方法は以下の通り、
Avitivityの中でAsyncTaskを使用し、その処理の最初にProgressDialogを作成して表示し、処理が終了したらdismiss()を呼び出して消去する。
ProgressDialogが表示されている間(くるくる回るアイコンの表示中)に、端末の方向を変更する。例えば、OrientationをPortraitからLandscapeに変更する。
- しばらく待つと上記の例外が発生する。
AndroidはConfigurationが変更されると実行中のActivityを破棄し、同じクラスの新たなActivityを生成して表示するのだが、しかし前のActivity(破棄対象のActivity)の上に表示されているProgressDialog、あるいは破棄対象のActivity上で起動されたAsyncTaskのことは「知ったことではない」という態度のようで、放置されてしまっているようだ。
このため、AsyncTaskが終了してProgressDialogのdismiss()が呼び出されると、もはや戻るべきActivityがないせいなのか(このあたりの事情はもちろんよくわからない)、例外が発生してしまう。
この問題の議論状況
あまたに存在するウェブ上のリソース等は、この問題を放置したままである。「長時間処理を行う場合のサンプル」として公開されているものはどれもこれもこの問題を気にもとめていないものであって、端的に言えばバグっているのである。
まともに議論されているものとしては、例えば以下がある。
解決方法
解決方法として示されているものは以下の通り。
orientationの変更をさせない
AndroidManifest.xml中にActivityを定義するとき「android:configChanges="orientation"」を挿入しておく。 こうすると、configChangesの値として記述されたものが起こった時にAndroidはActivityを破棄せず、その代わりにActivityの onConfigurationChanged(Configuration)が呼び出されるとのことである(未確認)。
このようにして、ProgressDialogを表示中に端末の向きが変更された場合でもActivityを破棄せずに、そのまま処理を継続できるというわけであるが、 しかしこれには問題がある。
本来、端末の向きが変更されたらその向き用のレイアウトを使用したいのである。ProgressDialogが消された後でレイアウトを強制的に変更するにはどうすればよいのか?
- そもそも、手軽にConfigurationを変更する方法として端末の回転があるが、例えば将来的にボタン一つでLocaleを変更できるような機能が追加された場合には(他に思いつかないのだが)、このプログラムはやはりクラッシュしてしまう。
Bacon Rank Android App
AsyncTaskを作成したら、それをアプリケーション側で保持させる。 Activityが破棄あるいは再生成されたら、AsyncTaskにnullあるいは新たなActivityをセットする。 ProgressDialogではなく、(Window.FEATURE_INDETERMINATE_PROGRESSを使用する(タイトルバーに小さな進行中のアイコンが表示される)。 ProgressDialogでもうまくいくかもしれない。
Acitivityのstatic領域としてワークスレッドを保持する
上述の議論のsamsonsuさんの方法。検討中。