当方向更改时使用选项卡重新加载活动时,Fragment会初始化两次

当我改变我的设备的方向时,我有问题重新加载与选项卡和片段的活动。

情况如下:

我有一个活动,在操作栏中有3个选项卡。 每个选项卡在主视图中的FrameLayout中加载一个不同的片段。 一切工作正常,如果我不改变设备的方向。 但是当我这样做的Android试图初始化当前选定的片段两次产生以下错误:

 E/AndroidRuntime(2022): Caused by: android.view.InflateException: Binary XML file line #39: Error inflating class fragment 

以下是产生错误的步骤顺序:

  1. 我加载活动,select选项卡nr 2.并更改设备的方向。
  2. Android破坏活动和由标签nr 2加载的片段的实例(从现在开始,“片段2”)。 然后继续创build活动和片段的新实例。
  3. 里面Activity.onCreate()我将第一个选项卡添加到操作栏。 当我这样做,这个标签会自动select。 这可能是未来的一个问题,但我现在不介意。 onTabSelected被调用,第一个片段的新实例被创build和加载(见下面的代码)。
  4. 我添加所有其他标签没有任何事件被触发,这是好的。
  5. 我调用ActionBar.selectTab(myTab)来selectTab nr 2。
  6. onTabUnselected()被调用第一个选项卡,然后onTabSelected()第二个选项卡。 这个序列代替片段2的实例的当前片段(见下面的代码)。
  7. 接下来, Fragment.onCreateView()在Fragment 2实例上被调用,并且碎片布局变得膨胀。
  8. 这是问题。 Android在片段实例ONCE AGAIN上调用onCreate()然后onCreateView() ,当我尝试膨胀(第二次)布局时,会产生exception。

显然问题是Android正在初始化片段两次,但我不知道为什么。

我尝试没有select第二个选项卡,当我reaload活动,但第二个片段无论如何得到初始化,它不显示(因为我没有select其选项卡)。

我发现这个问题: Android的碎片重新定位改变

用户基本上和我一样问,但我不喜欢所选的答案(这只是一个工作)。 必须有一些方法来让这个工作没有android:configChanges技巧。

如果不清楚,我想知道如何防止重新创build片段或避免重新初始化它。 这将是很高兴知道为什么这也发生。 :P

