CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Android 开发入门/课时/依赖注入
RawBlame
课时目录12 节

Android 开发入门

  1. 01Android 开发概览
  2. 02四大组件
  3. 03传统 View UI
  4. 04Jetpack Compose
  5. 05架构模式
  6. 06依赖注入
  7. 07本地存储
  8. 08网络层
  9. 09异步与响应式
  10. 10Firebase 生态
  11. 11测试与质量
  12. 12综合练习

依赖注入

Hilt 与 Koin。

25 分钟

依赖注入

这一课你将能做到

  1. 解释依赖注入(DI)解决什么问题,以及控制反转(IoC)的含义
  2. 使用 Hilt 完成 ViewModel、Repository、网络层的注入配置
  3. 对比 Hilt 与 Koin 的优劣,知道在项目中如何选择

为什么要学

依赖注入是「组装代码的艺术」。没有 DI,你需要手动 new 出每一层对象,传递十几层参数,改一个构造函数要改十处。DI 框架自动完成这些,让你专注业务逻辑。对于 Web 前端,DI 就像 React 的 Context + Provider,但类型安全、编译期检查。

人话:DI 就是「不要自己找对象,让框架给你介绍」。你只需要声明「我需要一个 TrackRepository」,Hilt 自动把 Retrofit、Room、OkHttp 都组装好送过来。

面试中,「为什么需要 DI」、「Hilt 的作用域」、「Koin vs Hilt」是高频考点。

先建立图像

没有 DI(手动组装):
┌────────────────────────────────────────┐
│  MainActivity                          │
│    ↓ new TrackRepository()             │
│      ↓ new Retrofit.Builder()...       │
│        ↓ new OkHttpClient()            │
│          ↓ new LoggingInterceptor()    │
│            ↓ ...十几层                  │
│  改一个参数,全链路修改                   │
└────────────────────────────────────────┘

有 DI(Hilt 自动组装):
┌────────────────────────────────────────┐
│  @HiltAndroidApp                       │
│    ↓ 自动扫描                           │
│  ┌─────────┐  ┌─────────┐  ┌────────┐ │
│  │@Singleton│  │@Singleton│  │@ViewModel│ │
│  │ Retrofit │→│TrackApi  │→│TrackVm   │ │
│  └─────────┘  └─────────┘  └────────┘ │
│       ↑ 注入    ↑ 注入       ↑ 注入     │
│  改配置只改一处,全链路自动生效            │
└────────────────────────────────────────┘

跟着做

步骤 1:Hilt 环境配置

build.gradle.kts(项目级):

plugins {
    id("com.google.dagger.hilt.android") version "2.51.1" apply false
}

build.gradle.kts(app 模块):

plugins {
    id("com.google.dagger.hilt.android")
    kotlin("kapt")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.51.1")
    kapt("com.google.dagger:hilt-android-compiler:2.51.1")
    implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
}

AndroidManifest.xml:

<application
    android:name=".CodeRoadMapApp"
    ...>

Application 类:

@HiltAndroidApp
class CodeRoadMapApp : Application()

步骤 2:网络层 Module

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(client: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.coderoadmap.com/")
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    @Singleton
    fun provideTrackApi(retrofit: Retrofit): TrackApiService {
        return retrofit.create(TrackApiService::class.java)
    }
}

步骤 3:数据库层 Module

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "coderoadmap.db"
        ).build()
    }

    @Provides
    fun provideTrackDao(database: AppDatabase): TrackDao {
        return database.trackDao()
    }
}

步骤 4:Repository 注入

@Singleton
class TrackRepository @Inject constructor(
    private val api: TrackApiService,
    private val dao: TrackDao,
    private val progressStore: ProgressDataStore
) {
    // 与上一课相同,构造函数参数由 Hilt 自动注入
}

步骤 5:ViewModel 与 Compose 注入

@HiltViewModel
class TrackListViewModel @Inject constructor(
    private val repository: TrackRepository
) : ViewModel() {
    // ...
}

// Compose 中使用
@Composable
fun TrackListScreen(
    viewModel: TrackListViewModel = hiltViewModel()
) {
    // ...
}

Activity 需要 @AndroidEntryPoint:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CodeRoadMapTheme {
                TrackListScreen()
            }
        }
    }
}

步骤 6:Qualifier 区分同类型

当需要提供多个同类型实例时:

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptor

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class LoggingInterceptor

@Module
@InstallIn(SingletonComponent::class)
object InterceptorModule {

    @AuthInterceptor
    @Provides
    fun provideAuthInterceptor(): Interceptor {
        return Interceptor { chain ->
            val request = chain.request().newBuilder()
                .addHeader("Authorization", "Bearer token")
                .build()
            chain.proceed(request)
        }
    }

    @LoggingInterceptor
    @Provides
    fun provideLoggingInterceptor(): Interceptor {
        return HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }
    }
}

// 使用
@Provides
fun provideOkHttpClient(
    @AuthInterceptor auth: Interceptor,
    @LoggingInterceptor logging: Interceptor
): OkHttpClient {
    return OkHttpClient.Builder()
        .addInterceptor(auth)
        .addInterceptor(logging)
        .build()
}

步骤 7:Koin 快速对比(了解)

Koin 是纯 Kotlin 的轻量 DI,无注解处理器:

