Fragment 不建议写有参构造方法,因为 系统会在重建 Fragment 时(旋转、进程被杀后恢复、返回栈还原、Nav 回退)用反射调用 无参构造 来实例化。构造函数里的入参不会被保存/恢复,轻则丢数据,重则直接崩溃(Unable to instantiate fragment ... must have a public empty constructor)。正确做法是用 arguments Bundle 传入“实例参数”,或用 ViewModel/DI 注入依赖。
为什么不写有参构造
-
系统恢复机制要求无参构造
FragmentManager 在需要还原 Fragment 时只知道类名,会通过反射调用无参构造。你写了 MyFragment(String id) 不会被调用,甚至根本没有无参构造就会崩溃。
-
构造入参不会持久化
构造器参数不参与 Fragment 的状态保存;而 arguments(Bundle)会被保存到 FragmentState,可跨配置变化/进程死亡恢复。
-
生命周期与时序不匹配
构造器发生在 onAttach/onCreate 之前,很多需要 Context/NavBackStackEntry/SavedStateHandle 的依赖此时还不可用;放构造里容易造成空依赖或持有错误的引用。
-
更容易泄漏与出错
把 Activity、View、Context 等放到构造参数并长期持有,配置变化后这些引用失效,易泄漏或 NPE。
正确传参方式(官方推荐)
1)arguments+ 工厂方法(最通用)
class DetailFragment : Fragment(R.layout.fragment_detail) {
private val itemId: String by lazy { requireArguments().getString(ARG_ID)!! }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 itemId ...
}
companion object {
private const val ARG_ID = "arg_id"
fun newInstance(itemId: String) = DetailFragment().apply {
arguments = bundleOf(ARG_ID to itemId) // 仅放可序列化到 Bundle 的数据
}
}
}
要点:
-
只放“实例参数” (能描述这个 Fragment 显示哪个对象/状态的最小信息)。
-
数据需 Parcelable/Serializable(可用 @Parcelize)。
2) Jetpack Navigation(Safe Args)
-
在 nav_graph.xml 定义 ,生成类型安全的 DetailFragmentArgs,自动读写到 arguments,并支持深链、返回栈恢复。
3) 依赖用ViewModel / DI注入(不是构造参数)
- 业务状态:ViewModel(可配 SavedStateHandle 持久化关键字段)。
- 服务/仓库/UseCase:Hilt/Koin 注入到 Fragment(仍保持 Fragment 无参构造)。
- 不要把大对象/Context 当作实例参数塞进 arguments。
进阶:FragmentFactory什么时候用
AndroidX 允许自定义 FragmentFactory 来创建带构造参数的 Fragment(常用于依赖注入):
class MyFragmentFactory(
private val repo: Repo
) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val clazz = loadFragmentClass(classLoader, className)
return when (clazz) {
DetailFragment::class.java -> DetailFragment(repo) // 自己决定怎么构造
else -> super.instantiate(classLoader, className)
}
}
}
前提:在 恢复之前 把 fragmentFactory 设置到 FragmentManager,否则仍会走默认无参路径。
注意:这只适合注入依赖;实例参数仍应放 arguments,因为它们需要被保存/恢复与支持深链/导航。
常见坑与对策
-
崩溃:must have a public empty constructor
→ 去掉有参构造,提供无参构造;用 newInstance + arguments 传参,或接入 FragmentFactory(并正确安装)。
-
旋转后参数丢失
→ 你把参数放在字段或构造器里了;改为从 arguments 读取;必要时把业务状态放 ViewModel + SavedStateHandle。
-
把大对象/Context塞进 arguments
→ 违反可序列化约束且易泄漏。改:传 id/轻量 DTO;大对象从仓库/ViewModel/DI 再拉。
-
setArguments 调用时机错误
→ 必须在 Fragment attach 前设置(创建后立刻),否则抛 IllegalStateException。
经验小抄
-
实例参数 → arguments;业务状态 → ViewModel;依赖 → DI/FragmentFactory。
-
保持 public 无参构造;或确保在还原前安装你的 FragmentFactory。
-
使用 Safe Args 获得类型安全与导航/深链联动。
这样做,你的 Fragment 才能在各种系统恢复场景下稳定、可还原、可测试。