背景:
HomeActivity
下有ViewPager2
+FragmentStateAdapter
+4个Fragment
。这是一个很简单的页面。
在后台日志中发现,首页的Fragment
一直在重复创建,最极端的日志中,一个Activity
下有50多个Fragment
。
业务代码抽象后如下,Activity
代码:
kotlin
class LeakFragmentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fragment_leak)
val adapter = ViewPagerAdapter(this)
findViewById<ViewPager2>(R.id.viewPager).adapter = adapter
adapter.refreshData(listOf("111", "222"))
}
}
ViewPagerAdapter
代码如下:
kotlin
class ViewPagerAdapter(private val activity: FragmentActivity) : FragmentStateAdapter(activity) {
private var pageTitles = emptyList<String>()
override fun getItemCount(): Int = pageTitles.size
override fun containsItem(itemId: Long): Boolean {
return pageTitles.find { it.hashCode().toLong() == itemId } != null
}
override fun getItemId(position: Int): Long {
return pageTitles[position].hashCode().toLong()
}
override fun createFragment(position: Int): Fragment {
val result = PageFragment.newInstance(pageTitles[position]).apply {
text = pageTitles[position]
}
val allFragments = activity.supportFragmentManager.fragments
// 打印当前supportFragmentManager下的所有Fragment
Log.i("FrankTest", "ViewPagerAdapter# createFragment size:${allFragments.size} allFragments:${allFragments.map { it.hashCode() }}")
return result
}
fun refreshData(list: List<String>) {
pageTitles = list
notifyDataSetChanged()
}
}
一、分析
尝试复现:
通过分析业务代码,以及多次尝试,终于发现了一个复现路径:
开启不保留活动,返回桌面,再次打开APP,能够稳定复现。
Tips: 对于一些线上问题,一些可能的复现场景:开启不保留活动、旋转屏幕、切换语言、切换暗黑模式。
日志如下:

可以看出,每次重新进入页面,parentFragment.childFragmentManager.fragments
的数量每次都会增加一个。
而实际上,页面只有一个Fragment
在展示数据,其他的Fragment
都成了游离Fragment
,没有被实际使用。
问题归因:
这个问题很有意思,因为从表现上看,这属于内存泄露了,因为存在了大量的游离/无用Fragment
,导致了内存的增加。
但实际上呢,当该Activity
退出时,所有的Fragment
又都能销毁释放资源,又不属于内存泄露的严格定义(所有异常的内存占用都应该属于内存治理的一部分)。
这个问题的原因也很有意思,是Fragment
的又一大坑,主要还是因为View
/Fragment
各自管理状态恢复导致的。
问题原因:
1.saveEnabled=false
此问题的发生,还是因为有开发同学在xml中,给ViewPager2
设置了saveEnabled=false
。
xml
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="80dp"
android:background="@color/cardview_shadow_start_color"
android:saveEnabled="false" />
追溯当时为什么加入这个属性,也是为了解决页面的泄露,在StackOverFlow上,确实有人推荐用这个参数解决页面View
的泄露。
saveEnabled=false
,会阻止Activity
在onSaveInstanceState
时,缓存页面状态,也就不会恢复页面数据,从而走重新创建流程。
2.Fragment恢复
但是这个参数,在有Fragment
的时候就不灵了,因为Fragment的恢复和View的恢复是两条线。
从下面的截图可以看出,一个savedInstanceState: Bundle
中,包含了四个key。
android:viewHierarchyState
: 用于恢复页面View
的,比如输入框内容,滚动位置等。android:fragments
: 用于恢复Activity
下的Fragment
,并且设置页面数据。

