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的核心组件层级。我建议你仔细看看,理解组件之间的父子关系,这对排查问题很有帮助。
六、实战:完整的注入示例
我们来看一个完整的例子。假设有一个用户列表页面,需要从网络获取数据。
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 控制作用域,避免重复创建
- 忘记在AndroidManifest中声明Application类
- 在super.onCreate()之前访问注入的变量
- ViewModel的构造函数参数没有全部用@Inject标记
- Module没有用@InstallIn指定安装位置
好了,Hilt的配置和注入就讲到这里。你想想看,有了Hilt,我们再也不用手动管理依赖的生命周期了。代码更干净,测试也更方便。嗯,这就是我为什么推荐每个项目都用Hilt的原因。
公众号:蓝海资料掘金营,微信deep3321