
本文详解在使用 FragmentStateAdapter + ViewPager2 时,避免 Fragment 因 Tab 切换被销毁重建的核心方案,包括正确设置离屏缓存、优化 Adapter 实现及关键注意事项。
本文详解在使用 `fragmentstateadapter` + `viewpager2` 时,避免 fragment 因 tab 切换被销毁重建的核心方案,包括正确设置离屏缓存、优化 adapter 实现及关键注意事项。
FragmentStateAdapter 的设计初衷是按需创建/销毁 Fragment(尤其适用于大量页面场景),因此默认行为会在 Fragment 滑出可视范围后将其 destroy 并释放状态——这正是你遇到“切换 Tab 时 Fragment 被重新加载”的根本原因。但请注意:setRetainInstance(true) 在 FragmentStateAdapter 中已不再有效(该方法仅对旧版 FragmentPagerAdapter 生效,且自 AndroidX Fragment 1.3.0+ 起已被弃用),直接套用答案中的方案会导致编译错误或无效。
✅ 正确解决方案如下:
1. 合理设置 offscreenPageLimit
ViewPager2 提供了 setOffscreenPageLimit(int limit) 方法,用于指定在当前页两侧各保留多少个 Fragment 不被销毁。这是最直接、推荐的控制策略:
// 在设置 Adapter 后立即调用(务必在 attach 前) mViewPager.setAdapter(mAdapter); // 保留左右各 1 个 Fragment(共 3 页始终驻留内存) mViewPager.setOffscreenPageLimit(2); // 注意:limit ≥ 页面总数 - 1 才能完全禁用销毁
⚠️ 注意:
- limit 值为非负整数,最小值为 0(默认值,即只保留当前页);
- 若你的 Tab 总数为 n,要确保所有 Fragment 始终不被销毁,需设 limit = n - 1;
- 过高的 limit 会增加内存占用,需权衡性能与体验(例如 3 个 Tab 设 limit = 2 是合理选择)。
2. 使用 Fragment 构造传参替代动态添加(推荐重构)
你当前的 LandingAdapter 在 createFragment(int) 中直接返回预存的 Fragment 实例,存在严重隐患:Fragment 生命周期与 ViewPager2 管理逻辑冲突,可能导致状态错乱或 IllegalStateException。应改为每次 createFragment 都新建 Fragment 实例,并通过 arguments 传递必要参数:
public class LandingAdapter extends FragmentStateAdapter {
private final List<class extends fragment>> fragmentClasses = new ArrayList();
private final Bundle[] argsArray; // 可选:为每个 Fragment 预置参数
public LandingAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
this.argsArray = new Bundle[0]; // 或根据需求初始化
}
public void addFragment(Class extends Fragment> clazz, Bundle args) {
fragmentClasses.add(clazz);
// 动态扩展 argsArray 或统一用空 Bundle
}
@NonNull
@Override
public Fragment createFragment(int position) {
Class extends Fragment> clazz = fragmentClasses.get(position);
Fragment fragment = null;
try {
fragment = clazz.getDeclaredConstructor().newInstance();
if (argsArray.length > position && argsArray[position] != null) {
fragment.setArguments(argsArray[position]);
}
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate fragment at position " + position, e);
}
return fragment;
}
@Override
public int getItemCount() {
return fragmentClasses.size();
}
@Override
public long getItemId(int position) {
// 返回稳定 ID(如 position),避免因 Fragment 重建导致视图错位
return position;
}
}</class>
对应 Activity 初始化改为:
LandingAdapter mAdapter = new LandingAdapter(getSupportFragmentManager(), getLifecycle());
int savedAppMode = SharedPrefs.getInt(this, "app_mode_checked_item", 0);
if (savedAppMode == 1) {
mAdapter.addFragment(ItemsFrag1.class, null);
mAdapter.addFragment(ContactsFrag1.class, null);
mAdapter.addFragment(ImagesFrag1.class, null);
} else {
mAdapter.addFragment(ItemsFrag2.class, null);
mAdapter.addFragment(ContactsFrag2.class, null);
mAdapter.addFragment(ImagesFrag2.class, null);
}
mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(2); // 关键:保留全部 3 页
3. 在 Fragment 内部妥善管理状态
即使设置了 offscreenPageLimit,仍需确保 Fragment 自身状态可恢复:
- 将 UI 状态(如 EditText 文本、RecyclerView 滚动位置)保存至 onSaveInstanceState(),并在 onViewCreated() 中恢复;
- 避免在 onCreate() 或 onResume() 中执行重复初始化逻辑(如重复网络请求),建议用 ViewModel + LiveData 统一管理业务数据。
总结
| 方案 | 是否推荐 | 说明 |
|---|---|---|
| setOffscreenPageLimit(n-1) | ✅ 强烈推荐 | 简单有效,适配 ViewPager2 官方机制 |
| setRetainInstance(true) | ❌ 已废弃 | 不适用于 FragmentStateAdapter,且已被移除 |
| 预创建 Fragment 实例并复用 | ⚠️ 不推荐 | 易引发生命周期异常,违反 ViewPager2 设计原则 |
最终,请始终优先使用 setOffscreenPageLimit() 控制缓存范围,并配合 FragmentStateAdapter 的标准实例化模式,即可彻底解决 Tab 切换时 Fragment 重复创建的问题。











