Android Hilt 依赖注入最佳实践:从入门到架构
依赖注入(Dependency Injection)是现代 Android 架构的基石。从手写工厂到 Dagger 2 的编译期注入,再到 Hilt 的简化封装——这条路的终点是让开发者用最少的样板代码获得最可靠的依赖管理。Hilt 不是 Dagger 的替代品,而是站在 Dagger 肩膀上的「标准答案」,它把 Android 团队多年积累的最佳实践固化成了注解和约定。
本文从 DI 基础概念出发,逐步深入 Hilt 的每个核心机制,最终覆盖多模块架构、作用域管理、第三方库集成和测试策略。
一、依赖注入基础与 Hilt 架构
什么是依赖注入
一个类需要另一个类来完成工作,这种关系就是「依赖」。如果类 A 自己创建类 B 的实例,A 就和B的具体实现耦合了。依赖注入的核心思想是:不要自己创建依赖,让外部提供。
// ❌ 没有 DI:直接创建依赖,紧耦合
class OrderViewModel {
private val repository = OrderRepository(ApiService()) // 硬编码
}
// ✅ 有 DI:依赖通过构造函数注入
class OrderViewModel(
private val repository: OrderRepository // 外部提供
)
DI 的三种形式
| 形式 | 方式 | Hilt 是否使用 |
|---|---|---|
| 构造函数注入 | 通过构造函数参数传入依赖 | ✅ 主要方式 |
| 字段注入 | 通过 @Inject 标注字段,框架自动赋值 |
✅ Android 入口点专用 |
| 方法注入 | 通过方法参数传入依赖 | ❌ 极少使用 |
Hilt 组件层级树
Hilt 预定义了一组与 Android 生命周期对应的组件(Component),它们形成一棵层级树——子组件可以访问父组件的依赖:
SingletonComponent(Application 级别)
├── ActivityRetainedComponent(跨配置切换的 Activity 级别)
│ └── ViewModelComponent(ViewModel 级别)
├── ServiceComponent(Service 级别)
└── ActivityComponent(Activity 级别)
└── FragmentComponent(Fragment 级别)
└── ViewComponent(View 级别)
└── ViewWithFragmentComponent(带 Fragment 的 View)
二、核心注解速览
@HiltAndroidApp
标注 Application 子类,触发 Hilt 代码生成。这是 Hilt 的入口,每个项目必须且只能有一个。
@HiltAndroidApp
class MyApplication : Application() {
// Hilt 会自动生成 SingletonComponent
// 所有 @Singleton 作用域的依赖都在这里初始化
}
<application
android:name=".MyApplication"
...>
@AndroidEntryPoint
标注支持注入的 Android 类。Hilt 会为这些类生成注入代码。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var repository: UserRepository // 字段注入
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 此时 repository 已经被注入,可直接使用
val user = repository.getCurrentUser()
}
}
支持的 Android 类:
| Android 类 | 对应组件 | 注入方式 |
|---|---|---|
| Activity | ActivityComponent | 字段注入 |
| Fragment | FragmentComponent | 字段注入 |
| View | ViewComponent | 字段注入 |
| Service | ServiceComponent | 字段注入 |
| BroadcastReceiver | ActivityComponent | 字段注入 |
@Inject
两种用法:
// 1. 构造函数注入:告诉 Hilt 如何创建这个类的实例
class UserRepository @Inject constructor(
private val apiService: ApiService,
private val dao: UserDao
) {
// Hilt 会自动解析 ApiService 和 UserDao 的依赖
}
// 2. 字段注入:在 @AndroidEntryPoint 标注的类中使用
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var repository: UserRepository
}
@Inject 标注的字段必须是 lateinit var,不能是 private。Hilt 需要直接赋值。
@Module 和 @InstallIn
当类的构造函数不由你控制(第三方库、接口、抽象类),就需要用 Module 告诉 Hilt 如何提供实例。
@Module
@InstallIn(SingletonComponent::class) // 安装到 Application 级别
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
@InstallIn 决定了 Module 的生命周期和可见范围:
| @InstallIn 参数 | 生命周期 | 典型用途 |
|---|---|---|
SingletonComponent::class |
Application 存活期间 | Retrofit、Room、OkHttpClient |
ActivityRetainedComponent::class |
Activity 跨配置切换 | 跨旋转保持的数据 |
ViewModelComponent::class |
ViewModel 存活期间 | ViewModel 专属依赖 |
ActivityComponent::class |
Activity 存活期间 | Activity 级别的 Presenter |
FragmentComponent::class |
Fragment 存活期间 | Fragment 级别的 Adapter |
ViewComponent::class |
View 存活期间 | 自定义 View 的依赖 |
ServiceComponent::class |
Service 存活期间 | Service 的 Worker |
三、@Binds vs @Provides、@Qualifier 自定义限定符
@Binds:接口绑定到实现
当你有一个接口和多个实现时,用 @Binds 告诉 Hilt 绑定哪一个。
interface Logger {
fun log(message: String)
}
class FileLogger @Inject constructor(
private val fileWriter: FileWriter
) : Logger {
override fun log(message: String) { fileWriter.write(message) }
}
@Module
@InstallIn(SingletonComponent::class)
abstract class LoggerModule {
@Binds
@Singleton
abstract fun bindLogger(impl: FileLogger): Logger
// 注意:@Binds 方法必须是 abstract,参数是实现类,返回值是接口
}
@Provides:手动提供实例
当你需要构造逻辑(Builder 模式、工厂方法、第三方库对象)时,用 @Provides。
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideRoomDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app-database"
).build()
}
@Provides
fun provideUserDao(db: AppDatabase): UserDao = db.userDao()
}
@Binds vs @Provides 对比
| 特性 | @Binds | @Provides |
|---|---|---|
| 方法类型 | abstract |
concrete(可写在 object class) |
| Module 类型 | abstract class |
object 或 class |
| 适用场景 | 接口 → 实现绑定 | 需要构造逻辑的对象 |
| 性能 | 略优(编译期直接映射) | 运行时调用方法 |
| 能否访问其他依赖 | ❌ 不能 | ✅ 可以通过参数注入 |
@Binds 和 @Provides,只需将 Module 声明为 abstract class,@Provides 方法用伴生对象标注:
@Module
@InstallIn(SingletonComponent::class)
abstract class AppModule {
@Binds
abstract fun bindRepository(impl: DefaultRepository): Repository
companion object {
@Provides
fun provideApiKey(): String = BuildConfig.API_KEY
}
}
@Qualifier:同一类型多个绑定
当同一类型需要提供不同实例时(比如两个不同的 Retrofit、两个不同的 String),用 @Qualifier 区分。
// 1. 定义限定符注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthRetrofit
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ApiRetrofit
// 2. 在 @Provides 中标注
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@AuthRetrofit
@Provides
@Singleton
fun provideAuthRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://auth.example.com")
.build()
@ApiRetrofit
@Provides
@Singleton
fun provideApiRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
// 3. 注入时指定限定符
class AuthRepository @Inject constructor(
@AuthRetrofit private val retrofit: Retrofit
)
@ApplicationContext 和 @ActivityContext 来区分两种 Context,不需要自己定义:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
fun provideCacheDir(@ApplicationContext context: Context): File {
return context.cacheDir
}
}
四、ViewModel 注入
@HiltViewModel 基本用法
Hilt 通过 @HiltViewModel 让 ViewModel 的构造函数注入变得极为简洁:
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String? = savedStateHandle["userId"]
val userInfo: StateFlow<UiState<User>> = repository.getUser(userId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState.Loading)
}
// Activity / Fragment 中获取
@AndroidEntryPoint
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
// Hilt 自动处理 ViewModelProvider.Factory
}
SavedStateHandle 的用途
SavedStateHandle 是 ViewModel 的「安全箱」,用于保存进程被系统回收后需要恢复的数据:
@HiltViewModel
class DetailViewModel @Inject constructor(
private val repository: ItemRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
// 从 Navigation 参数中获取 itemId
private val itemId: String = savedStateHandle["itemId"]!!
// 保存临时编辑状态
fun saveDraft(draft: String) {
savedStateHandle["draft"] = draft
}
fun getDraft(): String? {
return savedStateHandle["draft"]
}
}
Fragment 中共享 ViewModel
@AndroidEntryPoint
class ListFragment : Fragment() {
// 各 Fragment 独立的 ViewModel
private val viewModel: ListViewModel by viewModels()
// 与 Activity 共享的 ViewModel
private val sharedViewModel: SharedViewModel by activityViewModels()
}
@AndroidEntryPoint
class DetailFragment : Fragment() {
// 同一 Activity 下的 Fragment 共享同一个 SharedViewModel
private val sharedViewModel: SharedViewModel by activityViewModels()
}
ViewModelProvider.Factory,构造函数中的所有依赖 Hilt 自动解析。SavedStateHandle 也由 Hilt 自动注入,不需要额外配置。
五、Hilt 与 Navigation 集成
@HiltNavGraphViewModel:Navigation 场景下的 ViewModel
在 Navigation Graph 中,ViewModel 的作用域默认是 NavGraph 级别的。Hilt 提供了 @HiltNavGraphViewModel 来配合 Navigation 场景:
// 1. 在 NavGraph 中定义目标
@HiltNavGraphViewModel
class OrderViewModel @Inject constructor(
private val orderRepository: OrderRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val orderId: String? = savedStateHandle["orderId"]
// ...
}
Navigation 参数传递
// 1. 定义 NavGraph 路由(带参数)
composable(
route = "detail/{itemId}",
arguments = listOf(navArgument("itemId") { type = NavType.StringType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
// itemId 会自动通过 SavedStateHandle 注入到 ViewModel
}
// 2. ViewModel 中通过 SavedStateHandle 获取
@HiltViewModel
class DetailViewModel @Inject constructor(
savedStateHandle: SavedStateHandle
) : ViewModel() {
val itemId: String = savedStateHandle["itemId"] ?: ""
}
// 3. 导航时传参
navController.navigate("detail/${item.id}")
NavGraph 作用域的 ViewModel
// 在 Navigation Graph 范围内共享 ViewModel
// 适合多页面共享状态的场景(如多步注册流程)
@HiltViewModel
class CheckoutViewModel @Inject constructor(
private val cartRepository: CartRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
// 购物车状态在 checkout 子图中共享
}
// Fragment 中获取 NavGraph 作用域的 ViewModel
val checkoutViewModel: CheckoutViewModel by hiltNavGraphViewModels(R.id.checkout_nav_graph)
by viewModels()— 当前 Fragment 独享by activityViewModels()— 同一 Activity 下所有 Fragment 共享by hiltNavGraphViewModels(R.id.nav_graph)— 同一 NavGraph 下共享
六、多模块项目中的 Hilt
模块划分原则
app/
├── app/ # 主模块(Application 入口)
├── core/ # 核心模块(网络、数据库、工具类)
│ ├── core-network/
│ ├── core-database/
│ └── core-common/
└── feature/ # 功能模块
├── feature-auth/
├── feature-home/
└── feature-profile/
各模块的 Module 安装策略
// core-network 模块
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides @Singleton
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()
@Provides @Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit = /* ... */
}
// core-database 模块
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides @Singleton
fun provideAppDatabase(@ApplicationContext ctx: Context): AppDatabase = /* ... */
}
// feature-auth 模块
@Module
@InstallIn(ViewModelComponent::class)
object AuthModule {
@Provides
fun provideAuthRepository(api: AuthApi): AuthRepository = /* ... */
}
@EntryPoint:在非 Hilt 管理的类中获取依赖
有时候你需要在 Hilt 不直接管理的类(如 ContentProvider、WorkManager Worker、第三方回调)中获取依赖。@EntryPoint 就是你的后门。
// 1. 定义 EntryPoint 接口
@EntryPoint
@InstallIn(SingletonComponent::class)
interface UserRepositoryEntryPoint {
fun userRepository(): UserRepository
}
// 2. 在非 Hilt 管理的类中使用
class SyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// 通过 EntryPoint 获取依赖
val entryPoint = EntryPointAccessors.fromApplication(
applicationContext,
UserRepositoryEntryPoint::class.java
)
val repository = entryPoint.userRepository()
repository.syncData()
return Result.success()
}
}
Feature 模块隔离
@Binds 将接口绑定到具体实现。这样 feature 模块之间互不依赖,只依赖 core 模块的接口。
// feature-auth 模块暴露接口
interface AuthRepository {
suspend fun login(email: String, password: String): Result<User>
}
// feature-auth 模块内部实现
class AuthRepositoryImpl @Inject constructor(
private val authApi: AuthApi
) : AuthRepository {
override suspend fun login(email: String, password: String) = /* ... */
}
// feature-auth 模块内部 Module
@Module
@InstallIn(SingletonComponent::class)
abstract class AuthModule {
@Binds @Singleton
abstract fun bindAuthRepository(impl: AuthRepositoryImpl): AuthRepository
}
七、作用域管理
作用域注解与组件对应关系
| 作用域注解 | 对应组件 | 生命周期 | 典型用途 |
|---|---|---|---|
@Singleton |
SingletonComponent | Application 全生命周期 | Retrofit、Room、OkHttpClient |
@ActivityRetainedScoped |
ActivityRetainedComponent | Activity 跨配置切换 | 跨旋转保持的数据管理器 |
@ViewModelScoped |
ViewModelComponent | ViewModel 存活期间 | ViewModel 专属的 UseCase |
@ActivityScoped |
ActivityComponent | Activity 一次生命周期 | Activity 级别的 Presenter |
@FragmentScoped |
FragmentComponent | Fragment 存活期间 | Fragment 专属 Adapter |
@ViewScoped |
ViewComponent | View 存活期间 | 自定义 View 的依赖 |
@ServiceScoped |
ServiceComponent | Service 存活期间 | Service 的 Worker |
作用域的本质
作用域注解的作用只有一个:同一组件实例内,标注了相同作用域的依赖只创建一次。
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Singleton // ✅ 整个 App 只创建一次
@Provides
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()
// ❌ 没有 @Singleton,每次注入都创建新实例
@Provides
fun provideGson(): Gson = Gson()
}
@Singleton 不等于全局单例!它的含义是「在 SingletonComponent 的生命周期内单例」。如果 @Module 安装在 ActivityComponent 中,@Singleton 不会生效(编译会报错)。作用域注解必须与 @InstallIn 的组件匹配。
作用域不匹配的编译错误
// ❌ 编译错误:@Singleton 只能用于 SingletonComponent
@Module
@InstallIn(ActivityComponent::class)
object BadModule {
@Singleton // 错误!应该用 @ActivityScoped
@Provides
fun providePresenter(): Presenter = Presenter()
}
// ✅ 正确:作用域与组件匹配
@Module
@InstallIn(ActivityComponent::class)
object GoodModule {
@ActivityScoped
@Provides
fun providePresenter(): Presenter = Presenter()
}
无作用域 vs 有作用域
// 无作用域:每次请求都创建新实例
@Provides
fun provideFormatter(): DateFormatter = DateFormatter()
// 有作用域:同一组件内复用实例
@Singleton
@Provides
fun provideDatabase(@ApplicationContext ctx: Context): AppDatabase = /* ... */
- 创建成本高的对象(网络客户端、数据库)→
@Singleton - 需要保持状态的对象(缓存、计数器)→ 对应组件作用域
- 无状态工具类(Gson、DateFormatter)→ 通常不需要作用域
八、第三方库集成
Retrofit
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(
@ApplicationContext context: Context,
loggingInterceptor: HttpLoggingInterceptor
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.cache(Cache(context.cacheDir, 10 * 1024 * 1024)) // 10MB 缓存
.connectTimeout(30, TimeUnit.SECONDS)
.build()
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
}
Room
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Singleton
@Provides
fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATIONS)
.build()
@Provides
fun provideUserDao(db: AppDatabase): UserDao = db.userDao()
@Provides
fun provideOrderDao(db: AppDatabase): OrderDao = db.orderDao()
}
DataStore
@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {
@Singleton
@Provides
fun providePreferencesDataStore(
@ApplicationContext context: Context
): DataStore<Preferences> = PreferenceDataStoreFactory.create(
storageFile = { context.preferencesDataStoreFile("settings") }
)
// 如果使用 Proto DataStore
@Singleton
@Provides
fun provideProtoDataStore(
@ApplicationContext context: Context
): DataStore<UserPreferences> = DataStoreFactory.create(
serializer = UserPreferencesSerializer,
produceFile = { context.dataStoreFile("user_prefs.pb") }
)
}
OkHttp @Interceptor 注入
拦截器有时需要访问依赖(如 Token 刷新),需要单独提供:
// 1. 定义拦截器(构造函数注入)
class AuthInterceptor @Inject constructor(
private val tokenManager: TokenManager
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${tokenManager.getToken()}")
.build()
return chain.proceed(request)
}
}
// 2. 在 Module 中提供 OkHttpClient
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(
authInterceptor: AuthInterceptor,
loggingInterceptor: HttpLoggingInterceptor
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(authInterceptor) // 应用拦截器
.addNetworkInterceptor(loggingInterceptor) // 网络拦截器
.build()
}
addInterceptor 添加应用拦截器(先执行),addNetworkInterceptor 添加网络拦截器(后执行)。Auth 拦截器通常放在应用拦截器层,Logging 放在网络拦截器层。
集成模板汇总
| 库 | @InstallIn | 作用域 | 提供方式 |
|---|---|---|---|
| Retrofit | SingletonComponent | @Singleton | @Provides |
| OkHttpClient | SingletonComponent | @Singleton | @Provides |
| Room Database | SingletonComponent | @Singleton | @Provides |
| Room DAO | SingletonComponent | 无作用域 | @Provides |
| DataStore | SingletonComponent | @Singleton | @Provides |
| Gson / Moshi | SingletonComponent | 可选 @Singleton | @Provides |
| CoroutineScope | SingletonComponent | @Singleton | @Provides |
九、测试最佳实践
替换模块:@TestInstallIn
测试时,你需要用假实现替换真实依赖(如把 Retrofit API 替换为内存实现)。@TestInstallIn 是官方推荐方式:
// 生产代码中的 Module
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@Provides @Singleton
fun provideUserRepository(api: UserApi): UserRepository =
RealUserRepository(api)
}
// 测试中替换为假实现
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [RepositoryModule::class] // 替换目标
)
object TestRepositoryModule {
@Provides @Singleton
fun provideUserRepository(): UserRepository =
FakeUserRepository() // 不依赖网络
}
单元测试:不启动 Hilt
纯单元测试不需要 Hilt。直接构造被测类,手动传入依赖:
@Test
fun `load user returns success`() = runTest {
// 手动构造假依赖
val fakeRepository = FakeUserRepository()
fakeRepository.addUser(User(id = "1", name = "Test"))
// 直接创建 ViewModel,不走 Hilt
val viewModel = UserViewModel(fakeRepository, SavedStateHandle())
// 验证
val state = viewModel.userInfo.first()
assertTrue(state is UiState.Success)
assertEquals("Test", (state as UiState.Success).data.name)
}
Hilt Android 测试
@HiltAndroidTest // 启用 Hilt 测试
@RunWith(AndroidJUnit4::class)
class UserActivityTest {
@get:Rule
var hiltRule = HiltAndroidRule(this) // 必须添加
@Inject
lateinit var repository: UserRepository // 注入依赖
@Before
fun init() {
hiltRule.inject() // 触发注入
}
@Test
fun displaysUserName() {
// 先通过注入的 repository 设置数据
(repository as FakeUserRepository).addUser(
User(id = "1", name = "Alice")
)
// 启动 Activity
val scenario = ActivityScenario.launch(UserActivity::class.java)
// 验证 UI
onView(withText("Alice")).check(matches(isDisplayed()))
}
}
@UninstallModules:临时卸载模块
@HiltAndroidTest
@UninstallModules(NetworkModule::class) // 临时卸载网络模块
class OfflineTest {
@Module
@InstallIn(SingletonComponent::class)
object TestNetworkModule {
@Provides @Singleton
fun provideRetrofit(): Retrofit = throw RuntimeException("不应访问网络")
}
// 测试离线场景
}
测试策略对比
| 测试类型 | 使用 Hilt? | 替换策略 | 速度 |
|---|---|---|---|
| 纯单元测试 | ❌ 不使用 | 手动构造假依赖 | ⚡ 快 |
| Hilt 集成测试 | ✅ @HiltAndroidTest |
@TestInstallIn |
🐢 较慢 |
| 临时替换单个模块 | ✅ @UninstallModules |
在测试类内定义替代 Module | 🐢 较慢 |
| 端到端测试 | ✅ 完整 Hilt | 只替换外部服务 | 🐌 最慢 |
十、常见陷阱与速查表
陷阱 1:忘记 @HiltAndroidApp
// ❌ 编译通过但运行时崩溃
class MyApplication : Application() { // 缺少 @HiltAndroidApp
}
// ✅ 必须标注
@HiltAndroidApp
class MyApplication : Application() {
}
陷阱 2:在 @AndroidEntryPoint 类的父类中注入
// ❌ Hilt 不支持在父类中字段注入
open class BaseActivity : AppCompatActivity() {
@Inject lateinit var repository: Repository // 不会被注入!
}
@AndroidEntryPoint
class MainActivity : BaseActivity() {
// repository 是 null
}
// ✅ 在 @AndroidEntryPoint 标注的类中注入
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var repository: Repository // ✅ 正常注入
}
@AndroidEntryPoint,则父类的字段注入也是有效的。关键是 @AndroidEntryPoint 必须出现在字段注入所在的类或其父类上。
陷阱 3:作用域与 @InstallIn 不匹配
// ❌ 编译错误
@Module
@InstallIn(ActivityComponent::class)
object BadModule {
@Singleton // 错误!Singleton 只能用于 SingletonComponent
@Provides fun provideX(): X = X()
}
// ✅ 作用域与组件匹配
@Module
@InstallIn(ActivityComponent::class)
object GoodModule {
@ActivityScoped
@Provides fun provideX(): X = X()
}
陷阱 4:接口没有 @Binds 却期望自动注入
// ❌ Hilt 不知道要绑定哪个实现
interface Repository { fun getData(): String }
class RealRepository @Inject constructor() : Repository { /* ... */ }
// 注入 Repository 会报错:没有绑定
// ✅ 使用 @Binds 告诉 Hilt
@Module
@InstallIn(SingletonComponent::class)
abstract class RepoModule {
@Binds @Singleton
abstract fun bindRepository(impl: RealRepository): Repository
}
陷阱 5:在 object class 中使用 @Binds
// ❌ @Binds 不能在 object class 中
@Module
@InstallIn(SingletonComponent::class)
object BadModule {
@Binds // 编译错误!@Binds 必须在 abstract 方法中
fun bindRepo(impl: RealRepo): Repository = impl
}
// ✅ @Binds 在 abstract class 中
@Module
@InstallIn(SingletonComponent::class)
abstract class GoodModule {
@Binds @Singleton
abstract fun bindRepo(impl: RealRepo): Repository
}
陷阱 6:多模块中 Module 重复绑定
如果 core-network 和 feature-auth 都绑定了 ApiService,编译会报重复绑定。解决方案:
// 方案1:只在 core 模块绑定,feature 模块直接使用
// core-network 模块
@Module
@InstallIn(SingletonComponent::class)
object CoreNetworkModule {
@Provides @Singleton
fun provideApiService(retrofit: Retrofit): ApiService = retrofit.create()
}
// 方案2:用 @Qualifier 区分不同的 ApiService
@Qualifier
annotation class AuthApi
@Qualifier
annotation class MainApi
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@AuthApi @Provides @Singleton
fun provideAuthApi(retrofit: Retrofit): ApiService = retrofit.create()
@MainApi @Provides @Singleton
fun provideMainApi(retrofit: Retrofit): ApiService = retrofit.create()
}
速查表
| 场景 | 做法 | 关键注解 |
|---|---|---|
| Application 级别单例 | @InstallIn(SingletonComponent::class) + @Singleton |
@HiltAndroidApp |
| Activity 中注入依赖 | @AndroidEntryPoint + @Inject lateinit |
字段不能 private |
| 接口绑定实现 | @Module + @Binds |
abstract class |
| 第三方对象提供 | @Module + @Provides |
object class |
| 同一类型多个实例 | 自定义 @Qualifier |
@Qualifier + @Retention |
| ViewModel 注入 | @HiltViewModel + @Inject constructor |
by viewModels() |
| 非 Hilt 类获取依赖 | @EntryPoint 接口 |
EntryPointAccessors |
| 测试替换模块 | @TestInstallIn(replaces = [...]) |
@HiltAndroidTest |
| 单元测试 | 不用 Hilt,手动构造 | 直接传参 |
| 区分两种 Context | @ApplicationContext / @ActivityContext |
预定义限定符 |
| Navigation 参数传递 | SavedStateHandle 自动注入 |
@HiltViewModel |
@HiltAndroidApp(入口)+ @AndroidEntryPoint(注入点)+ @Module/@InstallIn(绑定规则)+ @Scope(生命周期)。记住这条链路,其他都是衍生。