屏幕方向改变后,每次Fragment都会重新创建,无法恢复状态。

8
更新:事实证明问题来自其他地方。感谢@Luksprog指出我忽略的问题。
  1. 该项目使用Android Studio的导航抽屉模式创建。抽屉在NavigationDrawerFragment类中实现。
  2. 当选择抽屉中的特定项时,将添加包含视图页的片段。该代码是在我的主活动中实现的。
  3. 当屏幕旋转时,将调用NavigationDrawerFragment的onCreate()方法,以保留上次选择的项。
  4. 问题就在这里-在重新创建时,NavigationDrawerFragment将再次调用selectItem(),这会触发我的菜单项选中处理程序。这会导致由Android恢复的ListFragment。

可以通过检查我的菜单选择处理程序代码中的活动菜单项来防止这种情况发生。


当活动由于任何原因重新创建时,例如方向更改,我希望保留 ViewPager 的最后浏览页面索引。

ViewPager 位于附加到活动的 Fragment(名为 ListFragment)中。我正在使用兼容库,因此该片段是 android.support.v4.app.Fragment 的子类。

我认为可以通过覆盖 onSaveInstanceState() 方法并在 onCreate() 中添加适当的逻辑来完成此操作,如 doc 中所述:

为了正确处理重启,重要的是您的活动通过正常的 Activity 生命周期恢复其先前的状态,在其中 Android 在销毁您的活动之前调用 onSaveInstanceState(),以便您可以保存有关应用程序状态的数据。然后可以在 onCreate() 或 onRestoreInstanceState() 中恢复状态。

但对于片段,情况似乎不同。当我从这个 ListFragment 导航到另一个活动并按下“返回”时,可以正确地恢复页面索引。但是当我旋转设备时,页面索引会丢失。

我添加了一些日志来查看问题。从日志中,我发现虽然ListFragment(我称之为ListFragment A)的onSaveInstanceState()被正确调用,但是这个特定的Fragment类不再显示在活动中。当方向改变并且活动被重新创建时,Android会调用onSaveInstanceState(),然后调用onDetach()来分离此片段。然后Android创建了一个新的ListFragment(我称之为ListFragment B),并将其附加到新的旋转活动中。这个ListFragment B有一个空的savedInstanceState传递给构造函数,因此上一页索引(以及Fragment A的任何配置)都会丢失。

实际上,每次屏幕旋转发生时,都会创建一个新的ListFragment实例,但似乎旧的实例不会被销毁。当我旋转设备时,会看到类似下面的日志:

