19

当我更改设备的方向时,我在重新加载带有选项卡和片段的活动时遇到问题。

情况如下:

我有一个在操作栏中有 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. onTabUnselected()为第一个选项卡调用,然后onTabSelected()为第二个选项卡调用。此序列替换片段 2 实例的当前片段(参见下面的代码)。
  7. 接下来,Fragment.onCreateView()在 Fragment 2 实例上调用,并且片段布局变得膨胀。
  8. 这是问题所在。Android 调用onCreate(),然后onCreateView()在片段实例 ONCE AGAIN 上调用,当我尝试膨胀(第二次)布局时会产生异常。

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

当我重新加载活动时,我尝试不选择第二个选项卡,但是第二个片段无论如何都会被初始化并且它没有显示(因为我没有选择它的选项卡)。

我发现了这个问题:Android Fragments recreated onorientation change

用户问的问题基本上和我一样,但我不喜欢选择的答案(这只是一种工作方式)。必须有某种方法可以在没有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
    }
}

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

4

9 回答 9

2

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

只需添加setRetainInstance(true);到 Fragment 的onAttach(Activity activity)onActivityCreated(Bundle savedInstanceState). 这两个是 Fragment 类中的回调。

所以基本上,setRetainInstance(true)它的作用是:当它通过时,它保持你的片段的状态:

  • onPause();
  • onStop();

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

希望能帮助到你。

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

setRetainInstance(true);

}

一如既往地开放更正。问候,爱德华·吉诃德。

于 2014-09-14T03:21:12.673 回答
1

似乎,当屏幕旋转并重新启动应用程序时,它正在通过调用 Fragment 类的默认构造函数来重新创建每个 Fragment。

于 2013-01-10T03:15:42.190 回答
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...)
于 2014-03-09T11:23:50.070 回答
0

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

onTabSelected我没有使用替换中,我在第一次创建片段时使用添加,如果不是,则附加。在 onTabUnselected 我使用分离。

问题是当视图被销毁时,我的 Fragment 被附加到FragmentManager并且从未被销毁。为了解决这个问题,我在 onSaveInstanceBundle 上实现了从FragmentManager.

代码是这样的:

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

在第一次尝试中,我将该代码放入 中onDestroy,但我收到一个异常,告诉我在 之后我无法执行此操作onSaveInstanceBundle,因此我将代码移至onSaveInstanceBundle并且一切正常。

抱歉,我工作的地方不允许我将代码放在 StackOverflow 上。这是我从代码中记住的。随意编辑答案以添加代码。

于 2013-06-03T20:50:32.977 回答
0

我想你正面临着我所面临的。我有一个 json 的线程下载器,它从 开始onCreate(),每次我更改方向时,都会调用线程并触发下载。我使用onSaveInstance()onRestoreInstance()在列表中传递 json 响应来解决此问题,并结合检查列表是否不为空,因此不需要额外的下载。

我希望这能给你一个提示。

于 2014-08-04T11:06:00.363 回答
0

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

android:configChanges="keyboardHidden|orientation|screenSize"
于 2014-12-02T09:44:53.413 回答
0

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

于 2014-11-20T12:01:21.290 回答
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
}
于 2014-03-19T09:13:20.520 回答
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() 中启动此方法;

于 2021-04-24T16:21:42.250 回答