第2章 Fragment生命周期与注入时机:onAttach、onCreate、onViewCreated中注入点分析、@AndroidEntryPoint注解作用
2.1 原理:Hilt如何与Fragment生命周期协同
Hilt的依赖注入基于JSR-330标准,但Fragment的生命周期并非线性稳定。Fragment从附加到Activity到视图销毁经历多个关键回调,而Hilt的注入点必须恰好位于依赖容器(Component)稳定可用的时刻。@AndroidEntryPoint注解会为Fragment自动生成一个Hilt Component(Hilt_FragmentSubcomponent),该Component的生命周期与Fragment的“存活状态”绑定——即从onAttach()到onDestroy()。但注入时机受限于Component创建与视图初始化两个阶段:
- onAttach():Fragment与Activity建立关联,此时Hilt Component已创建,但尚未绑定视图;
- onCreate():Component完全就绪,可注入非视图依赖(如ViewModel、UseCase);
- onViewCreated():视图已膨胀,可注入视图相关依赖(如Binding适配器、ViewModels with SavedState)。
最佳实践:将“构造注入”与“字段注入”分开:构造注入用于稳定依赖(如Repository、UseCase),字段注入用于视图相关或延迟初始化的依赖。
2.2 实现流程:三种注入点的选择策略
| 生命周期回调 | 注入点可用性 | 典型注入目标 | 注意事项 |
|---|---|---|---|
| onAttach() | Component已创建 | Application级单例、ActivityScoped依赖 | 不可访问Fragment视图;可访问Activity上下文 |
| onCreate() | Component完全就绪 | ViewModel、业务逻辑对象、数据源 | 视图未创建,注入后可直接使用依赖 |
| onViewCreated() | 视图已膨胀 | Binding、UI适配器、ViewModels依赖视图 | 适合注入后立即绑定视图 |
核心原则:无论在哪一个回调注入,
@Inject lateinit var字段都会在字段访问时自动被填充。但Hilt的Component会保证在super.onAttach()之后、onAttach()之前完成字段注入(若你在onAttach()之前访问字段则可能抛异常)。
2.3 @AndroidEntryPoint 注解作用
@AndroidEntryPoint是Hilt标准化Android组件的入口注解。作用于Fragment时,它会自动:
- 为Fragment生成一个
Hilt_BaseFragment子类,重写onAttach()、onCreate()、onViewCreated()等方法,注入依赖; - 创建并管理Fragment级别的
FragmentComponent(继承自ActivityComponent),生命周期与Fragment的活跃阶段对齐; - 确保所有标注了
@Inject或@HiltViewModel的字段在正确时机被注入。
// 使用@AndroidEntryPoint的Fragment示例
@AndroidEntryPoint
class UserProfileFragment : Fragment() {
@Inject
lateinit var userRepository: UserRepository // 字段注入
@Inject
lateinit var bindingInflater: UserProfileBindingInflater // 依赖视图的注入
private var _binding: FragmentUserProfileBinding? = null
private val binding get() = _binding!!
override fun onAttach(context: Context) {
super.onAttach(context)
// userRepository 在 super.onAttach() 后已经被注入,此处可直接使用
Log.d("HiltDemo", "userRepository injected: ${userRepository.hashCode()}")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在此可以调用 userRepository 做业务初始化
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentUserProfileBinding.bind(view)
// 可以注入依赖于view的对象,例如bindingInflater
bindingInflater.inflateInto(binding)
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}
2.4 内联SVG:Fragment生命周期与Hilt注入点对比图
2.5 常见错误与调试
- NullPointerException 访问未注入字段:在
onAttach()前(例如在构造方法中)访问@Inject lateinit var字段会崩溃。解决:确保在super.onAttach()之后访问。 - 视图相关注入在
onCreateView()中失败:Fragment的onCreateView()返回的View可能为空,Hilt注入器在onViewCreated()之后才能安全使用视图。应避免在onCreate()中注入需要视图的参数。 - @AndroidEntryPoint 遗忘:未标注该注解时,
@Inject字段不会被自动填充,会导致运行时UninitializedPropertyAccessException。
性能提示:避免在
onAttach()或onCreate()中注入重量级依赖(如数据库、网络客户端),这些依赖应在Fragment构造时通过构造注入传递,或使用@Singleton作用域缓存。在onViewCreated()中注入视图相关依赖时,需注意不要持有View导致内存泄漏。
2.6 编程式注入(非@AndroidEntryPoint场景)
某些特殊情况(如自定义DialogFragment)可能无法使用@AndroidEntryPoint,需手动调用Hilt.inject(this):
// 手动注入示例
class CustomDialogFragment : DialogFragment() {
@Inject
lateinit var analytics: AnalyticsService
override fun onAttach(context: Context) {
super.onAttach(context)
Hilt.inject(this) // 必须在 super.onAttach() 之后调用
}
}
注意:手动注入时必须确保Fragment已绑定Activity且Hilt的ActivityComponent已可用。
2.7 部署验证方法
- 在Fragment的
onAttach()中添加日志:Log.d("Hilt","${analytics.hashCode()}"); - 运行App,观察日志是否在
super.onAttach()后立即打印; - 若报错
dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper cannot be cast to ...,表明@AndroidEntryPoint缺失或Fragment的host Activity未标注@AndroidEntryPoint。
使用Android Studio的“Hilt Log”面板可查看注入拓扑。