80、依赖注入之Hilt:Hilt配置、@HiltAndroidApp、@AndroidEntryPoint、ViewModel注入

依赖注入,说白了就是帮你把对象“喂”到需要的地方去。你想想看,一个Activity里要创建ViewModel、要拿Repository、还要调网络库……如果全靠自己new,代码会变成一团乱麻。Hilt就是Google官方推荐的Android依赖注入框架,基于Dagger,但配置简单得多。

我个人习惯在项目一开始就把Hilt搭好。为什么?因为后期再改,牵一发动全身。今天我们就从零开始,把Hilt的配置、注解使用、ViewModel注入全部走一遍。

一、Hilt的配置流程

配置Hilt其实就三步:加依赖、配Gradle、写Application。嗯,这里要注意版本号别搞错。

1. 项目级build.gradle

// 项目根目录的 build.gradle
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48'
    }
}

2. 模块级build.gradle

// app/build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
    id 'com.google.dagger.hilt.android'
}

android {
    // ... 你的配置
}

dependencies {
    implementation 'com.google.dagger:hilt-android:2.48'
    kapt 'com.google.dagger:hilt-compiler:2.48'
    
    // 如果要用ViewModel注入,还需要这个
    implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
    // 或者用Fragment时
    implementation 'androidx.hilt:hilt-navigation-fragment:1.1.0'
}

// 允许kapt处理Hilt注解
kapt {
    correctErrorTypes true
}
⚠️ 避坑指南
我曾经在升级AGP版本时忘了同步Hilt版本号,结果编译报了一堆看不懂的错误。记住:Hilt版本要和AGP、Kotlin版本匹配。去官方文档查兼容性矩阵,别自己猜。

二、@HiltAndroidApp:应用的入口

这个注解必须加在Application类上。它会在编译时生成Hilt的根组件,说白了就是整个依赖图的起点。

@HiltAndroidApp
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // 这里不要做太多初始化,Hilt会在你之前完成
    }
}

记得在AndroidManifest.xml里声明这个Application:

<application
    android:name=".MyApplication"
    ... />
💡 个人经验
我习惯在Application里只放Hilt注解,不做其他逻辑。如果你需要初始化第三方库,可以创建一个单独的Initializer类,用Hilt注入。这样Application保持干净,测试也方便。

三、@AndroidEntryPoint:让组件支持注入

这个注解加在Activity、Fragment、Service、BroadcastReceiver上。加了之后,Hilt才能往里面注入依赖。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    // 直接注入,不用手动创建
    @Inject lateinit var repository: UserRepository
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 注意:super.onCreate()之前不能访问注入的变量
        setContentView(R.layout.activity_main)
        
        // 现在可以用了
        repository.loadUsers()
    }
}

Fragment也是一样:

@AndroidEntryPoint
class UserFragment : Fragment() {
    
    @Inject lateinit var viewModelFactory: UserViewModelFactory
    
    private lateinit var viewModel: UserViewModel
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        viewModel = ViewModelProvider(this, viewModelFactory)
            .get(UserViewModel::class.java)
    }
}
🔑 关键点
@AndroidEntryPoint 会为每个组件生成一个对应的Hilt组件。比如Activity对应ActivityComponent,Fragment对应FragmentComponent。这些组件有各自的生命周期,Hilt会自动管理。

四、ViewModel注入:最常用的场景

ViewModel注入是Hilt最实用的功能之一。你想想看,以前我们要写ViewModelProvider.Factory,还要手动传参数,多麻烦。Hilt直接帮你搞定。

1. 用@HiltViewModel注解

@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    val users: LiveData<List<User>> = userRepository.getUsers()
    
    fun refresh() {
        viewModelScope.launch {
            userRepository.refresh()
        }
    }
}

注意构造函数的@Inject注解。Hilt会帮你找到UserRepository的实例,自动传进来。

2. 在Activity/Fragment中使用

@AndroidEntryPoint
class UserListActivity : AppCompatActivity() {
    
    // 直接用by viewModels(),Hilt自动处理
    private val viewModel: UserViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_list)
        
        viewModel.users.observe(this) { users ->
            // 更新UI
        }
    }
}

如果你用Compose,更简单:

@Composable
fun UserScreen() {
    val viewModel: UserViewModel = hiltViewModel()
    val users by viewModel.users.observeAsState()
    
    // 渲染UI
}
💡 避坑指南
我曾经遇到一个问题:ViewModel里注入的Repository在测试时总是空指针。后来发现是因为测试类没有加@HiltAndroidTest注解。记住:测试环境也要配置Hilt,否则注入不生效。

五、Hilt的依赖图结构

下面这张图展示了Hilt的核心组件层级。我建议你仔细看看,理解组件之间的父子关系,这对排查问题很有帮助。

Hilt 组件层级结构 ApplicationComponent @HiltAndroidApp 生成,全局单例 ActivityComponent @AndroidEntryPoint Activity 生成 FragmentComponent @AndroidEntryPoint Fragment 生成 ViewComponent @WithFragmentBindings 或 @AndroidEntryPoint View 生命周期 • ApplicationComponent 应用启动→销毁 • ActivityComponent onCreate→onDestroy • FragmentComponent onAttach→onDetach • ViewComponent 视图存在期间 ⚠️ 子组件可以访问 父组件的依赖 反之不行

六、实战:完整的注入示例

我们来看一个完整的例子。假设有一个用户列表页面,需要从网络获取数据。

1. 定义Repository

// 用@Singleton表示全局单例
@Singleton
class UserRepository @Inject constructor(
    private val api: UserApi
) {
    suspend fun getUsers(): List<User> {
        return api.fetchUsers()
    }
}

2. 提供网络依赖

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    
    @Provides
    @Singleton
    fun provideUserApi(): UserApi {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(UserApi::class.java)
    }
}

3. ViewModel注入

@HiltViewModel
class UserListViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users.asStateFlow()
    
    init {
        loadUsers()
    }
    
    private fun loadUsers() {
        viewModelScope.launch {
            try {
                _users.value = repository.getUsers()
            } catch (e: Exception) {
                // 处理错误
            }
        }
    }
}

4. Activity中使用

@AndroidEntryPoint
class UserListActivity : AppCompatActivity() {
    
    private val viewModel: UserListViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_list)
        
        lifecycleScope.launch {
            viewModel.users.collect { users ->
                // 更新RecyclerView
            }
        }
    }
}
🎯 核心要点
  • @HiltAndroidApp 是应用的根,必须加在Application上
  • @AndroidEntryPoint 让Activity/Fragment支持注入
  • @HiltViewModel 配合 @Inject constructor 实现ViewModel自动注入
  • @Module + @InstallIn 定义依赖提供方式
  • @Singleton 控制作用域,避免重复创建
⚠️ 常见错误
  1. 忘记在AndroidManifest中声明Application类
  2. 在super.onCreate()之前访问注入的变量
  3. ViewModel的构造函数参数没有全部用@Inject标记
  4. Module没有用@InstallIn指定安装位置
我曾经犯过第三个错误——漏了一个参数没加@Inject,结果编译报错说找不到构造函数。排查了半小时才发现。

好了,Hilt的配置和注入就讲到这里。你想想看,有了Hilt,我们再也不用手动管理依赖的生命周期了。代码更干净,测试也更方便。嗯,这就是我为什么推荐每个项目都用Hilt的原因。


公众号:蓝海资料掘金营,微信deep3321