4

私のアプリケーションは、いくつかのフラグメントで構成されています。今まで、カスタム Application オブジェクトに格納されたそれらへの参照を持っていましたが、何か間違ったことをしていると思い始めています。

私の問題は、方向の変更後に mActivity へのすべてのフラグメントの参照が null になることに気付いたときに始まりました。そのため、方向の変更後に getActivity() を呼び出すと、NullPointerException がスローされます。getActivity() を呼び出す前に、フラグメントの onAttach() が呼び出されることを確認しましたが、それでも null が返されます。

以下は、アプリケーション内の唯一のアクティビティである MainActivity の削除されたバージョンです。

public class MainActivity extends BaseActivity implements OnItemClickListener,
        OnBackStackChangedListener, OnSlidingMenuActionListener {

    private ListView mSlidingMenuListView;
    private SlidingMenu mSlidingMenu;

    private boolean mMenuFragmentVisible;
    private boolean mContentFragmentVisible;
    private boolean mQuickAccessFragmentVisible;

    private FragmentManager mManager;

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

        /*
         * Boolean variables indicating which of the 3 fragment slots are visible at a given time
         */
        mMenuFragmentVisible = findViewById(R.id.menuFragment) != null;
        mContentFragmentVisible = findViewById(R.id.contentFragment) != null;
        mQuickAccessFragmentVisible = findViewById(R.id.quickAccessFragment) != null;

        if(!savedInstanceState != null) {
            if(!mMenuFragmentVisible && mContentFragmentVisible) {
                setupSlidingMenu(true);
            } else if(mMenuFragmentVisible && mContentFragmentVisible) {
                setupSlidingMenu(false);
            }

            return;
        }

        mManager = getSupportFragmentManager();
        mManager.addOnBackStackChangedListener(this);

        final FragmentTransaction ft = mManager.beginTransaction();
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);

        if (!mMenuFragmentVisible && mContentFragmentVisible) {
            /*
             * Only the content fragment is visible, will enable sliding menu
             */
            setupSlidingMenu(true);
            onToggle();

            ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);

        } else if (mMenuFragmentVisible && mContentFragmentVisible) {
            setupSlidingMenu(false);
            /*
             * Both menu and content fragments are visible
             */
            ft.replace(R.id.menuFragment, getCustomApplication().getMenuFragment(), MenuFragment.TAG);
            ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
        }

        if (mQuickAccessFragmentVisible) {
            /*
             * The quick access fragment is visible
             */
            ft.replace(R.id.quickAccessFragment, getCustomApplication().getQuickAccessFragment());
        }

        ft.commit();
    }

    private void setupSlidingMenu(boolean enable) {
        /*
         * if enable is true, enable sliding menu, if false
         * disable it
         */
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

        // launch the fragment that was clicked from the menu
    }

    @Override
    public void onBackPressed() {
        // Will let the user press the back button when
        // the sliding menu is open to display the content.
        if (mSlidingMenu != null && mSlidingMenu.isMenuShowing()) {
            onShowContent();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public void onBackStackChanged() {
        /*
         * Change selected position when the back stack changes
         */
        if(mSlidingMenuListView != null) {
            mSlidingMenuListView.setItemChecked(getCustomApplication().getSelectedPosition(), true);    
        }
    }

    @Override
    public void onToggle() {
        if (mSlidingMenu != null) {
            mSlidingMenu.toggle();
        }
    }

    @Override
    public void onShowContent() {
        if (mSlidingMenu != null) {
            mSlidingMenu.showContent();
        }
    }
}

以下は、CustomApplication の削除されたバージョンです。この実装の背後にある私の考えは、アプリケーションのライフ サイクル全体で各フラグメントのインスタンスを 1 つだけ保証することでした。

public class CustomApplication extends Application {

    private Fragment mSsportsFragment;
    private Fragment mCarsFragment;
    private Fragment mMusicFragment;
    private Fragment mMoviesFragment;

    public Fragment getSportsFragment() {
        if(mSsportsFragment == null) {
            mSsportsFragment = new SportsFragment();
        }

        return mSsportsFragment;
    }

    public Fragment getCarsFragment() {
        if(mCarsFragment == null) {
            mCarsFragment = new CarsFragment();
        }

        return mCarsFragment;
    }

    public Fragment getMusicFragment() {
        if(mMusicFragment == null) {
            mMusicFragment = new MusicFragment();
        }

        return mMusicFragment;
    }

    public Fragment getMoviesFragment() {
        if(mMoviesFragment == null) {
            mMoviesFragment = new MoviesFragment();
        }

        return mMoviesFragment;
    }
}

複数のフラグメントを最適に実装する方法と、それらの状態を維持する方法に関するヒントに非常に興味があります。参考までに、私のアプリケーションはこれまでに 15 以上のフラグメントで構成されています。私はいくつかの調査を行いましたが、 FragmentManager.findFragmentByTag() は良い賭けのようですが、うまく実装できていません。

私の実装は、方向の変更後に mActivity 参照が null になるという事実を除いて、うまく機能しているようです。これにより、メモリ リークの問題もある可能性があると信じることができます。

さらにコードが必要な場合は、お知らせください。問題はアクティビティとアプリケーションの実装に関連していると強く信じているため、意図的にフラグメント コードを含めないようにしましたが、間違っている可能性があります。

御時間ありがとうございます。

4

6 に答える 6

11

この実装の背後にある私の考えは、アプリケーションのライフ サイクル全体で各フラグメントのインスタンスを 1 つだけ保証することでした。

これはおそらく、すべてではないにしても、問題の原因の一部です。

構成が変更されると、 Android は引数なしの public コンストラクターを使用してフラグメントを再作成し、新しいインスタンスを作成します。したがって、グローバル スコープのフラグメントは、「各フラグメントのインスタンスを 1 つだけ保証する」わけではありません。

このカスタムApplicationクラスを削除してください。フラグメントが自然に再作成されることを許可してください。または、単一のアクティビティの存続期間中存続する必要がある場合は、 を使用してsetRetainInstance(true)ください。アクティビティ間でフラグメントを再利用しようとしないでください。

于 2013-04-01T13:42:29.193 に答える
3

mActivity への参照をどこで使用しているのかわかりません。しかし、それへの参照を保持しないでください。方向の変更後にアクティビティを再作成できるため、常に getActivity を使用してください。また、セッターによってフラグメントのフィールドを設定したり、割り当てによって常にバンドルと引数を使用したりしないでください

新しい Android フラグメントをインスタンス化するためのベスト プラクティス

また、 setRetainInstance(true) を使用して、向きの変更中にすべてのフラグメントのメンバーを保持することもできます。

Fragment の setRetainInstance(boolean) を理解する

于 2013-04-01T13:43:19.610 に答える
2

この問題を解決するには、フラグメントの onAttach メソッドによって提供されるアクティビティ オブジェクトを使用する必要があります。そのため、向きを変更するとフラグメントが再作成されるため、onAttach は現在の参照を提供します。

于 2013-05-14T13:13:39.047 に答える
-1

そうしないsetRetainInstance(true)と、たとえばApplication クラスonCreateのコレクションが null になります。それらを生かしてください List<Object>, Vector<Object>setRetainInstance(true)

于 2013-08-29T01:50:07.727 に答える