所以saveEnabled=false
,确实避免了View
的恢复,但是也正因为如此,后面Fragment
恢复后,没有正确被复用,具体的调用链后面会贴出来。
由于Fragment_Restored
仍然正常恢复了,并被添加到了FragmentStore
中。
FragmentStore
是真正管理Fragment
的地方,supportFragmentManager.fragments
,就是从FragmentStore
中获取Fragment
的。
但是该Fragment_Restored
没有被正确添加到FragmentStateAdapter
的mFragments
数组中,导致FragmentStateAdapter
又创建了新的Fragment
。
循环往复,FragmentStore
中的Fragment
越来越多。其中只有一个是正常使用的,其他都是游离的Fragment_Restored
。
除了
FragmentStateAdapter
,还有一个FragmentPagerAdapter
类里面,默认是不做状态恢复的。
二、saveEnabled的作用原理
核心逻辑分析:
saveEnabled
属性控制一个 View
及其子 View
是否参与状态保存与恢复流程(如屏幕旋转、Activity
重建)。当设置为 false
时,该 View
及其子树的状态不会被保存。
saveEnabled
属性的生效机制主要体现在状态保存阶段,而非恢复阶段。
1. saveEnabled的存储机制
设置方法:
kotlin
public void setSaveEnabled(boolean enabled) {
setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK);
}
enabled=true
:清除SAVE_DISABLED
标志位enabled=false
:设置SAVE_DISABLED
标志位
获取方法:
kotlin
public boolean isSaveEnabled() {
return (mViewFlags & SAVE_DISABLED_MASK) != SAVE_DISABLED;
}
2. saveEnabled的关键生效点:dispatchSaveInstanceState
核心判断逻辑:
kotlin
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();
if (state != null) {
container.put(mID, state);
}
}
}
关键条件分析:
mID != NO_ID
:View
必须有ID(mViewFlags & SAVE_DISABLED_MASK) == 0
:saveEnabled
必须为true
3. 状态保存的完整调用链
scss
Activity.onSaveInstanceState()
↓
ViewGroup.saveHierarchyState(container)
↓
ViewGroup.dispatchSaveInstanceState(container)
↓
遍历所有子View → View.dispatchSaveInstanceState(container)
↓
检查saveEnabled → View.onSaveInstanceState()
4. saveEnabled不影响状态恢复
恢复方法代码:
kotlin
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) { // 注意:这里没有检查saveEnabled!
Parcelable state = container.get(mID);
if (state != null) {
onRestoreInstanceState(state);
}
}
}
恢复状态时只检查ID,不检查saveEnabled
属性,这意味着:
saveEnabled
只控制是否保存状态- 不控制是否恢复状态
- 恢复完全依赖于是否存在已保存的状态数据
5. setSaveFromParentEnabled的独立机制
Fragment
中常见的调用:
kotlin
mFragment.mView.setSaveFromParentEnabled(false);
作用机制:
kotlin
public void setSaveFromParentEnabled(boolean enabled) {
setFlags(enabled ? 0 : PARENT_SAVE_DISABLED, PARENT_SAVE_DISABLED_MASK);
}
这是一个独立的控制机制 ,用于控制整个View
层次结构是否参与父级的状态保存遍历。
三、onRestoreInstanceState恢复的流程
这里的分析基于:
compileSdk 33
androidx.appcompat:appcompat:1.6.1
在ComponentActivity.java
源码:
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
// Restore the Saved State first so that it is available to
// OnContextAvailableListener instances
// 恢复View状态
savedStateRegistryController.performRestore(savedInstanceState)
// 恢复Fragment、ViewModel等内容
contextAwareHelper.dispatchOnContextAvailable(this)
super.onCreate(savedInstanceState)
ReportFragment.injectIfNeededIn(this)
if (contentLayoutId != 0) {
setContentView(contentLayoutId)
}
}
1.Fragment的恢复流程
总的调用栈,后面会拆解分析

1.ComponentActivity--->onCreate

2.FragmentActivity--->restoreSaveState

3.FragmentController-->restoreSaveState

4.FragmentManager解析state,传给FragmentStore
FragmentStore
是专门存放Fragment
的地方

5.FragmentStore恢复Fragment,并存储


