如何防止创建相同片段的 2 个实例?
Posted
技术标签:
【中文标题】如何防止创建相同片段的 2 个实例?【英文标题】:How to prevent creating 2 instances of same fragment? 【发布时间】:2021-11-12 03:39:36 【问题描述】:我有一个包含 MasterFragment 的活动 (MainActivity),其中包含一个 Viewpager,其中 FragmentA 和 FragmentB 处于纵向屏幕方向。
在横向模式下,viewpager 仅包含分屏左侧的 FragmentA,右侧包含 FragmentB。
所以基本上 FragmentB 在横向模式下移动到 viewpager 的右侧。
虽然 FragmentB 每次轮换只显示一次,但轮换后会同时创建两个实例。
问题是 FragmentB 实际上是一张地图,我需要防止同时创建 2 个实例。我需要在创建下一个实例之前销毁第一个实例。
FragmentStateManager 在 MainActivity 中调用 setContentView 时会重新创建 FragmentB。
如何防止这种情况发生?
一种解决方案是在 MainActivity 中使用 super.onCreate(null),但这显然是矫枉过正。
如何防止在 ViewPager2 中重新创建片段?
另一种解决方案是使用重新创建的片段实例并将其从 viewpager 移动到 framlayout,反之亦然。我该如何移动它?
MasterFragment.java
public class MasterFragment extends Fragment
NewPagerAdapter mSectionsPagerAdapter;
ViewPager2 mViewPager;
boolean mSplitView;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
View view = inflater.inflate(R.layout.masterfragment, container, false);
if (isLandScape())
mSplitView = true;
getChildFragmentManager().beginTransaction().replace(R.id.container, new FragmentB(), FragmentB.TAG).commit();
else if (isLandScape())
LinearLayout masterlayout = view.findViewById(R.id.masterlayout);
masterlayout.removeViewAt(1);
mViewPager = view.findViewById(R.id.pager);
mSectionsPagerAdapter = new NewPagerAdapter(getChildFragmentManager(), getLifecycle());
mViewPager.setOffscreenPageLimit(7);
mViewPager.setAdapter(mSectionsPagerAdapter);
return view;
public boolean isLandScape()
int orientation = getResources().getConfiguration().orientation;
return orientation == Configuration.ORIENTATION_LANDSCAPE;
public boolean backOnePage()
if(mViewPager == null)
return false;
int page = mViewPager.getCurrentItem();
if(page > 0)
mViewPager.setCurrentItem(page - 1);
return true;
return false;
public void viewFragmentB()
if(!mSplitView)
mViewPager.setCurrentItem(1);
public class NewPagerAdapter extends FragmentStateAdapter
public NewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle)
super(fragmentManager, lifecycle);
@NonNull
@Override
public Fragment createFragment(int position)
if(position == 0)
return new FragmentA();
return new FragmentB();
@Override
public int getItemCount()
return mSplitView ? 1 : 2;
masterfragment.xml(纵向)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:background="#ff000000"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_
android:layout_
android:layout_weight="1.0"
/>
</LinearLayout>
masterfragment.xml(横向)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/masterlayout"
android:layout_
android:layout_
android:layout_alignParentEnd="true"
android:background="#ff000000">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_
android:layout_
android:layout_weight="0.5"
/>
<FrameLayout
android:id="@+id/container"
android:layout_
android:layout_
android:layout_weight="0.5"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends FragmentActivity
MasterFragment mMasterFragment;
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
Fragment fragment = savedInstanceState != null
? getSupportFragmentManager().getFragment(savedInstanceState, "MasterFragment")
: null;
mMasterFragment = fragment instanceof MasterFragment
? (MasterFragment)fragment
: (MasterFragment)getSupportFragmentManager().findFragmentById(R.id.masterfragment);
@Override
public void onBackPressed()
if(mMasterFragment != null && mMasterFragment.backOnePage())
return;
super.finish();
片段A
public class FragmentA extends Fragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
View view = inflater.inflate(R.layout.fragment_home, container, false);
view.findViewById(R.id.title).setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
MasterFragment.getInstance().viewFragmentB();
);
return view;
片段B
public class FragmentB extends Fragment
public static String TAG = "OrderFragment";
static int COUNTER;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
View view = inflater.inflate(R.layout.fragment_crew, container, false);
COUNTER++; // COUNTER BECOMES 2
return view;
@Override
public void onDestroy()
COUNTER--;
super.onDestroy();
main.xml
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.mobile.MasterFragment"
android:id="@+id/masterfragment"
android:layout_
android:layout_/>
【问题讨论】:
可以分享一下主布局吗? 我添加了 main.xml 【参考方案1】:我认为你的问题是你总是在做 new FragmentB()
或 new Fragment...()
而不是检查它是否已经在 fragmentManager 中。
你必须做类似的事情(请原谅我的 kotlin 伪代码)
var fragmentB = fragmentManager.findFragmentByTag("FragB")
if fragmentB == null
fragmentB = // create new instance
fragmentManager.replace(..., fragmentB, "FragB") //use the same tag you'll use later to search for it
【讨论】:
ViewPager 使用的 'FragmentStateAdapter' 根据其在 viewpager 中的位置,将 FragmentB 添加到带有标签“f0”、“f1”的 FragmentManager。我试图在旋转到横向时找到该实例并使用它而不是新实例,但片段确实出现了(右半窗格为黑色)。 我的意思是“没有出现” 我认为解决方案是在创建另一个实例之前从片段管理器中删除片段 我已经有一段时间没有使用带有这些适配器的 VPager,所以没有办法覆盖标签吗? 那将是非常骇人听闻的。我认为不可能重用片段,我宁愿确保 FragmentStateManager 不会重新创建 FragmentB【参考方案2】:您需要在要保留的片段中使用setRetainInstance
,而不是 Activity 配置更改。
Source
编辑
感谢@martin 指出setRetainInstance
已被弃用。
根据我的理解,防止片段重新创建超过配置更改的要求是无法实现的。我建议按照Android Doc's 建议通过视图模型实例维护片段状态
【讨论】:
setRetainInstance
已被标记为已弃用,这不是一个好主意。 (在 API 级别 28 中已弃用)
setRetainInstance 已被弃用。我不是试图保留同一个实例,而是试图防止同时创建 2 个实例(onCreateView 被调用 2 次,而两者之间没有 onDestroy)
FragmentA 一次只创建一次,尽管它在 viewpager 中以纵向和横向的形式出现。
原因是,在横向旋转期间,您将在 FrameLayout 容器中添加一个新的 FragmentB 实例来显示它。同时,您的 MasterFragment 再次被重新创建,隐式调用 FragmentB 两次。
恐怕我们无法阻止重新创建某个片段。我宁愿将 Fragment 状态保存在 viewmodel 实例中并在重新创建时恢复它。我同意@Martin + 这个android-review.googlesource.com/c/platform/frameworks/support/+/…【参考方案3】:
看来我需要回答我自己的问题。
正如一些人提到的,Android 平台不直接支持这一点。
一些可能性是:
MainActivity 中的 super.onCreate(null)(这将禁用所有状态恢复)
编写自定义viewpager/adapter,省略处于保存状态的片段(大量工作)
旋转后,将片段从 Viewpager 移到 Framelayout 或从 Framelayout 移出,但这需要客户 viewpager/adapter 允许在不破坏片段视图的情况下移除片段视图(请参阅移动视图 Android Fragment - move from one View to another?),这可能是很多工作
【讨论】:
以上是关于如何防止创建相同片段的 2 个实例?的主要内容,如果未能解决你的问题,请参考以下文章
如何防止片段在活动重新创建时触发 onCreate onCreateView
UIScrollView addSubView:如何防止重新添加相同的 UILabel 实例?