4

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

当我意识到我的片段对 mActivity 的所有引用在方向更改后都变为空时,我的问题就开始了。因此,当我在方向更改后调用 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 引用在方向更改后变为空,这让我相信我也可能有一些内存泄漏问题。

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

谢谢你的时间。

4

6 回答 6

11

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

这可能是你的困难的一部分,如果不是全部的话。

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

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

于 2013-04-01T13:42:29.193 回答
3

我看不到您在哪里使用对 mActivity 的引用。但不要引用它。始终使用 getActivity,因为可以在方向更改后重新创建 Activity。此外,永远不要通过 setter 或分配来设置片段的字段,总是使用 Bundle 和 Arguments

实例化新 Android 片段的最佳实践

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

理解 Fragment 的 setRetainInstance(boolean)

于 2013-04-01T13:43:19.610 回答
2

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

于 2013-05-14T13:13:39.047 回答
0

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

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

在改变方向时,onAttach 为您提供对上下文的新引用,如果您想引用活动,您可以将上下文类型转换为活动。

于 2017-08-30T18:04:16.253 回答
0

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

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

在整个片段中传递这个 mContext

于 2018-10-13T09:14:37.833 回答
-1

如果您不... 应用程序类setRetainInstance(true)onCreate的集合List<Object>, Vector<Object>将变为空。确保你setRetainInstance(true)让他们活着。

于 2013-08-29T01:50:07.727 回答