Locked History Actions

Diff for "Android/View_not_attached_to_window_manager"

Differences between revisions 10 and 11
Deletions are marked like this. Additions are marked like this.
Line 136: Line 136:

== 私の見解 ==

Activityの中にビジネスロジックを実装しようとするのが根本的な間違いである。
もちろん、これはAndroidに限らず多くのGUI開発環境で行いがちのことではあるのだが。

一つのウインドウクラスを作成してその中にボタンや何やらをつめこみ、ついでにその画面にやらせたいことを一緒に記述してしまう。
例えば、データベースをオープンして、指定された条件で検索し、結果を取得するといったような処理である。


これは基本的には間違いであり、他の多くのGUI環境でも、処理内容・コード量があまりに小さいといった特殊なケース以外は行ってはならない。

しかし、Android環境の場合ではもっとラジカルになる。つまり、絶対に行ってはならない。
なぜなら、Android環境では、進行中のビジネスロジックとは無関係にウインドウ(Activity)がいつ破棄されるかわからないからだ。

ここが従来のGUI環境とは全く異なる。従来のGUI環境であれば、ウインドウが不意に消失してしまうことは基本的に想定する必要がないのである。
ウインドウの流れとビジネスロジックの流れを不可分にしてもうまく行ってしまう。

しかし、Androidアプリのビジネスロジックは、Activityとは無関係な流れを形成していなくてはならないのであり、Activityというものはその流れの中のある局面をユーザに表示するためだけのものでなくてはいけない。


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.

もう一つの方法

0 down vote

I faced this same problem, and I came up with a solution that didn't invole using the ProgressDialog and I get faster results.

What I did was create a layout that has a ProgressBar in it.

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ProgressBar

  • android:id="@+id/progressImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"

    />

</RelativeLayout>

Then in the onCreate method do the following

public void onCreate(Bundle icicle) {

  • super.onCreate(icicle); setContentView(R.layout.progress);

}

Then do the long task in a thread, and when that's finished have a Runnable set the content view to the real layout you want to use for this activity.

For example:

mHandler.post(new Runnable(){

public void run() {

  • setContentView(R.layout.my_layout);
  • }

});

This is what I did, and I've found that it runs faster than showing the ProgressDialog and it's less intrusive and has a better look in my opinion.

However, if you're wanting to use the ProgressDialog, then this answer isn't for you.

私の見解

Activityの中にビジネスロジックを実装しようとするのが根本的な間違いである。 もちろん、これはAndroidに限らず多くのGUI開発環境で行いがちのことではあるのだが。

一つのウインドウクラスを作成してその中にボタンや何やらをつめこみ、ついでにその画面にやらせたいことを一緒に記述してしまう。 例えば、データベースをオープンして、指定された条件で検索し、結果を取得するといったような処理である。

これは基本的には間違いであり、他の多くのGUI環境でも、処理内容・コード量があまりに小さいといった特殊なケース以外は行ってはならない。

しかし、Android環境の場合ではもっとラジカルになる。つまり、絶対に行ってはならない。 なぜなら、Android環境では、進行中のビジネスロジックとは無関係にウインドウ(Activity)がいつ破棄されるかわからないからだ。

ここが従来のGUI環境とは全く異なる。従来のGUI環境であれば、ウインドウが不意に消失してしまうことは基本的に想定する必要がないのである。 ウインドウの流れとビジネスロジックの流れを不可分にしてもうまく行ってしまう。

しかし、Androidアプリのビジネスロジックは、Activityとは無関係な流れを形成していなくてはならないのであり、Activityというものはその流れの中のある局面をユーザに表示するためだけのものでなくてはいけない。