这是相关的代码:

 public class MyActivity extends Activity implements ActionBar.TabListener { private static final String TAG_FRAGMENT_1 = "frag1"; private static final String TAG_FRAGMENT_2 = "frag2"; private static final String TAG_FRAGMENT_3 = "frag3"; Fragment frag1; Fragment frag2; Fragment frag3; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // my_layout contains a FragmentLayout inside setContentView(R.layout.my_layout); // Get a reference to the fragments created automatically by Android // when reloading the activity FragmentManager fm = getFragmentManager(); this.frag1 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_1); this.frag2 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_2); this.frag3 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_3) ActionBar actionBar = getActionBar(); // snip... // This triggers onTabSelected for the first tab actionBar.addTab(actionBar.newTab() .setText("Tab1").setTabListener(this) .setTag(MyActivity.TAG_FRAGMENT_1)); actionBar.addTab(actionBar.newTab() .setText("Tab2").setTabListener(this) .setTag(MyActivity.TAG_FRAGMENT_2)); actionBar.addTab(actionBar.newTab() .setText("Tab3").setTabListener(this) .setTag(MyActivity.TAG_FRAGMENT_3)); Tab t = null; // here I get a reference to the tab that must be selected // snip... // This triggers onTabUnselected/onTabSelected ab.selectTab(t); } @Override protected void onDestroy() { // Not sure if this is necessary this.frag1 = null; this.frag2 = null; this.frag3 = null; super.onDestroy(); } @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString()); if (curFrag == null) { curFrag = createFragmentInstanceForTag(tab.getTag().toString()); if(curFrag == null) { // snip... return; } } ft.replace(R.id.fragment_container, curFrag, tab.getTag().toString()); } @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString()); if (curFrag == null) { // snip... return; } ft.remove(curFrag); } private Fragment getFragmentInstanceForTag(String tag) { // Returns this.frag1, this.frag2 or this.frag3 // depending on which tag was passed as parameter } private Fragment createFragmentInstanceForTag(String tag) { // Returns a new instance of the fragment requested by tag // and assigns it to this.frag1, this.frag2 or this.frag3 } } 

Fragment的代码是无关紧要的,它只是返回onCreateView()方法覆盖的夸张视图。

我得到了一个简单的答案:

只需添加setRetainInstance(true); 到片段的onAttach(Activity activity)onActivityCreated(Bundle savedInstanceState) 。 这两个是在片段类callback。

所以基本上, setRetainInstance(true)做的是:它保持你的片段的状态,当它经历:

  • onPause();
  • onStop();

无论活动经历什么,它都会维护片段的实例。 问题可能在于,如果碎片太多,可能会给系统带来压力。

希望能帮助到你。

 @Override public void onAttach(Activity activity) { super.onAttach(activity); setRetainInstance(true); } 

一如既往地打开纠正。 问候,爱德华吉诃德。

看起来,当屏幕旋转,应用程序重新启动时,通过调用Fragment类的默认构造函数来重新创build每个Fragment。

我遇到了同样的问题,并使用以下解决方法:

在片段的onCreateView开始:

 if (mView != null) { // Log.w(TAG, "Fragment initialized again"); ((ViewGroup) mView.getParent()).removeView(mView); return mView; } // normal onCreateView mView = inflater.inflate(R.layout...) 

我认为这是一种避免重新扩展片段根视图的简单方法:

 private WeakReference<View> mRootView; private LayoutInflater mInflater; /** * inflate the fragment layout , or use a previous one if already stored <br/> * WARNING: do not use in any function other than onCreateView * */ private View inflateRootView() { View rootView = mRootView == null ? null : mRootView.get(); if (rootView != null) { final ViewParent parent = rootView.getParent(); if (parent != null && parent instanceof ViewGroup) ((ViewGroup) parent).removeView(rootView); return rootView; } rootView = mFadingHelper.createView(mInflater); mRootView = new WeakReference<View>(rootView); return rootView; } @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { mInflater=inflater!=null?inflater:LayoutInflater.from(getActivity()); final View view = inflateRootView(); ... //update your data on the views if needed } 

在清单文件中添加android:configChanges="orientation|screenSize"

为了保护活动重新创build尝试添加在您的活动标签(在清单) configChanges ,如:

 android:configChanges="keyboardHidden|orientation|screenSize" 

我的代码有点不同,但我相信我们的问题是一样的。

onTabSelected我没有使用replace,我使用添加的时候是第一次创build片段,并附上如果不是。 在onTabUnselected我使用detach。

问题是,当视图被破坏时,我的碎片被连接到FragmentManager并从未销毁。 为了解决这个问题,我在onSaveInstanceBundle上实现了从FragmentManager分离的片段。

代码是这样的:

 FragmentTransition ft = getSupportFragmentManager().begin(); ft.detach(myFragment); ft.commit(); 

在第一次尝试,我把代码放在onDestroy ,但我得到一个exception,告诉我,我不能做onSaveInstanceBundle ,所以我把代码移动到onSaveInstanceBundle和一切工作。

对不起,但我工作的地方不允许我把代码放在StackOverflow上。 这是我从代码中记得的。 随意编辑答案添加代码。

我想你正面临着我所面临的。 我有一个线程下载器的json在onCreate()开始,每次我改变方向的线程被调用和下载被激发。 我使用onSaveInstance()onRestoreInstance()来解决这个问题,在列表中传递json响应,结合检查列表是否为空,所以不需要额外的下载。

我希望这给你一个提示。