スポンサーリンク

Android SDK の動かないコード(中級編) 端末を「縦横切り替え」すると,ダイアログやアクティビティが死に WindowLeaked エラー


以下のAndroidアプリのコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:

  • Facebook SDKを使って,ログイン用のダイアログを表示する。
package com.facebook.android;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.facebook.android.Facebook.*;

public class MyGreatActivity extends Activity {

    Facebook facebook = new Facebook("(ここにAPP IDを記載する)");

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        facebook.authorize(this, new DialogListener() {
            @Override
            public void onComplete(Bundle values) {}

            @Override
            public void onFacebookError(FacebookError error) {}

            @Override
            public void onError(DialogError e) {}

            @Override
            public void onCancel() {}
        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        facebook.authorizeCallback(requestCode, resultCode, data);
    }
}

※このコードは,Facebookによって提供されている公式サンプルコードである。

Androidアプリで,Facebook APIを利用するための手順 (Facebook SDK for Androidの使い方)
http://language-and-engineering.hatenablog.jp/entry/20110830/p1

発生する問題


Facebookのログインダイアログが表示される際,「Loading...」という文言が表示されるが,

その最中に端末の「縦横切り替え」を行うと,アプリが強制終了する。


Logcatの内容:

09-05 22:33:56.294: ERROR/WindowManager(2120): Activity com.example.MyGreatActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@2f55d9b8 that was originally added here
09-05 22:33:56.294: ERROR/WindowManager(2120): android.view.WindowLeaked: Activity com.example.MyGreatActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@2f55d9b8 that was originally added here
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.view.ViewRoot.<init>(ViewRoot.java:215)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:148)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.view.Window$LocalWindowManager.addView(Window.java:409)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.app.Dialog.show(Dialog.java:238)
09-05 22:33:56.294: ERROR/WindowManager(2120): at com.facebook.android.Facebook.dialog(Facebook.java:622)
09-05 22:33:56.294: ERROR/WindowManager(2120): at com.facebook.android.Facebook.startDialogAuth(Facebook.java:297)
09-05 22:33:56.294: ERROR/WindowManager(2120): at com.facebook.android.Facebook.authorize(Facebook.java:195)
09-05 22:33:56.294: ERROR/WindowManager(2120): at com.facebook.android.Facebook.authorize(Facebook.java:103)
09-05 22:33:56.294: ERROR/WindowManager(2120): at com.example.MyGreatActivity.onCreate(LoginActivity.java:31)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2364)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2417)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.app.ActivityThread.access$2100(ActivityThread.java:116)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1794)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.os.Handler.dispatchMessage(Handler.java:99)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.os.Looper.loop(Looper.java:123)
09-05 22:33:56.294: ERROR/WindowManager(2120): at android.app.ActivityThread.main(ActivityThread.java:4203)
09-05 22:33:56.294: ERROR/WindowManager(2120): at java.lang.reflect.Method.invokeNative(Native Method)
09-05 22:33:56.294: ERROR/WindowManager(2120): at java.lang.reflect.Method.invoke(Method.java:521)
09-05 22:33:56.294: ERROR/WindowManager(2120): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:799)
09-05 22:33:56.294: ERROR/WindowManager(2120): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
09-05 22:33:56.294: ERROR/WindowManager(2120): at dalvik.system.NativeStart.main(Native Method)

原因

ダイアログ利用時に縦横切り替えを行うと,WindowLeakedエラーになる場合がある。


縦横切り替えは,アクティビティの新規生成になる。

Dialog利用時のWindowLeakedエラー
https://groups.google.com/group/andro...

  • onCreate 内で showDialog がよろしくない
  • onResumeに移したらうまくいきました。
  • 「縦横切替」を行った場合はアクティビティは状態が変わるというより一旦今のアクティビティを解放して、新しいアクティビティを生成している。そのためそれを考慮せず作成した場合アクティビティが知らない間に複数起動してしまうことがあります
  • onStop() や onDestroy() を経由せずいきなりプロセスが解放される時もある
  • onResume() , onPause() に読込、保存を等をしたほうがいい


