web-dev-qa-db-ja.com

画面を回転してもフラグメントが状態を保持しないのはなぜですか?

画面が回転してもPreferenceFragment内のカスタムDialogPreferenceサブクラスが表示されたままになる問題が発生しています。 PreferenceActivityを使用してもこの問題は発生しないので、Androidバグなのか、コードの問題なのかはわかりませんが、誰かに確認してもらいたいのですが同じ経験をしています。

これをテストするには、まず、少なくとも1つのDialogPreferenceを含む設定画面を作成します(どのサブクラスでもかまいません)。次に、それをPreferenceActivityに表示します。アプリを実行したら、DialogPreferenceを押してダイアログを表示します。次に、画面を回転して方向を変更します。ダイアログは表示されたままですか?

次に、同じことを試しますが、PreferenceActivityではなくPreferenceFragmentを使用して設定を表示します。繰り返しますが、画面を回転してもダイアログは表示されたままですか?

これまでのところ、PreferenceActivityを使用している場合はダイアログが表示されたままで、PreferenceFragmentを使用している場合は表示されないことがわかりました。 DialogPreferenceのソースコード を見ると、isDialogShowingonSaveInstanceState()のときに保存される状態情報なので、正しい動作はダイアログが表示されたままであるようです。画面の再配置で呼び出されます。したがって、バグが原因でPreferenceFragment(およびその中のすべてのもの)がその状態情報を復元できない可能性があります。

Androidバグの場合、PreferenceFragmentを使用するユーザーは状態情報を保存および復元できないため、広範囲にわたる影響があります。

誰か確認してもらえますか?バグではない場合、何が起こっているのでしょうか?

27

最後に、この問題の解決策を見つけました。これはバグではなく、Android開発者向けドキュメントの問題/見落としではありません。

ほら、私はPreferenceFragmentチュートリアル here に従っていました。その記事では、PreferenceFragmentをアクティビティ内でインスタンス化するために次のことを行うように指示しています。

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(Android.R.id.content, new SettingsFragment())
                .commit();
    }
} 

これの問題は、画面の向き(またはアクティビティを破棄して再作成するその他のアクション)を変更すると、PreferenceFragmentが作成されるtwice、それが状態を失う原因になります。

firstの作成は、super.onCreate()(上記)へのアクティビティの呼び出しによって行われ、onActivityCreated()メソッドが呼び出されますPreferenceFragment()とそれに含まれる各PreferenceのonRestoreInstanceState()メソッド。これらはすべての状態を正常に復元します。

しかし、super.onCreate()への呼び出しが戻ると、onCreate()メソッドがPreferenceFragment asecond時間。再び無意味に作成されるため(今回は、状態情報なし!)、正常に復元された状態はすべて完全に破棄/失われます。これは、アクティビティが再作成されると、アクティビティが破棄されたときに表示されていた可能性があるDialogPreferenceが表示されなくなる理由を説明しています。

それで、解決策は何ですか?次のように、PreferenceFragmentがすでに作成されているかどうかを確認する小さなチェックを追加するだけです。

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Fragment existingFragment = getFragmentManager().findFragmentById(Android.R.id.content);
        if (existingFragment == null || !existingFragment.getClass().equals(SettingsFragment.class))
        {
            // Display the fragment as the main content.
            getFragmentManager().beginTransaction()
                .replace(Android.R.id.content, new SettingsFragment())
                .commit();
        }
    }
}

または、次のように、onCreate()が状態を復元するためのものかどうかを単にチェックする方法もあります。

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null)
        {
            // Display the fragment as the main content.
            getFragmentManager().beginTransaction()
                .replace(Android.R.id.content, new SettingsFragment())
                .commit();
        }
    }
}

したがって、ここで学んだ教訓は、onCreate()には2つの役割があるということです。これは、アクティビティを初めてセットアップすることも、以前の状態から復元することもできます。

答え here がこの解決策の実現につながりました。

52

私自身もこの問題を抱えていました。 DialogFragmentがnullであるために状態を復元しない、または少なくともそれが私に起こったというバグがあります。

複数のソースを使用して、最終的にはソリューションが機能するようになりました。ダイアログでこれを拡張してくださいBaseDialogFragment

_import Android.app.Dialog;
import Android.os.Bundle;
import Android.util.Log;
import Android.view.LayoutInflater;
import Android.view.View;
import Android.view.ViewGroup;
import Android.support.v4.app.DialogFragment;

import com.actionbarsherlock.app.SherlockDialogFragment;

public class BaseDialogFragment extends DialogFragment {

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        if (savedInstanceState == null || savedInstanceState.isEmpty())
            savedInstanceState = WorkaroundSavedState.savedInstanceState;

        setRetainInstance(true);
        Log.d("TAG", "saved instance state oncreate: "
                + WorkaroundSavedState.savedInstanceState);
        super.onCreate(savedInstanceState);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        if (savedInstanceState == null || savedInstanceState.isEmpty())
            savedInstanceState = WorkaroundSavedState.savedInstanceState;
        Log.d("TAG", "saved instance state oncretaedialog: "
                + WorkaroundSavedState.savedInstanceState);

        return super.onCreateDialog(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (savedInstanceState == null || savedInstanceState.isEmpty())
            savedInstanceState = WorkaroundSavedState.savedInstanceState;

        Log.d("TAG", "saved instance state oncretaeview: "
                + WorkaroundSavedState.savedInstanceState);

        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onDestroyView() // necessary for restoring the dialog
    {
        if (getDialog() != null && getRetainInstance())
            getDialog().setOnDismissListener(null);

        super.onDestroyView();
    }

    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        // ...

        super.onSaveInstanceState(outState);
        WorkaroundSavedState.savedInstanceState = outState;
        Log.d("TAG", "saved instance state onsaveins: "
                + WorkaroundSavedState.savedInstanceState);

    }

    @Override
    public void onDestroy()
    {
        WorkaroundSavedState.savedInstanceState = null;
        super.onDestroy();
    }

    /**
     * Static class that stores the state of the task across orientation
     * changes. There is a bug in the compatibility library, at least as of the
     * 4th revision, that causes the save state to be null in the dialog's
     * onRestoreInstanceState.
     */
    public static final class WorkaroundSavedState {
        public static Bundle savedInstanceState;
    }
}
_

メソッドにsavedInstanceStateパラメータがあるサブクラスでは、_WorkaroundSavedState.savedInstanceState_を使用してスーパーを呼び出す必要がある場合があることに注意してください。状態を復元するとき(つまりonCreate()の場合)、savedInstanceStateを無視して、代わりに_WorkaroundSavedState.savedInstanceState_を使用します。静的ホルダーは最もクリーンなソリューションではありませんが、機能します。 onDestroy()でnullに設定してください。

いずれにせよ、画面を回転させてもDialogFragmentは消えません(そしてconfigChangesがないためです)。このコードで問題が解決されたかどうかをお知らせください。解決されていない場合は、何が起こっているのかを見ていきます。また、これをPreferenceFragment内でテストしていないことに注意してください。代わりに、互換性クラスまたはFragmentからの他のActionBarSherlocksをテストしました。

0
Oleg Vaskevich