fragment为什么不建议有参数的构造方法

74 阅读3分钟

Fragment 不建议写有参构造方法,因为 系统会在重建 Fragment 时(旋转、进程被杀后恢复、返回栈还原、Nav 回退)用反射调用 无参构造 来实例化。构造函数里的入参不会被保存/恢复,轻则丢数据,重则直接崩溃(Unable to instantiate fragment ... must have a public empty constructor)。正确做法是用 arguments Bundle 传入“实例参数”,或用 ViewModel/DI 注入依赖。


为什么不写有参构造

  1. 系统恢复机制要求无参构造

    FragmentManager 在需要还原 Fragment 时只知道类名,会通过反射调用无参构造。你写了 MyFragment(String id) 不会被调用,甚至根本没有无参构造就会崩溃。

  2. 构造入参不会持久化

    构造器参数不参与 Fragment 的状态保存;而 arguments(Bundle)会被保存到 FragmentState,可跨配置变化/进程死亡恢复。

  3. 生命周期与时序不匹配

    构造器发生在 onAttach/onCreate 之前,很多需要 Context/NavBackStackEntry/SavedStateHandle 的依赖此时还不可用;放构造里容易造成空依赖或持有错误的引用。

  4. 更容易泄漏与出错

    把 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 才能在各种系统恢复场景下稳定、可还原、可测试