mucchinのAndroid戦記

起動時にDialogを表示させるActivityで、横向き(LANDSCAPE)から縦向き(PORTRAIT)へ向きを変えたときに発生するエラーの対処方法

起動時にDialogを出すActivityで、画面の向きを切り替えるとエラーが発生するけどどうしたらいいの?

以下のようなパターンのActivityを持つアプリって、割とよく作られるのではないかと思います。
起動後すぐに、HTTP通信をして、画面に表示するための画像だとか文字列だとかを取得した後に、画面表示を行う。
その通信をする時に、通信用のスレッドを起動させて、ProgressDialogを表示させておく。
データの取得が完了したら、ProgressDialogを消す。
というような仕様のActivityを持つアプリ。

私もそんなアプリを作りました。
やり方としては、まず、onCreate()でProgressDialogを表示させて、通信をするスレッドを起動する。
このスレッドのRunnableには、Handlerのインスタンスを渡しておいて、通信が完了したら、渡したHandleのsendMessage()を実行して、handleMessage()でProgressDialogを消す(dismiss()を実行する)という事をしました。
※上記の意味がよくわからないという人は、おそらくこの記事の問題にぶち当たってないと思うので、あまりいないかなと・・・、という事で、今回は上記の詳細説明は省略します。


そこで発覚した問題が・・・。
何故か、画面の向きを切り替えると、強制終了するのです!
発生したExceptionを見てみると、
Activity xxActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView云々と出てる。

なんだこりゃ?
色々調べて、色々試しました。


まず、onCreate()でDialogを出さない方がいい、という情報を見つけました。
onResume()やonStart()でDialogを出した方がいい、と。
で、試しましたが、結果、解決せず・・・。
これで解決したという人もいるみたいですので、試してみてください。
次に見つけた方法は、Activityの向きを固定してやれというものでした。
ユーザビリティの観点から、その案は自分の中で棄却しました。
ちなみに、縦向き、あるいは横向き固定でいい、むしろその方法がいい!という事でしたら、それでもOKです。
設定方法は、Manifestファイルの設定で簡単に出来ます。
1.Manifestファイルを、EclipseのAndroid Manifest Editorで開く。
2.Applicationタブを開く。
3.画面左下の「Application Nodes」から、向きを固定したいActivityをクリック
4.その右側に「Screen Orientation」というのがあるので、プルダウンから「portrait」か「landscape」を選択する。縦向き固定なら「portrait」、横向き固定なら「landscape」。
で、OKです。
以下の画像の赤枠部分です。

次に見つけた方法は、向きが変わっても初期化されないようにする、というものです。
Manifestファイルの、問題の発生するActivityのタグに、以下のようなconfigChangesの設定の記述を入れます。(詳細は後述します。)
<activity android:name=”xxActivity” android:configChanges=”orientation”>
</activity>
Eclipseでの設定方法
1.Manifestファイルを、EclipceのAndroid Manifest Editorで開く。
2.Applicationタブを開く。
3.画面左下の「Application Nodes」から、問題の発生するActivityをクリック
4.その右側に「Config Changes」というところのSelectボタンを押す。
 ※先ほどの画像の、赤枠部分の一つ下の項目です。
5.「orientation」という項目にチェックを入れる。
これをすると、どうなるのか。
画面を切り替えても、Activityの破棄、生成が行われない。らしい・・・。
私の場合、何故かこの設定をする事で強制終了はしなくなりました。
ですが、動きがかなり気になったので、もう少し突っ込んで調べてみました。
スポンサーリンク




Config Changesの意味、役割とは?
Config Changesの設定をする事で、Activity側で、その端末の設定が変わったというイベントを処理できるようになるようです。
今回の場合、orientationのイベント(画面の向きを変えたとき)は、VMに任せず、自分でやりますよー。という宣言みたいな意味合いでしょうか。
Activity側でonConfigurationChanged()を実装してやれば、そのメソッドでイベントを受け取る事ができるようになる。という事らしい。
でも、おかしい・・・。
おかしいと思ったのは以下の点です。
・画面を切り替えたときに、結局onCreate()が呼ばれている。

・横向き(landscape)から縦向き(portrait)へ切り替えたときは、onConfigurationChanged()が呼ばれるが、縦向き(portrait)から横向き(landscape)へ切り替えたときはonConfigurationChanged()が呼ばれない。

・onConfigurationChanged()がonCreate()の後に呼ばれる。

・onCreate()でgetResources().getConfiguration()で取ってきたConfigrationのorientationを見ると、横向き(landscape)から縦向き(portrait)へ変えても、縦向き(portrait)から横向き(landscape)に変えても、常に横向き(landscape)になってる。

一つ目、二つ目に関しては、全く謎です。
そういう仕様なのか、Androidのバグなのか。
三つ目が疑問に感じた理由は、orientationが変わったからActivityのonCreate()が呼ばれたはずなのに、なぜ、ConfigrationChangedイベントがonCreateより後に来るのか?という事です。そういう仕様?逆じゃないの?
四つ目に関しても、全く謎です。
例えば、最初にActivityを表示したときの画面の向きに依存する、というのなら理解できるのですが、そういうわけでもないようです。


という事で、更に調べました。

まず一つ目の疑問の、画面を切り替えたときに、結局onCreate()が呼ばれてしまうではないか!という問題です。
これは、実はorientationの他に、変更されているConfigがあるから、です。
それは、「keyboardHidden」というものです。
これは、どうもエミュレータがそういう設定なのでしょうね。
用いる実機によっては、発生しないかもしれません。
要はハード側の実装依存という事です。
こういう問題に出くわすと、実機でのテストもやっぱり必要だなと感じました。

これがわかって、Config Changesの「keyboardHidden」にもチェックを入れてみました。
動作させると、onCreate()が実行されなくなりました。
納得!!というわけで、一つ目の疑問は解決!!
二つ目の疑問を飛ばして、三つ目の疑問の、「onConfigurationChanged()がonCreate()の後に呼ばれる。」という件も、これでわかりました。
keyboardHiddenによるConfig変更により、onCreate()が実行されていたという一つ目の疑問に関連しますが、keyboardHiddenが変わったイベントの後で、orientationが変わったというイベントが来ているからですね。
これも納得。
これで疑問の半分は解決しました。
4つ目の疑問は、2つ目の疑問が解決できれば自動的に解決できるような気がしますが・・・。
二つ目の「横向き(landscape)から縦向き(portrait)へ切り替えたときは、onConfigurationChanged()が呼ばれるが、縦向き(portrait)から横向き(landscape)へ切り替えたときはonConfigurationChanged()が呼ばれない。」という問題に関しては、結局わかりません・・・。
しかし、エミュレータがそういう仕様なのかもしれない、もしくは不具合?という気がしています。
要は、ハード側の実装依存で、Android端末の種類によって変わるのではないか?という予想です。
というわけで、これは実機を入手後、検証したいと思います。

上記で、何か私の誤認などがあり、親切にもご指摘くださいます方がおられましたら、ご指摘をお願いします。