2.View恢复流程
由于View.java
属于android源码,不在androidx下面,不方便debug,这里直接断点到ViewPager2
的恢复流程中,查看逻辑。
saveEnabled=true ![]() |
1.先调用onRestoreInstanceState ,mPendingCurrentItem 正确赋值 ![]() restorePendingState ,调用((StatefulAdapter) adapter).restoreState(mPendingAdapterState); ![]() FragmentStateAdapter 将恢复后的Fragment 加入到mFragments ![]() ensureFragment 能够正确获取到恢复后的Fragment ![]() |
---|---|
saveEnabled=false |
1.未调用onRestoreInstanceState ,导致后续没有将恢复后的Fragment 加入到mFragments ![]() ensureFragment 无法正确获取到恢复后的Fragment 3.导致重新走了createFragment 创建了新的Fragment ,即使此时FragmentStore 已经有可用的Fragment |
3.小结
查看下方流程图,可以看到,FragmentStore
虽然恢复了Fragment
,但是因为saveEnabled
导致部分流程没走,后续又创建了新的Fragment
,那么系统恢复的Fragment
就游离了,无人使用。

这里说一下我对Activity
恢复流程的看法,存在两个方面的问题:
- 既然
Activity
的恢复Bundle
,已经使用了不同的字段来存储Fragment
/View
的状态,那他们的恢复流程就应该独立互不干扰。在ViewPager2
中,即使View
不恢复,没有走onRestoreInstanceState
。Fragment
恢复后,也应当能添加到FragmentStateAdapter
的mFragments
中,完成它复用的使命。 - 系统提供了
saveEnabled
用来阻止View
的恢复,却没有提供对等功能来阻止Fragment
的复用。
四、Fragment保存状态到SavedInstanceState的流程:
这里拓展一下Fragment
状态的保存流程,可略过
Activity
会递归所有的Fragment
,以及Fragment
下面的Fragment
,进行状态的保存

1. 触发起点:ComponentActivity.onSaveInstanceState
kotlin
// ComponentActivity.java
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState); // 关键调用
}
作用 :Activity
生命周期回调,系统要求保存实例状态时触发
2. 状态注册控制器:SavedStateRegistryController.performSave
kotlin
// SavedStateRegistryController.java
public void performSave(@NonNull Bundle outBundle) {
mRegistry.performSave(outBundle); // 委托给SavedStateRegistry
}
作用:控制器层,负责协调状态保存操作
3. 状态注册表核心:SavedStateRegistry.performSave
kotlin
// SavedStateRegistry.java
public void performSave(@NonNull Bundle outBundle) {
// 遍历所有已注册的状态提供者
for (Iterator<Map.Entry<String, SavedStateProvider>> it =
mComponents.iteratorWithAdditions(); it.hasNext(); ) {
Map.Entry<String, SavedStateProvider> entry = it.next();
Bundle savedState = entry.getValue().saveState(); // 调用各个Provider
if (savedState != null) {
outBundle.putBundle(entry.getKey(), savedState);
}
}
}
作用 :遍历所有注册的Provider
,执行状态保存
4. FragmentManager的Lambda回调:saveState
kotlin
// FragmentManager中的Lambda表达式被调用
SavedStateProvider provider = () -> {
return saveAllStateInternal(); // 执行Fragment状态保存
};
作用 :执行FragmentManager
注册的状态保存逻辑
5. Fragment管理器核心保存:saveAllStateInternal
kotlin
// FragmentManager.java
Bundle saveAllStateInternal() {
Bundle bundle = new Bundle();
// 保存活跃的Fragment
mFragmentStore.saveActiveFragments(bundle);
// 保存其他状态信息
return bundle;
}
作用 :保存FragmentManager
管理的所有Fragment
状态
6. Fragment存储管理:FragmentStore.saveActiveFragments
kotlin
// FragmentStore.java
void saveActiveFragments(@NonNull Bundle bundle) {
// 遍历所有活跃的Fragment
for (FragmentStateManager fragmentStateManager : mActive.values()) {
if (fragmentStateManager != null) {
Fragment f = fragmentStateManager.getFragment();
Bundle result = fragmentStateManager.saveState(); // 保存单个Fragment状态
if (result != null && !result.isEmpty()) {
bundle.putBundle(f.mWho, result);
}
}
}
}
作用 :遍历保存所有活跃Fragment
的状态
7. Fragment状态管理器:FragmentStateManager.saveState
kotlin
// FragmentStateManager.java
@NonNull Bundle saveState() {
Bundle result = new Bundle();
saveBasicState(result); // 保存基础状态
return result;
}
作用 :管理单个Fragment
的状态保存
8. 基础状态保存:FragmentStateManager.saveBasicState
kotlin
// FragmentStateManager.java
void saveBasicState(@NonNull Bundle result) {
mFragment.performSaveInstanceState(result); // 调用Fragment的保存方法
// 保存其他基础信息
}
作用 :保存Fragment
的基础状态信息
9. Fragment实例状态保存:Fragment.performSaveInstanceState
kotlin
// Fragment.java
void performSaveInstanceState(@NonNull Bundle outState) {
onSaveInstanceState(outState); // 调用用户重写的方法
// 保存ChildFragmentManager状态
mChildFragmentManager.saveAllStateInternal();
}
作用 :执行Fragment
的实际状态保存逻辑
10.完整流程图