D/ListFragment﹕ [1110257048] onSaveInstanceState() called, storing last page index 3
D/ListFragment﹕ [1109835992] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108826176] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108083096] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1106541040] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108316656] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1109134136] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108630992] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108592888] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1109729064] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1110257048] onDestroy()
D/ListFragment﹕ [1110257048] onDetach()
D/ListFragment﹕ [1109835992] onDestroy()
D/ListFragment﹕ [1109835992] onDetach()
D/ListFragment﹕ [1108826176] onDestroy()
D/ListFragment﹕ [1108826176] onDetach()
D/ListFragment﹕ [1108083096] onDestroy()
D/ListFragment﹕ [1108083096] onDetach()
D/ListFragment﹕ [1106541040] onDestroy()
D/ListFragment﹕ [1106541040] onDetach()
D/ListFragment﹕ [1108316656] onDestroy()
D/ListFragment﹕ [1108316656] onDetach()
D/ListFragment﹕ [1109134136] onDestroy()
D/ListFragment﹕ [1109134136] onDetach()
D/ListFragment﹕ [1108630992] onDestroy()
D/ListFragment﹕ [1108630992] onDetach()
D/ListFragment﹕ [1108592888] onDestroy()
D/ListFragment﹕ [1108592888] onDetach()
D/ListFragment﹕ [1109729064] onDestroy()
D/ListFragment﹕ [1109729064] onDetach()
D/ListFragment﹕ [1110903656] onAttach()
D/ListFragment﹕ [1110903656] onCreate()
D/ListFragment﹕ [1110903656] savedInstanceState is not NULL.
D/ListFragment﹕ [1110903656] Retrieving last page index 3
D/ListFragment﹕ [1110905248] onAttach()
D/ListFragment﹕ [1110905248] onCreate()
D/ListFragment﹕ [1110905248]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110905248]   Retrieving last page index 0
D/ListFragment﹕ [1110906440] onAttach()
D/ListFragment﹕ [1110906440] onCreate()
D/ListFragment﹕ [1110906440]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110906440]   Retrieving last page index 0
D/ListFragment﹕ [1110907632] onAttach()
D/ListFragment﹕ [1110907632] onCreate()
D/ListFragment﹕ [1110907632]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110907632]   Retrieving last page index 0
D/ListFragment﹕ [1110908824] onAttach()
D/ListFragment﹕ [1110908824] onCreate()
D/ListFragment﹕ [1110908824]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110908824]   Retrieving last page index 0
D/ListFragment﹕ [1110910016] onAttach()
D/ListFragment﹕ [1110910016] onCreate()
D/ListFragment﹕ [1110910016]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110910016]   Retrieving last page index 0
D/ListFragment﹕ [1110911208] onAttach()
D/ListFragment﹕ [1110911208] onCreate()
D/ListFragment﹕ [1110911208]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110911208]   Retrieving last page index 0
D/ListFragment﹕ [1110912400] onAttach()
D/ListFragment﹕ [1110912400] onCreate()
D/ListFragment﹕ [1110912400]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110912400]   Retrieving last page index 0
D/ListFragment﹕ [1110913592] onAttach()
D/ListFragment﹕ [1110913592] onCreate()
D/ListFragment﹕ [1110913592]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110913592]   Retrieving last page index 0
D/ListFragment﹕ [1110914784] onAttach()
D/ListFragment﹕ [1110914784] onCreate()
D/ListFragment﹕ [1110914784]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110914784]   Retrieving last page index 0
D/HomeActivity﹕ fragment updated
D/ListFragment﹕ [1110914784] onCreateView()
D/ListFragment﹕ [1111031048] onAttach()
D/HomeActivity﹕ Fragment attached.
D/ListFragment﹕ [1111031048] onCreate()
D/ListFragment﹕ [1111031048]   savedInstanceState is NULL.
D/ListFragment﹕ [1111031048] onCreateView()
D/ListFragment﹕ [1111031048] onResume(), restoring page index 0

这是我旋转屏幕大约10次后的日志。标签中的数字是类的hashCode()。上面的内容显示了即使它们被最新的(1111031048)替换,以前创建的片段的onSaveInstanceState()onCreate()仍然被调用。
请注意,我在片段类中没有调用setRetainInstance()。事实上,我尝试了setRetainInstance(false)setRetainInstance(true),但没有改变任何东西。
我做错了什么吗?我可以理解为什么需要重新创建ListFragment,但为什么savedInstanceState为空?如果这是预期的行为,那么解决我的需求的正确方法是什么,即在配置更改时保留页面索引?
将页面索引设置为静态类变量应该是可能的,但我不确定它是否实际解决了问题,还是只是隐藏了它(因为我在上面的日志中闻到了内存泄漏)。

你是否在所有重写的活动和片段方法(如onCreateonSaveInstanceState)等中调用super - Ari
你确定你的包含ViewPager的碎片在旋转后不会被你的代码错误地再次添加,并替换保存的碎片吗? - user
1
@Luksprog 您说得完全正确。我太专注于片段,忽略了这部分。我会更新我的帖子。非常感谢! - Edmund Tam
你应该发布一个答案并接受它(如果你得到了一个合适的答案,也可以针对其他问题),这样问题就会被标记为已解决。 - user
谢谢。我不确定像这样的愚蠢错误是否适合我自己回答问题(并将其标记为答案),但我遵循了您的建议,希望它能尽快帮助到某人。 - Edmund Tam
5个回答

