方向改变后,片段对mActivity的引用变为null。片段状态维护无效

时间:2013-04-01 13:31:17

标签: android android-fragments android-lifecycle android-memory

我的应用程序由几个片段组成。到目前为止,我已将它们的引用存储在自定义的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的剥离版本。我在这个实现背后的想法是在整个应用程序的生命周期中只保证每个片段的一个实例。

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这一事实让我相信我可能也有一些内存泄漏问题。

如果您需要查看更多代码,请告诉我们。我故意避免包含片段代码,因为我坚信问题与我的Activity和Application实现有关,但我可能错了。

感谢您的时间。

6 个答案:

答案 0 :(得分:11)

  

我在这个实现背后的想法是在整个应用程序的生命周期中只保证每个片段的一个实例

这可能是你困难的根源(如果不是全部的话)。

在配置更改时, Android将重新创建片段,方法是使用公共零参数构造函数创建新实例。因此,您的全局范围片段不会“仅保证每个片段的一个实例”。

请删除此自定义Application课程。请允许自然地重新创建片段,或者如果它们需要在单个活动的生命中存活,请使用setRetainInstance(true)。不要尝试跨活动重用片段。

答案 1 :(得分:3)

我看不到你在哪里使用mActivity的引用。但是不要提及它。始终使用getActivity,因为可以在方向更改后重新创建Activity。此外,不要通过setter设置片段的字段,也不要指定始终使用Bundle和Arguments

Best practice for instantiating a new Android Fragment

此外,您可以使用setRetainInstance(true)在方向更改期间保留所有片段的成员。

Understanding Fragment's setRetainInstance(boolean)

答案 2 :(得分:2)

要解决此问题,您必须使用片段的onAttach方法提供的activity对象,以便在更改方向片段时重新创建,以便onAttach为您提供当前引用

答案 3 :(得分:0)

您可以使用onAttach(Context context)在此片段中创建私有上下文变量

 @Override
public void onAttach(Context context) {
    this.context = context;
    super.onAttach(context);
}

关于更改方向,onAttach为您提供了对上下文的新引用,如果您想要引用活动,则可以将上下文强制转换为活动。

答案 4 :(得分:0)

当设备旋转时调用OnCreate时,上下文也可以在onCreate内的片段中重新分配

private Context mContext; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //get new activity reference here mContext = getActivity(); }

在整个片段中传递此mContext

答案 5 :(得分:-1)

如果setRetainInstance(true)中没有onCreate,那么Application类中的集合List<Object>, Vector<Object>将为空。确保setRetainInstance(true)让它们活着。