重新附加时片段创建/恢复重复视图

Posted

技术标签:

【中文标题】重新附加时片段创建/恢复重复视图【英文标题】:Fragment create/restore a duplicate view when re-attach 【发布时间】:2017-04-07 06:38:50 【问题描述】:

我的ViewPagerOffscreenPageLimit=2 中有6 页(易于重现我的错误)。 6 页中的所有数据均来自服务器。在onCreateView 中,我向服务器发送请求,并在从服务器获取数据时刷新 UI。

当我多次选择第一个选项卡并快速更改为最后一个选项卡时,某些寻呼机显示错误。当时我在片段中的字段mMainLayout不为空。

例如,我的第一页中有一个ListView。当页面出错时,另一个ListView在右边ListView的上方。当我尝试滚动 ListView 时,只有右侧(底部)移动了。

我知道我的响应监听器持有 mMainLayout 和其他一些视图的引用,我在方法 onCreateView 中创建了一个新的 mMainLayout,我想片段在重新附加/恢复并删除旧的(或将其从容器中删除)时使用新的 mMainLayout。但我错了。

我知道FragmentPagerAdapterinstantiateItem 中附加/重新附加片段并在destroyItem 中分离。适配器没有删除片段。适配器没有制作新的片段。 Fragment 保持视图和视图状态本身。

我从容器中删除 mMainLayout 并将我的片段中的所有视图设置为空 onDestroyView 并且在 onSaveInstanceState 中不保存任何内容。但仍然很容易重现错误。

活动:

@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    registerReceiver(receiver, new IntentFilter("com.souyidai.intent.action.log_fragment"));
    FragmentManager fragmentManager = getFragmentManager();
    mResources = getResources();
    mMainPagerAdapter = new MainPagerAdapter(fragmentManager);
    mPager = (ViewPager) findViewById(R.id.pager);
    mPager.setAdapter(mMainPagerAdapter);
    mPager.setOffscreenPageLimit(CACHE_SIZE);
    mIndicator = (TabPageIndicator) findViewById(R.id.indicator);
    mIndicator.setViewPager(mPager);
    ...


private class MainPagerAdapter extends FragmentPagerAdapter 
    public MainPagerAdapter(FragmentManager fm) 
        super(fm);
    

    @Override
    public Fragment getItem(int position) 
        MainConfig.TabItem tab = mTabs.get(position);
        String tabType = tab.getTabType();
        String code = tab.getCode();
        MainConfig.TabItem.SubTabItem subTabItem = tab.getSubitem().get(0);
        Fragment fragment = MainFragment.newInstance(code, subTabItem);
        return fragment;
    

    @Override
    public CharSequence getPageTitle(int position) 
        MainConfig.TabItem tab = mTabs.get(position);
        return tab.getTitle();
    

    @Override
    public int getCount() 
        return mTabs.size();
    

片段:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
    mContainer = container;
    mMainLayout = inflater.inflate(R.layout.fragment_main, container, false);
    ...



@Override
public void onDestroyView() 
    super.onDestroyView();
    mContainer.removeView(mMainLayout);
    mContainer = null;
    mMainLayout = null;
    mSwipeRefreshLayout = null;
    mListView = null;
    mHeaderLayout = null;
    mFooterLayout = null;
    ...

android.support.v13.app.FragmentPagerAdapter

@Override
public Object instantiateItem(ViewGroup container, int position) 
    if (mCurTransaction == null) 
        mCurTransaction = mFragmentManager.beginTransaction();
    

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) 
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
     else 
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    
    if (fragment != mCurrentPrimaryItem) 
        FragmentCompat.setMenuVisibility(fragment, false);
        FragmentCompat.setUserVisibleHint(fragment, false);
    

    return fragment;

我发现了这个:

Android fragment onCreateView creating duplicate views on top of each other

但我只在FragmentPagerAdapter.getItem 中创建新片段。所以我们是不同的。

如果 mMainLayout 在方法 onCreateView 中不为空,我会通过删除 mMainLayout 的所有子级来解决此问题。然后一切都很好。但是我仍然对为什么会出现这个错误感到困惑?

我做了一些测试。 1.尝试添加片段两次,应用程序崩溃和日志显示:java.lang.IllegalStateException: Fragment already added: ... 2.尝试附加一个fragment两次,系统不调用Fragment.onCreateFragment.onCreateView

【问题讨论】:

请添加您的代码 使用片段的'setRetainInstance(true)' api 如果你想保留你的片段状态。在第二次加载相同的片段时,如果它在内存中,它将从先前的实例加载 UI 组件,否则将创建新的片段。因此,您不需要在 ondestroyview 方法中将所有 UI 组件设为 null。 @keyur9779 我不想保留我的片段状态。但看起来那个片段仍然从以前的状态恢复。 为什么要覆盖instanceiteItem?在 FragmentPagerAdapter 或 FragmentStatePagerAdapter 的情况下,您只需要覆盖 getItem(Along with getCount()),以便从片段列表中返回特定位置的片段。 【参考方案1】:

这种情况是由于instantiateItem()的自定义实现而发生的 我注意到一件事。您正在搞乱检查是否已添加片段。

@Override
public Object instantiateItem(ViewGroup container, int position) 
    if (mCurTransaction == null) 
        mCurTransaction = mFragmentManager.beginTransaction();
    

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) 
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        //make sure you are not attaching already added fragment
        //try with comment and uncomment two lines below
        //if(!fragment.isAdded())
        //mCurTransaction.attach(fragment);
     else 
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    
    if (fragment != mCurrentPrimaryItem) 
        FragmentCompat.setMenuVisibility(fragment, false);
        FragmentCompat.setUserVisibleHint(fragment, false);
    

    return fragment;

不知道你为什么要为mMainLayout创建类成员字段

//try to go with default
View view = inflater.inflate(R.layout.fragment_main, container, false);

推荐你学习this

【讨论】:

如果我添加了两次片段,应用程序崩溃并且日志显示:java.lang.IllegalStateException: Fragment already added: ... 你是对的,没有必要持有mMainLayout的引用。但我必须持有一些视图参考,以便在从服务器获取数据时更新视图。 我的应用程序没有崩溃,所以我认为调用 fragment.isAdded 没有帮助【参考方案2】:

您可以尝试使用代码,我遇到的类似问题已通过以下更改得到解决。在这种情况下,无需将onDestroyView 中的所有引用设为空。在这种情况下,您需要始终在代码中的其他任何地方使用这些引用之前进行空检查。

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
    if(mMainLayout == null)
    
      mMainLayout = inflater.inflate(R.layout.fragment_main, container,false);
...
     
    return mMainLayout;

mMainlayout不为null时,表示你的fragment实例已经有一个mMainLayout的实例,并且已经添加到ViewGroup container,不需要再添加。当您再次将相同的视图添加到同一个容器时,您将面临问题。

【讨论】:

我最后在我的问题中提到我解决了和你一样的问题。我想知道为什么视图/片段被添加了两次。 由于您的 ViewGroup 容器中已经添加了相同的布局,因此我们需要进行空检查以确保不添加两次。

以上是关于重新附加时片段创建/恢复重复视图的主要内容,如果未能解决你的问题,请参考以下文章

android如何跨片段分离/附加保留视图状态

如何在屏幕方向更改时附加片段?

活动重新创建意图附加内容为空

我可以立即在其他地方移动片段而不重新创建任何内容

OnDetach/onAttach 片段重新创建片段活动

我可以在不重新创建任何内容的情况下立即将片段移动到其他地方吗?