第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时,它会自动:

  1. 为Fragment生成一个Hilt_BaseFragment子类,重写onAttach()、onCreate()、onViewCreated()等方法,注入依赖;
  2. 创建并管理Fragment级别的FragmentComponent(继承自ActivityComponent),生命周期与Fragment的活跃阶段对齐;
  3. 确保所有标注了@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注入点对比图

Fragment生命周期与Hilt注入点对比 onAttach onCreate onCreateView onViewCreated 注入点A 注入点B 注入点C 注入点说明 A (onAttach): 可注入 Application/Activity 级依赖,不可访问视图。 B (onCreate): 注入 ViewModel、业务逻辑,视图未创建。 C (onViewCreated): 注入视图相关依赖(Binding、适配器),推荐在视图膨胀后注入。 @AndroidEntryPoint 会在每个生命周期回调前自动完成字段注入(通过重写方法),开发者无需手动调用注入。

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 部署验证方法

  1. 在Fragment的onAttach()中添加日志:Log.d("Hilt","${analytics.hashCode()}");
  2. 运行App,观察日志是否在super.onAttach()后立即打印;
  3. 若报错dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper cannot be cast to ...,表明@AndroidEntryPoint缺失或Fragment的host Activity未标注@AndroidEntryPoint。

使用Android Studio的“Hilt Log”面板可查看注入拓扑。