片段onCreateView和onActivityCreated调用两次

我正在开发一个使用Android 4.0 ICS和片段的应用程序。

从ICS 4.0.3(API level 15)API的演示示例应用程序考虑这个修改的示例:

public class FragmentTabs extends Activity { private static final String TAG = FragmentTabs.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); bar.addTab(bar.newTab() .setText("Simple") .setTabListener(new TabListener<SimpleFragment>( this, "mysimple", SimpleFragment.class))); if (savedInstanceState != null) { bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab")); Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number")); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); } public static class TabListener<T extends Fragment> implements ActionBar.TabListener { private final Activity mActivity; private final String mTag; private final Class<T> mClass; private final Bundle mArgs; private Fragment mFragment; public TabListener(Activity activity, String tag, Class<T> clz) { this(activity, tag, clz, null); } public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) { mActivity = activity; mTag = tag; mClass = clz; mArgs = args; // Check to see if we already have a fragment for this tab, probably // from a previously saved state. If so, deactivate it, because our // initial state is that a tab isn't shown. mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); if (mFragment != null && !mFragment.isDetached()) { Log.d(TAG, "constructor: detaching fragment " + mTag); FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); ft.detach(mFragment); ft.commit(); } } public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { Log.d(TAG, "onTabUnselected detaching fragment " + mTag); ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show(); } } public static class SimpleFragment extends Fragment { TextView textView; int mNum; /** * When creating, retrieve this instance's number from its arguments. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(FragmentTabs.TAG, "onCreate " + (savedInstanceState != null ? ("state " + savedInstanceState.getInt("number")) : "no state")); if(savedInstanceState != null) { mNum = savedInstanceState.getInt("number"); } else { mNum = 25; } } @Override public void onActivityCreated(Bundle savedInstanceState) { Log.d(TAG, "onActivityCreated"); if(savedInstanceState != null) { Log.d(TAG, "saved variable number: " + savedInstanceState.getInt("number")); } super.onActivityCreated(savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { Log.d(TAG, "onSaveInstanceState saving: " + mNum); outState.putInt("number", mNum); super.onSaveInstanceState(outState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(FragmentTabs.TAG, "onCreateView " + (savedInstanceState != null ? ("state: " + savedInstanceState.getInt("number")) : "no state")); textView = new TextView(getActivity()); textView.setText("Hello world: " + mNum); textView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb)); return textView; } } 

}

这是从运行这个例子,然后旋转手机检索输出:

 06-11 11:31:42.559: D/FragmentTabs(10726): onTabSelected adding fragment mysimple 06-11 11:31:42.559: D/FragmentTabs(10726): onCreate no state 06-11 11:31:42.559: D/FragmentTabs(10726): onCreateView no state 06-11 11:31:42.567: D/FragmentTabs(10726): onActivityCreated 06-11 11:31:45.286: D/FragmentTabs(10726): onSaveInstanceState saving: 25 06-11 11:31:45.325: D/FragmentTabs(10726): onCreate state 25 06-11 11:31:45.340: D/FragmentTabs(10726): constructor: detaching fragment mysimple 06-11 11:31:45.340: D/FragmentTabs(10726): onTabSelected attaching fragment mysimple 06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate tab: 0 06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate number: 0 06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView state: 25 06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated 06-11 11:31:45.348: D/FragmentTabs(10726): saved variable number: 25 06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView no state 06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated 

我的问题是,为什么onCreateView和onActivityCreated调用两次? 第一次与保存状态的一个捆绑和第二次与一个空的savedInstanceState?

这导致保留片段旋转状态的问题。

我也在这个问题上摸不着头脑,因为Dave的解释有点难以理解,所以我会发表我的(显然是工作的)代码:

 private class TabListener<T extends Fragment> implements ActionBar.TabListener { private Fragment mFragment; private Activity mActivity; private final String mTag; private final Class<T> mClass; public TabListener(Activity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; mFragment=mActivity.getFragmentManager().findFragmentByTag(mTag); } public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName()); ft.replace(android.R.id.content, mFragment, mTag); } else { if (mFragment.isDetached()) { ft.attach(mFragment); } } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { } } 

正如你所看到的,它几乎和Android示例一样,除了不在构造函数中分离, 而是使用replace而不是add