10

即使答案已经被接受,让我再澄清一下:这个"问题"在于Android Studio模板中。正如Edmund指出的那样,问题出现在导航抽屉调用菜单时被重新创建,从而重新调用片段。 为了解决这个问题,我对NavigationDrawerFragment.java文件进行了轻微修改,如Android Studio所建议的。 原始内容:

    @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Read in the flag indicating whether or not the user has demonstrated awareness of the
    // drawer. See PREF_USER_LEARNED_DRAWER for details.
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);

    if (savedInstanceState != null) {
        mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
        mFromSavedInstanceState = true;
    }

    // Select either the default item (0) or the last selected item.
    selectItem(mCurrentSelectedPosition);

}
新的内容:
    @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Read in the flag indicating whether or not the user has demonstrated awareness of the
    // drawer. See PREF_USER_LEARNED_DRAWER for details.
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);

    if (savedInstanceState != null) {
        mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
        mFromSavedInstanceState = true;
    } else {
        // Select either the default item (0) or the last selected item.
        selectItem(mCurrentSelectedPosition);
    }
}

刚刚浪费了半天的时间才解决这个问题,希望能对其他人有所帮助。


1
这对我帮助很大。我一直在尝试找出如何防止屏幕旋转时片段的重新创建。谢谢! - Eugene H
非常感谢你。 - zaerymoghaddam
太棒了!这正是我在寻找的。我想知道为什么预构建的导航抽屉没有完全准备好配置更改。它帮了我大忙!谢谢! - crubio

5

根据问题的更新,这个问题已经解决,再次感谢@Luksprog指出我忽略的问题。

Fragment的行为实际上与Activity类相一致。

这是我遇到问题的原因:

  • 该项目使用Android Studio提供的“创建项目”向导中的导航抽屉模式创建。抽屉在NavigationDrawerFragment类中实现。
  • 当选择抽屉中的特定项时,添加了持有视图页的片段。该代码是由我的主页活动实现的。
  • 当屏幕旋转时,NavigationDrawerFragmentonCreate()方法被调用,保留上次选择的项。
  • 这里出了问题 - 在重新创建时,NavigationDrawerFragment会再次调用selectItem(),从而触发我的菜单项选中处理程序。这导致ListFragment被替换。

可以通过检查菜单选择处理程序代码中的活动菜单项或禁用那个selectItem()调用来防止此问题。


2

您不需要处理savedInstanceState。因为所有的片段都存储在片段管理器中(就像片段数组一样),当方向改变时,活动会被销毁,但是片段管理器仍然保留之前添加的所有片段。所以您只需要检查片段是否已经添加。

private void openFragment(Fragment fragment, String tag) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            Fragment existingFragment = fragmentManager.findFragmentByTag(tag);
            if (existingFragment != null) {
                fragmentTransaction.add(R.id.container, fragment, tag);
                fragmentTransaction.commit();
            }
        }

0

尝试在这里使用onCreateView(),而不是onCreate(),因为后者只适用于片段,并可能产生差异。

重写onSaveInstanceState()以存储您的状态,并在onCreateView()中使用savedInstanceState.get*()恢复它。这是我们在应用程序中的做法,应该可以工作。正如@Ari所提到的,确保调用super.onSaveInstanceState(outState)

class MyFragment extends ListFragment {

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(ARG_PAGE, page); 
        super.onSaveInstanceState(outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(savedInstanceState != null){
            if(savedInstanceState.containsKey(ARG_PAGE)){
                page = savedInstanceState.getInteger(ARG_PAGE);
            }
        }
    }
}

-2

在 AndroidManifest.xml 中添加

<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
</activity>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接