fragment的启动优化
我们先来看一段min栏的启动代码。
supportFragmentManager.beginTransaction()
.add(R.id.mini_player, MiniPlayFragment().apply {
})
.commitAllowingStateLoss()
xml:
<FrameLayout
android:id="@+id/mini_player"
android:layout_width="match_parent"
android:layout_height="match_parent" />
出现的警告信息。
06-19 16:37:18.946 17397 17397 D FragmentManager: StrictMode violation in com.flyme.auto.localmusic.ui.main.fragments.MiniPlayerFragment
06-19 16:37:18.946 17397 17397 D FragmentManager: androidx.fragment.app.strictmode.WrongFragmentContainerViolation: Attempting to add fragment MiniPlayerFragment{706b678} (b26a8c70-04f6-40b3-ac6f-41e4ea805151 id=0x7f0a01f4) to container android.widget.FrameLayout{8ebca9b V.E...... ......I. 0,0-0,0 #7f0a01f4 app:id/mini_player_container} which is not a FragmentContainerView
06-19 16:37:18.946 17397 17397 D FragmentManager: at androidx.fragment.app.strictmode.FragmentStrictMode.i(FragmentStrictMode.kt:13)
06-19 16:37:18.946 17397 17397 D FragmentManager: at androidx.fragment.app.h.f(FragmentStateManager.java:158)
06-19 16:37:18.946 17397 17397 D FragmentManager: at androidx.fragment.app.h.m(FragmentStateManager.java:125)
06-19 16:37:18.946 17397 17397 D FragmentManager: at androidx.fragment.app.i.t(FragmentStore.java:31)
06-19 16:37:18.946 17397 17397 D FragmentManager: at androidx.fragment.app.FragmentManager.W0(FragmentManager.java:28)
06-19 16:37:18.946 17397 17397 D FragmentManager: at androidx.fragment.app.FragmentManager.S(FragmentManager.java:10)
06-19 16:37:18.946 17397 17397 D FragmentManager: at androidx.fragment.app.FragmentManager.x(FragmentManager.java:12)
- 主要问题
WrongFragmentContainerViolation:尝试将 MiniPlayerFragment 添加到一个不是 FragmentContainerView 的容器(android.widget.FrameLayout)中。
可优化的点
1、 修改为FragmentContainerView
这是 AndroidX Fragment 库的严格模式(StrictMode)检测到的违规行为。
从 AndroidX Fragment 1.3.0 开始,推荐使用 FragmentContainerView 作为 Fragment 的容器,而不是普通的 FrameLayout。
1.1 设计目的
- FragmentContainerView
专门为 Fragment 设计,继承自 FrameLayout,但针对 Fragment 的使用场景做了优化。
是 AndroidX Fragment 库(1.2.0+)推荐的容器,未来会支持更多 Fragment 专属特性。
- FrameLayout
通用的视图容器,并非为 Fragment 定制,可能存在一些边缘场景的兼容性问题。
1.2 主要区别
特性 | FragmentContainerView | FrameLayout |
---|---|---|
动画支持 | 完美支持 Fragment 转场动画(如 enter/exit) | 可能因视图层级问题导致动画异常 |
生命周期同步 | 严格保证 Fragment 和容器视图的生命周期同步 | 可能出现短暂的生命周期不一致。 |
Z-Order 处理 | 自动管理 Fragment 的视图层级(避免重叠问题) | 需要手动控制视图层级 |
严格模式检查 | 通过 StrictMode 检查(默认要求) | 会触发 WrongFragmentContainerViolation 警告 |
Fragment 事务优化 | 对 add/replace 等操作有内部优化 | 无特殊优化 |
2、 commitAllowingStateLoss()
这里使用了commitAllowingStateLoss,但不建议这样子使用
- 状态安全性:MiniPlayerFragment 可能是 UI 核心组件,状态丢失会导致用户体验问题。
- 替代方案:
使用 commit() + 检查 isStateSaved:
if (!supportFragmentManager.isStateSaved) {
supportFragmentManager.commit { add(...) }
}
1. 核心问题:commit() 的时机限制
当 Activity 或 Fragment 正在销毁(如屏幕旋转、返回键退出、内存回收)时,其状态会被保存(调用 onSaveInstanceState),此时:
- commit():
如果在 状态已保存后 调用,会直接抛出异常:
IllegalStateException: Can not perform this action after onSaveInstanceState
- commitAllowingStateLoss():
允许提交,但可能导致 UI 状态丢失(如 Fragment 添加了但视图不显示)。
2. isStateSaved 的作用
FragmentManager.isStateSaved 是一个状态检查方法,用于判断当前是否允许安全提交事务:
- 返回 true:
Activity/Fragment 已触发 onSaveInstanceState,此时提交事务可能失败或丢失状态。 - 返回 false:
状态未保存,可以安全调用 commit()。
3. 典型场景
(1) 异步回调中提交事务
例如在网络请求回调或 LiveData 观察中动态添加 Fragment:
viewModel.data.observe(this) { result ->
// 检查状态是否已保存
if (!supportFragmentManager.isStateSaved) {
supportFragmentManager.commit {
replace(R.id.container, ResultFragment(result))
}
}
}
(2) 处理用户快速操作
当用户快速点击按钮触发多个 Fragment 事务时,防止因 Activity 突然进入后台导致崩溃:
button.setOnClickListener {
if (!supportFragmentManager.isStateSaved) {
supportFragmentManager.commit {
add(R.id.container, DialogFragment())
}
}
}
(3) 替代 commitAllowingStateLoss()
避免滥用 commitAllowingStateLoss()(状态丢失风险),改用安全提交:
fun safeCommit(block: FragmentTransaction.() -> Unit) {
if (!supportFragmentManager.isStateSaved) {
supportFragmentManager.commit(block)
} // 否则忽略或记录日志
}
4. 为什么需要这种检查?
-
状态一致性:
Android 的 onSaveInstanceState 机制要求 UI 状态在此时必须固定,后续修改无法被保存,可能导致恢复时界面错乱。 -
崩溃预防:
直接调用 commit() 在状态保存后会立即抛出异常,而 isStateSaved 提供了前置检查机会。
5. 对比 commitAllowingStateLoss()
方法 | 安全性 | 状态丢失风险 | 适用场景 |
---|---|---|---|
commit() + isStateSaved | 高 | 无 | 绝大多数需要安全提交的场景 |
commitAllowingStateLoss() | 低 | 有 | 非关键UI更新(如日志记录) |
6. 常见误区
-
误区 1:认为 commitAllowingStateLoss() 是万能解决方案。
事实:它只是隐藏了问题,可能导致用户看到残缺的 UI 状态(如 Fragment 已添加但视图未显示)。 -
误区 2:在 onCreate 中无需检查 isStateSaved。
事实:onCreate 通常是安全的,但若从后台恢复时执行异步操作仍需检查。
总结
-
commit() + isStateSaved 是处理 Fragment 事务提交时机的 安全模式,优先于 commitAllowingStateLoss。
-
适用场景:所有动态提交 Fragment 事务的场合(尤其是异步回调、用户交互等不确定时机)。
-
本质:通过前置检查避免 IllegalStateException 和状态丢失,兼顾稳定性和用户体验。
3、 setReorderingAllowed
setReorderingAllowed(true) 是 Fragment 事务(FragmentTransaction)中的一个优化选项,主要用于 提升 Fragment 事务的执行效率 和 避免不必要的中间状态。以下是它的详细解释和实际作用:
1. 什么是事务优化?
当执行多个 Fragment 操作(如 add、replace、remove)时,默认情况下,FragmentManager 会按顺序逐步执行每个操作,导致 中间状态被频繁触发(例如短暂的生命周期变化或视图重绘)。
启用 setReorderingAllowed(true) 后,FragmentManager 会将这些操作合并为一个 原子性变更,跳过中间状态,直接呈现最终结果。
2. 解决了什么问题?
(1) 性能提升
- 减少不必要的生命周期回调:
例如,替换 Fragment A → B 时,默认会先执行 A 的 onPause → onStop,再执行 B 的 onStart → onResume。启用优化后,可能跳过部分中间状态。 - 减少视图重绘次数:
避免因多次操作导致的界面闪烁。
(2) 避免动画冲突
在转场动画(如 setCustomAnimations)中,优化后的动画会更流畅,避免因中间状态导致的动画断裂。
(3) 防止状态不一致
在复杂事务(如同时执行 add + remove + hide)中,确保所有操作一次性完成,降低并发问题风险。
3. 适用场景
- 多个 Fragment 同时操作(例如 replace + addToBackStack)。
- 使用转场动画(需要平滑过渡效果时)。
嵌套 Fragment 或动态场景(如 ViewPager2 配合 Fragment)。
4. 底层原理
-
操作合并:
将多个 add/remove/replace 操作合并为一次视图层级变更。 -
生命周期跳过:
通过 FragmentStateManager 直接跳转到最终状态(如从 CREATED 到 RESUMED,跳过 STARTED)。 -
动画优化:
使用 SpecialEffectsController 统一处理动画队列。
5. 代码示例对比
supportFragmentManager.commit {
replace(R.id.container, FragmentA())
addToBackStack(null)
setReorderingAllowed(true) // 合并操作为原子性变更
}