【问题标题】:Fragment gets initialized twice when reloading activity with tabs when orientation changes当方向改变时使用选项卡重新加载活动时,片段被初始化两次
【发布时间】:2012-06-25 16:03:23
【问题描述】:

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

情况如下:

我有一个在操作栏中有 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 会销毁由选项卡 nr 2 加载的片段的活动和实例(从现在开始,“片段 2”)。然后它继续创建 Activity 和 Fragment 的新实例。
  3. Activity.onCreate() 内部,我将第一个选项卡添加到操作栏。当我这样做时,此选项卡会自动选择。它可能代表未来的问题,但我现在不介意。调用onTabSelected 并创建并加载第一个片段的新实例(参见下面的代码)。
  4. 我添加了所有其他选项卡而没有触发任何事件,这很好。
  5. 我致电 ActionBar.selectTab(myTab) 选择 Tab nr 2。
  6. 第一个选项卡调用onTabUnselected(),然后第二个选项卡调用onTabSelected()。此序列替换片段 2 实例的当前片段(参见下面的代码)。
  7. 接下来,在 Fragment 2 实例上调用 Fragment.onCreateView(),并且片段布局得到扩展。
  8. 这就是问题所在。 Android 在片段实例 ONCE AGAIN 上调用 onCreate() 然后 onCreateView(),当我尝试扩展(第二次)布局时会产生异常。

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

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

我发现了这个问题:Android Fragments recreated on orientation 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
    }
}

片段的代码无关紧要,它只是在onCreateView() 方法覆盖上返回一个膨胀的视图。

【问题讨论】:

  • 只是一个建议...如果您缩小帖子的大小,您更有可能得到答案:P。尽管我当然很欣赏您包含的详细信息...我建议您包含一些易于阅读并吸引人们注意力的摘要(在您的帖子开头的某个地方)。
  • 另外,使用inline code粗体(但不要太多),甚至 html 标题 (

    ) 都可以帮助组织您的帖子。 ..但不要滥用它太多:P
  • 需要一些代码,最好是 onTabSelectedonTabUnselected 方法
  • 感谢您的建议。我考虑了帖子的长度,但我想尽可能详细地说明,因为除了最后一部分之外,一切似乎都运行良好。

标签: android tabs android-fragments device-orientation


【解决方案1】:

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

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

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

  • onPause();
  • onStop();

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

希望对你有帮助。

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

setRetainInstance(true);

}

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

【讨论】:

  • 不适合我,片段不断被创建两次,所以即使我将小部件的状态保存为一个包,它也会在第二次重新加载时丢失。
  • 我解决了我自己的问题,虽然我不知道我的解决方案是否适用于这个问题:stackoverflow.com/a/26523274/986188
【解决方案2】:

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

【讨论】:

  • 是的.. 但是你是怎么克服的?正确的做法是什么?
【解决方案3】:

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

在片段的 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】:

    我认为这是避免重新膨胀片段的根视图的一种万无一失的方法:

    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】:

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

      【讨论】:

        【解决方案6】:

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

        android:configChanges="keyboardHidden|orientation|screenSize"
        

        【讨论】:

          【解决方案7】:

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

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

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

          代码是这样的:

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

          在第一次尝试中,我将该代码放在onDestroy 中,但我收到一个异常,告诉我在onSaveInstanceBundle 之后我不能这样做,所以我将代码移到onSaveInstanceBundle 并且一切正常.

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

          【讨论】:

            【解决方案8】:

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

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

            【讨论】:

              【解决方案9】:

              我用下面的代码解决了这个问题。

              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() 中启动该方法;

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-09-30
                • 2016-04-22
                • 1970-01-01
                • 1970-01-01
                • 2011-09-27
                • 1970-01-01
                相关资源
                最近更新 更多