読者です 読者をやめる 読者になる 読者になる

プログラマってこんなかんじ??

アプリ作ったり歌ったりしてます

ダイアログの縦横切替でメモリリーク(XxxActivity has leaked〜)

AlertDialog.Builder#show〜 でダイアログ表示した状態で、
画面の縦横切替 を行うと例外発生してアプリ終了してしまった。


ログを見るとこんなエラーログが出力されている。

XxxActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@4053b7b0 that was originally added here


リーク発生!!


AndroidManifest.xml の 「android:configChanges」 属性をいじって
「Activityの再生成を行わないようにする」 が対応としては簡単なんだが、
これは根本的解決になっていないので却下。


ではどうしたものか、、というわけで調べてみると公式に記載発見。


Creating Dialogs


ダイアログ表示したいときは

「Activity#showDialog、onCreateDialog」を使ってねー
そうすると ダイアログのライフサイクル も Activity管理 になるよー

とのこと。


簡単に書くと、

  1. Activity#onCreateDialog を override してダイアログ実装しとく(任意idに紐付け)
  2. 任意の場所で Activity#showDialog(1で指定したid) を呼び出す
  3. onCreateDialog が呼び出されて ダイアログが表示される


そうすることで、
ダイアログ表示したままで Activityが再起動(onDestroy〜onCreate) したとしても、
Activityと一緒に自動的にダイアログも再表示される、と。
なるほど。


というわけでさっそく、
ボタン押下でダイアログを表示するだけのサンプルアプリ書いてみた。

public class MainActivity extends Activity {

    private static final int DIALOG_ID_ABESHI = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //case1 WindowLeaked発生するパターン(修正前)
        Button buttonLeak = new Button(this);
        buttonLeak.setText("leak");
        buttonLeak.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Dialog dialog = createDialog(DIALOG_ID_ABESHI);
                if (dialog != null) {
                    dialog.show();
                }
            }
        });

        //case2 WindowLeaked発生しないパターン(修正後)
        Button buttonNotLeak = new Button(this);
        buttonNotLeak.setText("not leak");
        buttonNotLeak.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                showDialog(DIALOG_ID_ABESHI);
            }
        });

        LinearLayout layout = new LinearLayout(this);
        layout.addView(buttonLeak);
        layout.addView(buttonNotLeak);
        setContentView(layout);
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        return createDialog(id);
    }

    /**
     * ダイアログ生成
     * @param id
     * @return
     */
    private Dialog createDialog(int id) {
        if (id == DIALOG_ID_ABESHI) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("あべし");
            builder.setPositiveButton("OK", new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d(getClass().getName(), "onClick");
                }
            });
            return builder.create();
        }
        return null;
    }
}


このコードで、ダイアログを表示したままで画面の縦横切替を行うとこんな結果になる。

case1 WindowLeaked〜 が発生する(ダイアログは勝手に閉じてそのままアプリ終了)
case2 ダイアログが表示された状態 で縦横画面表示


修正後の case2 だと、リーク発生しなくなった!!


そこでちょっと沸いた疑問。


もしかして、Activity#showDialog を呼ぶたびに onCreateDialog が毎回呼び出されて
毎回ダイアログ再生成、とかはないよなー??と。


Activity.java のソース見てみるとそんなことはもちろんなく、
一度生成済のダイアログは内部で保持されている様子。
よくできてる。


というわけで、ダイアログ表示する際には、
「Activity#showDialog、onCreateDialog」を使ったほうがいいね、というお話でした!