Hilt初始化时机:Application.onCreate()中的初始化

说白了吧,Hilt的初始化时机藏在 Application.onCreate() 里。我第一次接手一个已经上线的项目时,发现冷启动速度比竞品慢了快1秒。一查,问题就出在Hilt初始化把主线程卡住了。

嗯,这一章我们就来深挖 Hilt到底在Application.onCreate()里做了什么,以及为什么它可能成为启动性能的瓶颈。

一、Hilt初始化的“幕后黑手”

当你给Application类加上 @HiltAndroidApp 注解后,Hilt会生成一个名为 Hilt_YourApplication 的基类。这个基类的 onCreate() 方法里,会触发Hilt的组件初始化流程。

@HiltAndroidApp
public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 你以为这里空空的?其实Hilt已经偷偷插入了代码
    }
}

// Hilt生成的基类(简化后)
public class Hilt_MyApp extends Application {
    @Override
    public void onCreate() {
        inject();
        super.onCreate();
    }

    private void inject() {
        // 创建SingletonComponent,扫描所有@InstallIn模块
        // 构建对象图,实例化所有单例
    }
}

你看,Hilt的初始化就是在这个 inject() 方法里完成的。它会做三件事:

  1. 创建 SingletonComponent(应用级组件)
  2. 收集所有通过 @Module @InstallIn(SingletonComponent.class) 注册的模块
  3. 实例化所有标记为 @Singleton 的依赖(比如OkHttpClient、Retrofit等)

关键点: 这三个步骤全是同步执行的,而且跑在主线程上。

二、性能隐患:初始化耗时到底有多大?

我参与过一个短视频App的项目,当时冷启动时有明显白屏。我们在 onCreate() 前后加了时间戳日志,结果发现Hilt初始化就花了 800ms。为什么这么慢?

  • 项目里有十几个模块,每个模块都在 SingletonComponent 里绑定了大量提供者方法
  • 有几个网络库的初始化(比如连接池预热)特别重
  • 第三方SDK的Hilt模块也在Application层初始化

避坑指南: 我曾经在 Application.onCreate() 里直接写了一个 Thread.sleep(500) 来模拟耗时初始化,结果冷启动时系统直接弹了ANR对话框——因为主线程阻塞超过5秒就会被系统杀掉。所以即使是Hilt自带的初始化,只要内容多了,也极有可能造成启动慢甚至ANR。

三、复现延迟问题的标准步骤

如果你也想在本地复现Hilt初始化导致的启动延迟,可以按这个步骤来:

  1. 第一步:新建一个带Hilt的Android项目,确保Application类加了 @HiltAndroidApp
  2. 第二步:在Application中重写一个“假”的延时初始化模块
    @Module
    @InstallIn(SingletonComponent.class)
    public class HeavyInitModule {
    
        @Provides
        @Singleton
        public HeavyObject provideHeavyObject() {
            // 模拟耗时操作
            try {
                Thread.sleep(500); // 故意延迟500ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return new HeavyObject();
        }
    }
    
  3. 第三步:在Application.onCreate()里打印启动时间
    @HiltAndroidApp
    public class DemoApp extends Application {
        @Override
        public void onCreate() {
            long start = System.currentTimeMillis();
            super.onCreate(); // 触发Hilt初始化
            long cost = System.currentTimeMillis() - start;
            Log.d("HiltInit", "Hilt初始化耗时: " + cost + "ms");
        }
    }
    
  4. 第四步:运行App,观察日志。你会看到类似 Hilt初始化耗时: 520ms 的输出。

小贴士: 如果项目大但没加耗时模块,就用 Trace 工具(Android Studio Profiler)抓一下主线程的调用栈,可以清晰地看到Hilt的 inject() 方法占用了多少时间。

四、核心流程:一张图看懂

下面这个流程图画了Hilt在Application.onCreate()中初始化的完整路径。从左到右,你看到的就是冷启动时主线程的工作顺序——**初始化越重,白屏时间越长**。

Hilt在Application.onCreate()中的初始化流程 调用super.onCreate() 触发生成基类Hilt_App.inject() 创建SingletonComponent 收集@InstallIn模块 实例化所有单例(含耗时模块) 初始化完成,继续onCreate后续代码 注意:节点3→6三条路径是并行执行的,但实例化单例可能卡住主线程

五、如何定位初始化中的“罪魁祸首”?

如果你已经在项目里发现了启动慢,别急着改架构。先找出哪个模块最耗时间。我一般用两个方法:

方法 操作 优点
手工日志 在每个@Provides方法开头和结尾打印时间戳 简单直接,适合少量模块
Android Profiler 录制Method Trace,查看主线程上Hilt相关方法的执行时长 全局视角,不用改代码
AOP/字节码 用AspectJ或Transform API插桩Hilt生成的注入方法 自动化,适合大型团队

我个人的习惯: 在项目初期就用手工日志打一遍所有Singleton模块的初始化耗时,并把它写进CI的启动性能基线。如果新提交的代码导致某个模块初始化超过阈值,就直接发告警。这样能避免后期“积重难返”。

六、避坑总结

  • 不要在Application.onCreate()里放重初始化代码——比如图片库、热修复、网络库的初始化,可以考虑移到第一个Activity的延迟加载里
  • @Singleton里的对象越少越好——能做成@ActivityScoped或@FragmentScoped的就别放全局
  • 第三方SDK的Hilt模块要慎用——有些SDK的初始化里藏着网络请求或文件IO,一定要评测后再引入