// 定义模块
val appModule = module {
    single { OkHttpClient.Builder().build() }
    single { Retrofit.Builder().client(get()).build() }
    single { get<Retrofit>().create(TrackApiService::class.java) }
    single { TrackRepository(get(), get(), get()) }
    viewModel { TrackListViewModel(get()) }
}

// Application 中启动
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApp)
            modules(appModule)
        }
    }
}

// Compose 中使用
@Composable
fun TrackListScreen(
    viewModel: TrackListViewModel = koinViewModel()
) { }

讲清楚要点

  1. DI 的核心是控制反转:对象不自己创建依赖,而是被动接收。这降低了耦合,提高了可测试性(可以注入 Mock 实现)。

  2. Hilt 的作用域决定生命周期:@Singleton 全应用单例;@ActivityScoped 与 Activity 同生共死;@ViewModelScoped 与 ViewModel 绑定。选错作用域会导致内存泄漏或数据不同步。

  3. Hilt 是编译期,Koin 是运行时:Hilt 用注解处理器生成代码,编译期报错,性能更好;Koin 用 DSL 运行时解析,启动稍慢但写法更简洁。大型项目推荐 Hilt,小型项目或快速原型可用 Koin。

  4. @Binds vs @Provides:@Binds 用于接口绑定实现(更简洁,生成代码更少);@Provides 用于需要自定义创建逻辑的场景(如 Retrofit.Builder)。

  5. AssistedInject 处理运行时参数:当 ViewModel 需要运行时参数(如 trackSlug)时,用 @AssistedInject 或 SavedStateHandle。

你可能会懵的地方

  • 为什么 Hilt 需要 kapt? Hilt 基于 Dagger,Dagger 用注解处理器生成工厂代码。kapt 是 Kotlin 的注解处理工具,把 Kotlin 注解转成 Java 能理解的格式。

  • @HiltViewModel 和 @AndroidEntryPoint 什么关系? @HiltViewModel 标记 ViewModel 可被注入;@AndroidEntryPoint 标记 Android 组件(Activity/Fragment)可接收注入。两者配合完成 hiltViewModel() 的解析。

  • 单元测试怎么替换依赖? Hilt 提供 @TestInstallIn 替换 Module;或者不用 Hilt,直接手动 new ViewModel 传入 Mock Repository。后者更简单,推荐用于纯逻辑测试。

  • Koin 真的比 Hilt 差吗? 不是「差」,是「不同」。Koin 无 kapt、无生成代码、增量编译快,适合 Kotlin Multiplatform。Hilt 生态更成熟,与 Navigation、WorkManager 集成更好。

  • DI 会导致运行时性能下降吗? Hilt 是编译期生成,运行时零反射,性能与手写 new 无异。Koin 有少量运行时开销,但对 App 启动时间影响通常在 10ms 以内。

错法对照

错法问题正解
在 @Provides 里做耗时操作阻塞主线程,启动慢只创建对象,不执行逻辑
滥用 @Singleton内存常驻,无法释放按实际生命周期选作用域
忘加 @AndroidEntryPoint运行时崩溃 IllegalStateExceptionActivity/Fragment/Service 必须加
在 @Binds 方法里写逻辑@Binds 必须是抽象方法需要逻辑时用 @Provides
用 lateinit 注入后手动赋值破坏 DI 语义,易出错让框架管理,不要手动干预

卡点急救

  1. 编译报错「 cannot be provided without an @Inject constructor」:检查类是否有 @Inject 构造函数,或是否忘记在 Module 中提供该类型。

  2. 注入的 Repository 是 null:通常是 kapt 没跑成功。执行 Build → Clean Project → Rebuild Project,或检查 build.gradle 中 kapt 插件顺序。

  3. 多个 Module 提供同类型冲突:用 @Qualifier 区分,或检查是否误加了多个 @Provides 方法返回同类型。

面试常问

  • 为什么需要依赖注入?

    1. 解耦:类不依赖具体实现,只依赖接口;2. 可测试:测试时注入 Mock;3. 可维护:修改实现不需要改调用方;4. 生命周期管理:框架自动处理对象创建和销毁。
  • Hilt 的 @Singleton 和 @ActivityScoped 区别? @Singleton 全应用唯一,进程存活期间一直存在;@ActivityScoped 与 Activity 生命周期绑定,Activity 销毁时释放。用错会导致内存泄漏(如 @Singleton 持有 Activity Context)。

  • Dagger、Hilt、Koin 的关系? Dagger 是 Google 的编译期 DI 框架,功能强但配置复杂。Hilt 是 Dagger 的 Android 封装,预定义组件和作用域,简化配置。Koin 是轻量级运行时 DI,纯 Kotlin,无注解处理,适合小型项目和 KMP。

自测三问

  1. 解释 @Provides 和 @Binds 的使用场景差异。
  2. 为什么 @HiltViewModel 需要配合 hiltViewModel() 或 viewModel() 使用?
  3. 设计一个场景:需要两个不同配置的 Retrofit 实例,如何实现?

小结一句话

依赖注入是「让框架组装对象」的艺术;Hilt 用编译期安全换取企业级可靠性,Koin 用简洁换取开发效率;选对工具,让架构真正落地。

延伸

  • Hilt 官方文档
  • Koin 官方文档
  • 下一课:本地存储——Room、DataStore 与文件系统。
加载本节练习…
← 上一课架构模式下一课 →本地存储