当我更改设备的方向时,我在使用标签和片段重新加载活动时出现问题。
情况如下:
我的活动在操作栏中有3个标签。每个选项卡在主视图中的FrameLayout
中加载不同的片段。如果我不改变设备的方向,一切正常。但是,当我这样做时,Android会尝试初始化当前选定的片段两次,从而产生以下错误:
E/AndroidRuntime(2022): Caused by: android.view.InflateException: Binary XML file line #39: Error inflating class fragment
以下是产生错误的步骤序列:
Activity.onCreate()
内我将第一个标签添加到操作栏。当我这样做时,会自动选择此选项卡。它可能代表未来的问题,但我现在不介意。调用onTabSelected
并创建并加载第一个片段的新实例(参见下面的代码)。ActionBar.selectTab(myTab)
选择Tab nr 2。onTabUnselected()
,然后第二个选项卡会调用onTabSelected()
。此序列替换片段2的实例的当前片段(请参阅下面的代码)。Fragment.onCreateView()
,片段布局膨胀。onCreate()
然后调用onCreateView()
,当我尝试(第二次)对布局进行充气时会产生异常。显然问题是Android正在初始化片段两次,但我不知道为什么。
当我自动加载活动时,我没有尝试选择第二个标签,但是第二个片段无论如何都被初始化并且没有显示(因为我没有选择它的标签)。
我发现了这个问题:Android Fragments recreated on orientation change
用户询问的基本上和我一样,但我不喜欢所选的答案(它只是一个workaroud)。必须有一些方法可以在没有android:configChanges
技巧的情况下使其工作。
如果不清楚,我想知道如何阻止片段的重新创建或避免它的双重初始化。很高兴知道为什么会发生这种情况。 :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
}
}
片段的代码无关紧要,它只返回onCreateView()
方法覆盖的膨胀视图。
答案 0 :(得分:2)
我得到了一个简单的答案:
只需将setRetainInstance(true);
添加到片段的onAttach(Activity activity)
或onActivityCreated(Bundle savedInstanceState)
即可。
这两个是片段类中的回调。
基本上,setRetainInstance(true)
的作用是:
当它经历时,它会保持片段的状态:
onPause();
onStop();
无论Activity通过什么,它都会维护片段的实例。 问题可能是,如果碎片太多,可能会给系统带来压力。
希望它有所帮助。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
setRetainInstance(true);
}
一如既往地打开校正。此致,Edward Quixote。
答案 1 :(得分:1)
似乎在旋转屏幕并重新启动应用程序时,它会通过调用Fragment类的默认构造函数来重新创建每个Fragment。
答案 2 :(得分:0)
我的代码有点不同,但我相信我们的问题是一样的。
在onTabSelected
我没有使用替换,我在第一次创建片段时使用add,如果不是,则使用attach。在onTabUnselected我使用detach。
问题是当视图被破坏时,我的片段被附加到FragmentManager
并且永远不会被破坏。为了解决这个问题,我在onSaveInstanceBundle上实现了从FragmentManager
分离片段。
代码是这样的:
FragmentTransition ft = getSupportFragmentManager().begin();
ft.detach(myFragment);
ft.commit();
在第一次尝试中,我将该代码放在onDestroy
中,但我得到一个异常,告诉我在onSaveInstanceBundle
之后无法执行此操作,因此我将代码移至{{ 1}}并且一切正常。
很抱歉,但我工作的地方不允许我将代码放在StackOverflow上。这是我从代码中记得的。随意编辑答案以添加代码。
答案 3 :(得分:0)
我遇到了同样的问题并使用了以下解决方法:
在片段的onCreateView开头:
if (mView != null) {
// Log.w(TAG, "Fragment initialized again");
((ViewGroup) mView.getParent()).removeView(mView);
return mView;
}
// normal onCreateView
mView = inflater.inflate(R.layout...)
答案 4 :(得分:0)
我认为这是一种避免重新膨胀片段根视图的简单方法:
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
}
答案 5 :(得分:0)
我认为你面对的是我所面对的。我有一个json的线程下载程序,它从onCreate()
开始,每次我改变方向时调用线程并触发下载。我使用onSaveInstance()
和onRestoreInstance()
修复此问题,以便在列表中传递json响应,同时检查列表是否为空,因此不需要额外下载。
我希望这会给你一个提示。
答案 6 :(得分:0)
添加
android:configChanges="orientation|screenSize"
在清单文件中
答案 7 :(得分:0)
要保护重新创建的活动,请尝试在“活动”代码中添加configChanges
(在清单中),例如:
android:configChanges="keyboardHidden|orientation|screenSize"
答案 8 :(得分:0)
我使用下面的代码解决了这个问题。
private void loadFragment(){
LogUtil.l(TAG,"loadFragment",true);
fm = getSupportFragmentManager();
Fragment hf = fm.findFragmentByTag("HOME");
Fragment sf = fm.findFragmentByTag("SETTING");
if(hf==null) {
homeFragment = getHomeFragment();// new HomeFragment();
settingsFragment = getSettingsFragment();// new Fragment();
fm.beginTransaction().add(R.id.fm_place, settingsFragment, "SETTING").hide(settingsFragment).commit();
fm.beginTransaction().add(R.id.fm_place, homeFragment, "HOME").commit();
activeFragment = homeFragment;
}else{
homeFragment = hf;
settingsFragment = sf;
activeFragment = sf;
}
}
在 OnCreate() 中启动这个方法;