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

时间:2012-06-25 16:03:23

标签: android tabs android-fragments device-orientation

当我更改设备的方向时,我在使用标签和片段重新加载活动时出现问题。

情况如下:

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

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

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

  1. 我加载活动,选择标签nr 2.并更改设备的方向。
  2. Android会破坏活动和tab nr 2加载的片段实例(从现在开始,'片段2')。然后它继续创建活动和片段的新实例。
  3. Activity.onCreate()内我将第一个标签添加到操作栏。当我这样做时,会自动选择此选项卡。它可能代表未来的问题,但我现在不介意。调用onTabSelected并创建并加载第一个片段的新实例(参见下面的代码)。
  4. 我添加了所有其他标签而没有触发任何事件,这很好。
  5. 我致电ActionBar.selectTab(myTab)选择Tab nr 2。
  6. 第一个选项卡会调用
  7. onTabUnselected(),然后第二个选项卡会调用onTabSelected()。此序列替换片段2的实例的当前片段(请参阅下面的代码)。
  8. 接下来,在Fragment 2实例上调用Fragment.onCreateView(),片段布局膨胀。
  9. 这是问题所在。 Android会在片段实例ONCE AGAIN上调用onCreate()然后调用onCreateView(),当我尝试(第二次)对布局进行充气时会产生异常。
  10. 显然问题是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()方法覆盖的膨胀视图。

9 个答案:

答案 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() 中启动这个方法;

相关问题