经过大量的头痛和试错,我发现在构造函数中find片段似乎使得double的onCreateView问题神奇地消失了(我认为当通过ActionBar.setSelectedNavigationItem()path调用onTabSelected时,它最终为null保存/恢复状态)。

好的,这是我发现的。

我不明白的是,当发生configuration更改(电话旋转)时附加到活动的所有碎片都被重新创build并添加回活动。 (这是有道理的)

TabListener构造函数中发生的事情是该选项卡被分离,如果它被发现并附加到活动。 见下文:

 mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); if (mFragment != null && !mFragment.isDetached()) { Log.d(TAG, "constructor: detaching fragment " + mTag); FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); ft.detach(mFragment); ft.commit(); } 

稍后在活动onCreate中,从保存的实例状态中select先前select的选项卡。 见下文:

 if (savedInstanceState != null) { bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab")); Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number")); } 

select该选项卡后,将重新挂接在onTabSelectedcallback中。

 public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } } 

被附加的片段是第二次调用onCreateView和onActivityCreated方法。 (第一次是当系统重新创build活动和所有附件)第一次onSavedInstanceState包将保存数据,但不是第二次。

解决方法是不要在TabListener构造函数中分离片断,只需将其附加。 (你仍然需要通过它的标签在FragmentManager中find它)。另外,在onTabSelected方法中,我检查片段在被附加之前是否被分离。 像这样的东西:

 public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mFragment == null) { mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); Log.d(TAG, "onTabSelected adding fragment " + mTag); ft.add(android.R.id.content, mFragment, mTag); } else { if(mFragment.isDetached()) { Log.d(TAG, "onTabSelected attaching fragment " + mTag); ft.attach(mFragment); } else { Log.d(TAG, "onTabSelected fragment already attached " + mTag); } } } 

这两个upvoted答案在这里显示一个导航模式NAVIGATION_MODE_TABS一个活动的解决scheme,但我有同样的问题与NAVIGATION_MODE_LIST 。 当屏幕方向改变的时候,它使我的碎片莫名其妙地失去了状态,这真的很烦人。 谢天谢地,由于他们有用的代码,我设法弄清楚了。

基本上,当使用列表导航is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's onNavigationItemSelected() is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's onCreateView() from being called twice, this initial automatic call to onNavigationItemSelected()的from being called twice, this initial automatic call to should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes onCreateView()被调用两次!

请参阅下面的onNavigationItemSelected()实现。

 public class MyActivity extends FragmentActivity implements ActionBar.OnNavigationListener { private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item"; private boolean mIsUserInitiatedNavItemSelection; // ... constructor code, etc. @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) { getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM)); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getActionBar().getSelectedNavigationIndex()); super.onSaveInstanceState(outState); } @Override public boolean onNavigationItemSelected(int position, long id) { Fragment fragment; switch (position) { // ... choose and construct fragment here } // is this the automatic (non-user initiated) call to onNavigationItemSelected() // that occurs when the activity is created/re-created? if (!mIsUserInitiatedNavItemSelection) { // all subsequent calls to onNavigationItemSelected() won't be automatic mIsUserInitiatedNavItemSelection = true; // has the same fragment already replaced the container and assumed its id? Fragment existingFragment = getSupportFragmentManager().findFragmentById(R.id.container); if (existingFragment != null && existingFragment.getClass().equals(fragment.getClass())) { return true; //nothing to do, because the fragment is already there } } getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit(); return true; } } 

我从这里借鉴了这个解决scheme的灵感。

我有一个简单的活动只携带一个片段(有时会被取代)有同样的问题。 然后我意识到我只使用onSaveInstanceState在片段(和onCreateView来检查savedInstanceState),而不是在活动。

在设备上,包含碎片的活动被重新启动,并且被调用。 在那里,我附加了所需的片段(在第一次启动时是正确的)。

在Android设备上,首先重新创build可见的片段,然后调用创build包含我的片段的活动,从而replace原来可见的片段。

为了避免这一点,我只是改变了我的活动检查savedInstanceState:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); If (savedInstanceState != null) return; // following code to attach fragment initially 

我甚至没有覆盖活动的onSaveInstanceState。

在我看来,这是因为你每次都实例化你的TabListener …所以系统正在从savedInstanceState中重新创build你的片段,然后你在onCreate中再次执行它。

你应该把它包装在一个if(savedInstanceState == null)所以只有在没有savedInstanceState时才会触发它。