android - 方向更改后, fragment 对 mActivity 的引用变为空。无效的 fragment 状态维护

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

我的应用程序由几个 fragment 组成。到目前为止,我已经将对它们的引用存储在自定义 Application 对象中,但我开始认为我做错了什么。

当我意识到我的 fragment 对 mActivity 的所有引用在方向更改后都变为空时,我的问题就开始了。因此,当我在方向更改后调用 getActivity() 时,会抛出 NullPointerException。 在调用 getActivity() 之前,我已经检查过我的 fragment 的 onAttach() 是否被调用,但它仍然返回 null。

以下是我的 MainActivity 的精简版,它是我的应用程序中唯一的 Activity 。

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 的精简版本。我对这一实现的想法是保证在我的应用程序的整个生命周期中每个 fragment 只有一个实例。

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;
    }
}

我对有关如何最好地实现多个 fragment 以及如何维护它们的状态的技巧非常感兴趣。供您引用,到目前为止,我的应用程序包含 15 个以上的 fragment 。 我做了一些研究,似乎 FragmentManager.findFragmentByTag() 是一个不错的选择,但我未能成功实现它。

除了 mActivity 引用在方向更改后变为 null 之外,我的实现似乎运行良好,这让我相信我也可能有一些内存泄漏问题。

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

感谢您的宝贵时间。

最佳答案

My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle

这可能是您遇到困难的部分原因,如果不是全部的话。

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

请删除此自定义 Application 类。请允许 fragment 自然地重新创建,或者如果它们需要为单个 Activity 的生命而活,请使用 setRetainInstance(true)。不要尝试跨 Activity 重用 fragment 。

关于android - 方向更改后, fragment 对 mActivity 的引用变为空。无效的 fragment 状态维护,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15744445/

相关文章:

Android 从另一个 Activity 更改 TextView

android - 应用程序被杀死时如何打开主要 Activity ?

java - 如何在线性布局中以编程方式创建 TextView?

安卓 : How to simplify my layout?

android - 如何使用嵌套 fragment (Android 4.2)在 ViewPager 中添加 fragment

android - 非法状态异常 : Can't change container ID of Fragment

java - 检查 fragment 是否存在并重用它

android - onResume() 和 onResumeFragments() 的区别

android - 带有 LENGTH_LONG 的 Snackbar 的实际持续时间是多少

android - 获取内部存储路径以使用 MUPDF 打开复制的 PDF