コンポーネントのライフサイクル
http://developer.android.com/intl/ja/...

解決策

応急処置としては,AndroidManifest.xml中で,

該当アクティビティのactivityタグの属性として下記を追加すればよい。

                  android:configChanges="orientation|keyboardHidden"

参考:

端末の縦横を切り替えてもMediaPlayerの再生を停止しない方法
http://www.musta.jp/archives/2010/07/...

  • Androidでは画面の縦横が切り替わると、Activity が一度終了してしまう
  • AndroidManifest の該当 Activity に下記のように指定すると終了させないようにすることができる
    • android:configChanges="orientation|keyboardHidden"


androidで画面の向きや回転の制御をすごく簡単に行う方法
http://masterka.seesaa.net/article/19...

  • カスタマイズしたListViewとタブを組み合わせてtwitter風のUIで作った画面の向きを変えると以下のエラーが出た。android.view.WindowLeaked: Activity
  • エラーの原因は、画面の向き変更時にactivityのrestore等のタイミングで裏側を走っているスレッドがUIを触っていることらしい
  • 普通に回転を処理しようとすると以下のように処理が動く
    • onSaveInstanceState→onPause→onStop→onDestroy→onCreate→onRestoreInstanceState→onResume
  • しかし、android:configChangesを入れると上記の処理は動かずに安全に設定変更として回転処理を行ってくれる


しかし,この方法は応急処置である。



ちゃんと対処したい場合,下記のようにする。

  • ダイアログ呼び出し処理は showDialog( int ) だけ書く。
  • 具体的なダイアログの描画内容については,アクティビティの onCreateDialog( int ) に渡ってきた引数に基づいて場合分けして描画処理を行う。


サンプルコード:

ダイアログは永遠に(2) - ダイアログとアクティビティ
http://ichitcltk.hustle.ne.jp/gudon/m...

  • アクティビティクラスには、ダイアログの状態を自動的に管理する仕組みが用意されている。
  • アクティビティに結びつけられた、複数のダイアログを管理するために、ユーザ定義のダイアログの識別子(ID)が使われる。
  • ダイアログを操作するには、ダイアログのIDを引数にとる以下の3つのメソッドがある。


[android]ダイアログ表示方法(画面の縦横切替で XxxActivity has leaked〜 エラー対策)
http://d.hatena.ne.jp/dai4649/20110513

  • リーク発生するコードとしないコード
  • 一度生成済のダイアログは内部で保持されている
  • ダイアログ表示する際には、「Activity#showDialog、onCreateDialog」を使ったほうがいい


参考:

Androidのダイアログ表示でメモリリーク?
http://memory.empressia.jp/article/43...

  • 開発環境とかでログを表示しながら、実際にダイアログを表示して、縦横切り替えをしてみると「android.view.WindowLeaked」って表示される
  • Dialogは、Activityの一部として扱われるから、Activityのライフサイクルにのせてあげないといけない。のせないなら、自前でちゃんと管理しないとダメ
  • ライフサイクルにのせる方法は、ActivityのonCreateDialog(int)で生成してreturnで返してあげるしかない
  • 「android:configChanges="orientation|keyboardHidden"」書けばいいじゃんって思った人
    • RootActivity以外でDialog表示して、Homeキーとかおして別の重いアプリとか起動してみると、裏でActivityが終了して同じエラーが出る
  • Androidのダイアログは、onCreateDialog(int)で作りましょう

6.4 ダイアログの作成
http://www.techdoctranslator.com/andr...

  • 通常はアクティビティ内の onCreateDialog(int) コールバックメソッドからダイアログを生成します。このコールバックを使用すると、Android システムが各ダイアログの状態を自動的に管理し、それらをアクティビティと関連付けを行うことによりそのアクティビティがダイアログの事実上の"オーナー"となります。

しかし残念ながら,冒頭のコードでは facebook.authorize() によってダイアログ生成しているが,

そのメソッドは void なので,Dialogを返してもらえない。

なので,onCreateDialog() の実装には利用できない。

応急処置で我慢するしかない,という事になる。