11.关键技术点
1. 注册时机
kotlin
// FragmentManager.attachController() 中
registry.registerSavedStateProvider(SAVED_STATE_TAG, () -> {
return saveAllStateInternal();
});
2. 状态恢复
kotlin
// 对应的状态恢复
Bundle savedState = registry.consumeRestoredStateForKey(SAVED_STATE_TAG);
if (savedState != null) {
restoreFromSavedState(savedState);
}
从Activity
开始,通过SavedState
框架,最终调用到每个Fragment
的状态保存方法,确保整个应用的状态能够完整保存和恢复。
五、解决方法:
方案一:直面问题,解决泄露
saveEnabled
改回true
,在有ViewPager2
的场景,不应该使用此参数。
缺点:
需要解决页面上的View
泄露,以及View
泄露导致的Fragment
泄露。尤其是首页这样的多团队共建页面,View
的泄露不好治理
方案二:复用恢复后的Fragment
既然系统已经恢复了Fragment_Restore
,我们在系统createFragment
时,直接返回这个恢复Fragment_Restore
,就不会再创建新的了。
结论:不行。 ❌
因为Fragment_Restore
已经走完了该有的Add生命周期,createFragment
后续会走add流程,而Fragment_Restore
已经被add了,会报错。
ViewPagerAdapter
做如下改造:
kotlin
override fun createFragment(position: Int): Fragment {
// 优先使用恢复后的Fragment
val result = activity.supportFragmentManager.findFragmentByTag("f${position}")
?: PageFragment.newInstance(pageTitles[position]).apply {
text = pageTitles[position]
}
val allFragments = activity.supportFragmentManager.fragments
Log.i("FrankTest", "ViewPagerAdapter# createFragment size:${allFragments.size} allFragments:${allFragments.map { it.hashCode() }}")
return result
}
运行后的崩溃:

方案三:同时清除Fragment状态
保持saveEnabled=false
,但是同时清除Fragment
的恢复参数
kotlin
class LeakFragmentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.remove("android:support:fragments")
setContentView(R.layout.activity_fragment_leak)
val adapter = ViewPagerAdapter(this)
findViewById<ViewPager2>(R.id.viewPager).adapter = adapter
adapter.refreshData(listOf("111", "222"))
}
}
经验证,上述代码可以解决游离Fragment
的问题,多次重复步骤,supportFragmentManager.fragments
的Fragment
数量一直是1个。
系统的这个常量是包内访问的,应用无法直接获取:

我们可以在项目内写同包名的路径,然后指向这个包内访问的常量,在项目内就可以访问了:

优点:
快速解决问题,View
不恢复,Fragment
也不应该恢复。
缺点:
对于真正需要Fragment
恢复的业务,此方案不适用。
建议短期先用方案三临时解决问题,再将各页面泄露情况通知到各业务团队,完全解决后,再恢复页面恢复逻辑。