Revision 9 as of 2010-10-25 05:24:07

Clear message
Locked History Actions

Android/View_not_attached_to_window_manager

View not attached to window manager

例外の発生状況

ProgressDialogAsyncTaskの中で安易に使用すると、

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さんの方法。検討中。

ActivityをDialogとして使う

※上述の議論のruiさんの方法。以下は抄訳。

すべてを試し、何日間も実験した。 Activityの回転を拒否することはしたくない。 やりたいことと言うと。

  1. プログレスダイアログは動的な情報を表示する。例えば、「サーバに接続中」とか「データをダウンロード中」とか。
  2. スレッドが重い処理を行い、ダイアログを更新する。
  3. 最後に、結果をUIに表示する。

問題は、スクリーンが回転すると、本に書かれているすべての解決策は失敗するということだ。 こういった状況で「正しい」やり方であるはずのAsyncTaskクラスを使っても。 スクリーンが回転すると、スレッドがやりとりしている「現在のコンテキスト」は消滅してしまう。そして、表示中のダイアログは失敗する。 いかなるトリック(新しいコンテキストを実行中のスレッドに与えようと、スレッド状態を回転中に保持しようと)をコードに追加しようと、問題はいつでもダイアログなのだ。 コードは複雑になるが、必ず失敗する。

うまく行った唯一の解決策はActivity/Dialogトリックだ。

  1. ダイアログを作成して表示するのではなく、マニフェストでandroid:theme="@android:style/Theme.Dialog"と設定したActivityを作成する。これはダイアログのように見える。
  2. showDialog(DIALOG_ID)ではなく、startActivityForResult(yourActivityDialog, yourCode)にする。
  3. Use onActivityResult in the calling Activity to get the results from the executing thread (even the errors) and update the UI.
  4. On your 'ActivityDialog', use threads or AsyncTask to execute long tasks and onRetainNonConfigurationInstance to save "dialog" state when rotating the screen.

これは早いしうまく動く。

This is fast and works fine. I still use dialogs for other tasks and the AsyncTask for something that doesn't require a constant dialog on screen. But with this scenario, I always go for the Activity/Dialog pattern.

And, I didn't try it, but it's even possible to block that Activity/Dialog from rotating, when the thread is running, speeding things up, while allowing the calling Activity to rotate.