2013年8月4日日曜日

アプリ作成中にハマった点など

先日、α版をリリースしましたが、いくつもバグを出してしまいました(申し訳ありません)。いくつかはまった点を書いておきます。多少ともどなたかの参考になれば幸いです。

Activityのライフサイクルを確認するときは開発者オプションを使う

最もバグが出てしまったのは、Activityの状態管理の部分です。

このゲームでも、Androidの仕様通り、ActivityのonCreateやonPause等で、BundleにActivityの状態を保存しています(所持金や所持品、パーティ情報など)。この処理は最初から組み込んであったのですが、テスト環境(エミュレータおよび実機)では、ほとんど動作していなかったようです。テスト中にはそのアプリしか動かさないので、Activityがずっと保持されていたわけです。

開発者オプションの「アクティビティを保持しない」にチェックを入れると、Activityから離れたときに、すぐにGCされるようになるようです。リリース前に必ずこの状態でテストしたほうがよいでしょう。

ParcelクラスのreadParcelableに指定するクラスローダ

Parcelから、Parcelableのオブジェクトを取り出すときに使うのがreadParcelableメソッドですが、この引数に指定するクラスローダは、アプリのクラスローダにする必要があるようです(Androidのクラスローダにはシステムクラスローダとアプリのクラスローダがある、参考)。readParcelable(null)とすると、システムクラスローダが使用され、ClassNotFoundExceptionになります。

以下のようにするといいようです。
dungeonContext = source.readParcelable(this.getClass().getClassLoader());

Activity間で共有するインスタンスとライフサイクルに注意する

Activityの遷移時に大きめのデータを渡すとき、以下のページにあるようにpublic static fieldでデータを共有しています。


このとき、前のActivityは遷移後もなくなったわけではなく、次のActivityが生成された後で、onPauseやonDestroyが呼ばれることがあります。そこで、共有したオブジェクトを操作してしまうと(たとえばBundleに保存するなど)、わかりにくいバグが起きる可能性があります。

渡した後は触らないようにするか、素直にIntentを使うのがよさそうです。

ダイアログ表示ボタンを連打するとDialogFragmentが二重に表示される

ボタン連打でダイアログが二重に表示される問題は以前からありました。前作ではDialogFragmentは使っていませんでしたので、「表示する処理(ボタンの処理)で表示中かどうか判定する」という泥臭い処理を行っていました。

今回はダイアログの数が増えていますので、すべてにその処理をいれるのは面倒です。また、DiagloFragmentを使うようにしたので、共通化ができそうです。

そこで、以下のブログの記事を参考にさせていただきました。有用な記事をありがとうございます。


ただ、これでも「二つのボタンを同時押しする」と二重に表示されてしまいます。実機だと、二本指でタップすることで、容易に再現できます。

ですので、アプリのダイアログ基底クラスに、以下のように表示中フラグを導入しました。とりあえず問題なく動作しているようです。

private static boolean dialogShowing = false; // 表示中フラグ
private static Object lock = new Object(); // ロック用(いらないかも)

public void show(FragmentManager manager) {
    synchronized (lock) {
        if (dialogShowing) {
            return;
        }
        dialogShowing = true;
    }

    dismissPreviousDialog(manager, ALERT_DIALOG_FIXED_TAG);
    super.show(manager, ALERT_DIALOG_FIXED_TAG);
}

@Override
public void onResume() {
    super.onResume();
    synchronized (lock) {
        dialogShowing = false;
    }
}

private void dismissPreviousDialog(FragmentManager manager, String tag) {
    AlertDialogFragment prev = (AlertDialogFragment) manager.findFragmentByTag(tag);
    if (prev != null) { // フラグメントが表示されていなければ処理なし
        Dialog dialog = prev.getDialog();
        if (dialog != null) { // ダイアログがなければ処理なし
            if (dialog.isShowing()) { // ダイアログが表示されていなければ処理なし
                prev.dismiss(); // ダイアログ消去
            }
        }
    }
}
コードが読みにくくて申し訳ありません。

0 件のコメント:

コメントを投稿