2013年1月10日木曜日

[Android] メモリリークしないようにViewからAlertDialogを表示する

AlertDialogの表示方法が気になったので調べてみました。
Activityに依存しない形で、カスタムしたViewからAlertDialogを表示してみます。

(ここではDialogFragmentは扱いません。が、今後はできるだけFragmentを使用するようにしましょう)


いくつかウェブサイトを見てみると、記載されている方法は大きく分けて以下の3つです。

(1) AlertDialog.builder#show()を使う
(2) 上は直接呼ばずに、Activity#onCreateDialog()をオーバーライドする
(3) 画面の回転を抑止する or 画面回転時のActivity再生成を抑止する

ちなみに、AlertDialog.builder#show()とするのも、AlertDialog#create().show()とするのも内部動作は同じことです。create()は表示はしないけどAlertDialogのみ得たい場合に使います。

(1)は一番よく見かける方法ですが、show()によって作成されたAlertDialogのインスタンスを表示したままほっとくと、画面回転等のActivity再生成時にメモリリークします。多くの記載ではAlertDialogのインスタンスが作成されたまま何も処理が行われていません。これはよろしくない。

なので、Activity#onCreateDialog()をオーバーライドして、Activityのライフサイクルに合わせてAlertDialogの表示を管理してもらおうというのが(2)の方法です。この方法では、AlertDialog.builder#show()を直接呼ばずに、Activity#showDialog()を呼び出して表示をコントロールしてもらいます。

さて、(2)を提唱する記載のうちいくつかで(1)は誤っているとしているものがありましたが。これはどうなのか。
AlertDialogを表示したままほっとくというサンプルは確かに誤っていると言えますが、AlertDialog.builder#show()を直接呼び出すこと自体は誤りとは言えません。
要するに以下のどちらを選択するか?ということになります。

・AlertDialog#show()を直接呼び出す
 →AlertDialog表示後の管理は自分で行う

・Activity#onCreateDialog()をオーバーライドする
 →AlertDialog表示後の管理をActivityにまかせる

Activityに依存できるダイアログを表示する場合にはActivity#onCreateDialog()をオーバーライドする形で良いのですが、Activityに依存しない形でダイアログを実装したい時があります。例えばカスタムViewの作成等です。カスタムViewが、それを表示するActivityに処理を依存していたらややこしいことになります。

ではカスタムView内でAlertDialogを表示した後、メモリリークしないように破棄をしてみましょう。
タップするとAlertDialogを表示するViewとしてSpinnerがあるのでサンプルとしてソースコードを見てみます。

以下はSpinnerをタップされた時の処理です。

public void show() {
    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
    if (mPrompt != null) {
        builder.setTitle(mPrompt);
    }
    mPopup = builder.setSingleChoiceItems(mListAdapter,
    getSelectedItemPosition(), this).show();
}

AlertDialog.builder#show()が呼ばれています。作成されたAlertDialogはmPopupとして保持されています。

次はAlertDialogの破棄です。View#onDetachedFromWindow()のタイミングで行っているようです。

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();

    if (mPopup != null && mPopup.isShowing()) {
        mPopup.dismiss();
    }
}

これで、Activityの破棄に連動してViewからダイアログの破棄を行うことができます。

(3)の方法はメモリリークに対する根本的な解決方法になっていないように感じるので、別の方法をとったほうがよいかなと思います。

2 件のコメント:

  1. この度slice launcherを導入させて頂きました。使い勝手はかなり良く、このまま使い続けたいと思いますが、数点の要望があります。この手のアプリはどのアプリにも言える事だと思いますが、端末の不具合等で初期化等した時に備え、登録内容のbackup/restore機能が必要と思います。又、私の様にスペックの低い機種を使用しているユーザーの為にも、SDへ移動出来る様にお願い致します。(yoshi.ueno83@gmail.com)

    返信削除
    返信
    1. ご指摘ありがとうございます。

      Google Playに追記いただいた点と合わせて3点、検討させていただきたいと思います。

      削除