Android依赖注入实战:从Dagger到Hilt的进化之路
# 前言
作为一名Android开发者,你是否也曾陷入这样的困境:
"这个类需要另一个类的实例,我应该在哪里创建它?直接在构造函数里new吗?还是通过某个工厂方法?"
或者更糟的是:
"这个类的依赖太复杂了,构造函数参数已经多到数不清了..."
这些问题其实都指向同一个解决方案——依赖注入(Dependency Injection, DI)。🤔
提示
依赖注入是一种设计模式,它将组件的依赖关系从组件内部转移到外部,通过外部提供依赖的方式,使组件更加解耦、可测试和可维护。
在Android开发中,依赖注入框架帮助我们管理对象的生命周期和依赖关系,让代码更加清晰和可维护。今天,我们就来深入探讨Android中最流行的依赖注入框架:从Dagger到Hilt的进化之路。
# 依赖注入的基本概念
在开始之前,让我们先理解几个关键概念:
- 依赖(Dependency):一个类需要另一个类来完成它的功能,这个被需要的类就是依赖。
- 依赖注入(DI):不是让类自己创建依赖,而是由外部框架将依赖"注入"到类中。
// 传统方式 - 类自己创建依赖
class UserRepository {
private val apiService = RetrofitClient.createApiService()
fun fetchData(): String {
return apiService.getData()
}
}
// 依赖注入方式 - 依赖由外部提供
class UserRepository(private val apiService: ApiService) {
fun fetchData(): String {
return apiService.getData()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通过这种方式,UserRepository不再关心ApiService如何创建,只需专注于自己的业务逻辑。🎯
# Dagger:依赖注入的王者
在Hilt出现之前,Dagger是Android开发中最流行的依赖注入框架。它是Square公司基于Java注解处理器开发的高性能DI框架。
# Dagger的核心组件
Dagger主要由以下几个核心组件组成:
- @Module:定义如何提供依赖的类
- @Provides:在@Module中标记提供依赖的方法
- @Inject:标记需要依赖的构造函数或字段
- @Component:连接提供方和使用方的接口
@Module
class NetworkModule {
@Provides
fun provideApiService(): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
.create(ApiService::class.java)
}
}
@Component(modules = [NetworkModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
}
class MainActivity : AppCompatActivity() {
@Inject
lateinit var apiService: ApiService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appComponent = DaggerAppComponent.create()
appComponent.inject(this)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Dagger的挑战
尽管Dagger功能强大,但在Android项目中使用它也面临一些挑战:
- 配置复杂:需要编写大量的Module和Component
- 编译时间长:注解处理需要时间,大型项目可能需要几分钟
- 学习曲线陡峭:概念多,配置复杂,新手容易迷失
- Android特定支持不足:需要额外配置来支持Android组件的生命周期
# Hilt:为Android量身定制的DI框架
面对Dagger的挑战,Google与Square合作开发了Hilt——一个基于Dagger的、专为Android设计的依赖注入库。Hilt简化了Dagger在Android中的使用,提供了更简洁的API和更好的Android集成。
# Hilt的核心优势
- 自动生成组件:Hilt自动为Android组件创建相应的Hilt组件
- 简化配置:减少了大量样板代码
- 生命周期感知:自动管理依赖的生命周期
- 作用域绑定:将依赖与Android组件的生命周期绑定
# Hilt的基本使用
让我们看看如何用Hilt重写之前的例子:
// 定义接口
interface ApiService {
fun getData(): String
}
// 实现接口
class ApiServiceImpl @Inject constructor() : ApiService {
override fun getData(): String = "Data from API"
}
// 在ViewModel中使用
@HiltViewModel
class MainViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
fun fetchData(): String = apiService.getData()
}
// 在Activity中使用
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("MainActivity", viewModel.fetchData())
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
看到区别了吗?几乎不需要任何配置!Hilt会自动处理依赖的创建和注入。🚀
# Hilt的关键组件
# 1. @HiltAndroidApp
这是Hilt的入口点,需要在Application类上添加:
@HiltAndroidApp
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
}
}
2
3
4
5
6
# 2. @AndroidEntryPoint
标记需要依赖注入的Android组件:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// ...
}
2
3
4
支持的组件包括:
- Application
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
# 3. @Module 和 @InstallIn
定义自定义依赖提供方式:
@Module
@InstallIn(ActivityComponent::class) // 限定作用域为Activity
object ActivityModule {
@Provides
fun provideString(): String = "Hello from ActivityModule"
}
2
3
4
5
6
# 4. @ViewModelInject
在ViewModel中注入依赖:
@HiltViewModel
class MyViewModel @ViewModelInject constructor(
private val repository: MyRepository
) : ViewModel() {
// ...
}
2
3
4
5
6
# 5. 限定符(Qualifiers)
当有多个相同类型的依赖时,使用限定符来区分:
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class AuthInterceptor
@Module
@InstallIn(ActivityComponent::class)
object NetworkModule {
@Provides
@AuthInterceptor
fun provideAuthInterceptor(): Interceptor {
return Interceptor { chain ->
val request = chain.request()
.newBuilder()
.addHeader("Authorization", "Bearer token")
.build()
chain.proceed(request)
}
}
}
class MyViewModel @ViewModelInject constructor(
@AuthInterceptor private val authInterceptor: Interceptor
) : ViewModel() {
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Hilt的高级特性
# 1. 作用域绑定
Hilt将依赖与Android组件的生命周期绑定:
| 注解 | 绑定到的组件 | 生命周期 |
|---|---|---|
| @Singleton | ApplicationComponent | Application |
| @ActivityRetainedScoped | ActivityRetainingComponent | Activity的onCreate到onDestroy |
| @ActivityScoped | ActivityComponent | Activity的onCreate到onDestroy |
| @FragmentScoped | FragmentComponent | Fragment的onAttach到onDetach |
| @ViewScoped | ViewComponent | View的onCreate到onDestroy |
# 2. 预定义绑定
Hilt为Android组件提供了预定义绑定:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// 获取Application实例
private val application by inject<Application>()
// 获取Activity
private val activity by inject<Activity>()
// 获取ApplicationContext
private val context by inject<Context>()
// 获取Resources
private val resources by inject<Resources>()
// 获取SupportFragmentManager
private val supportFragmentManager by inject<FragmentManager>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用这些预定义绑定
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3. 多绑定
将多个相同类型的依赖收集到一个集合中:
@Module
@InstallIn(ActivityComponent::class)
object BindingsModule {
@Binds
@IntoMap
@ViewModelKey(UserViewModel::class)
fun bindUserViewModel(viewModel: UserViewModel): ViewModel = viewModel
@Binds
@IntoMap
@ViewModelKey(SettingsViewModel::class)
fun bindSettingsViewModel(viewModel: SettingsViewModel): ViewModel = viewModel
}
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
@HiltViewModel
class MainActivityViewModel @ViewModelInject constructor(
@ViewModelKey(UserViewModel::class) private val userViewModel: ViewModel,
@ViewModelKey(SettingsViewModel::class) private val settingsViewModel: ViewModel
) : ViewModel() {
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 实战案例:使用Hilt构建MVVM架构
让我们通过一个完整的示例,看看如何使用Hilt构建一个现代化的MVVM架构应用。
# 1. 添加依赖
首先,在app模块的build.gradle中添加Hilt依赖:
dependencies {
implementation 'com.google.dagger:hilt-android:2.45'
kapt 'com.google.dagger:hilt-compiler:2.45'
// 如果使用Kotlin KAPT
kapt 'androidx.hilt:hilt-compiler:1.0.0'
// 如果使用Navigation Component
implementation 'androidx.hilt:hilt-navigation-fragment:1.0.0'
}
2
3
4
5
6
7
8
9
10
# 2. 创建数据层
// Repository接口
interface UserRepository {
suspend fun getUsers(): List<User>
}
// Repository实现
class UserRepositoryImpl @Inject constructor(
private val apiService: ApiService,
private val userDao: UserDao
) : UserRepository {
override suspend fun getUsers(): List<User> {
// 先从数据库获取
val cachedUsers = userDao.getAllUsers()
if (cachedUsers.isNotEmpty()) {
return cachedUsers
}
// 如果数据库没有,从网络获取
val remoteUsers = apiService.getUsers()
userDao.insertAll(remoteUsers)
return remoteUsers
}
}
// ApiService
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
}
// UserDao
@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
@Insert
suspend fun insertAll(users: List<User>)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 3. 创建ViewModel
@HiltViewModel
class UserListViewModel @ViewModelInject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
fun loadUsers() {
viewModelScope.launch {
_users.value = repository.getUsers()
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4. 创建UI层
@AndroidEntryPoint
class UserListActivity : AppCompatActivity() {
private lateinit var binding: ActivityUserListBinding
private val viewModel: UserListViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserListBinding.inflate(layoutInflater)
setContentView(binding.root)
setupRecyclerView()
observeViewModel()
binding.refreshLayout.setOnRefreshListener {
viewModel.loadUsers()
}
}
private fun setupRecyclerView() {
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this@UserListActivity)
adapter = UserAdapter()
}
}
private fun observeViewModel() {
viewModel.users.observe(this) { users ->
(binding.recyclerView.adapter as UserAdapter).submitList(users)
binding.refreshLayout.isRefreshing = false
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 5. 配置Hilt Modules
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
@Module
@InstallIn(SingletonComponent::class)
abstract class DatabaseModule {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
@Provides
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Dagger迁移到Hilt的最佳实践
如果你已经在项目中使用Dagger并计划迁移到Hilt,以下是一些最佳实践:
# 1. 逐步迁移
不要一次性迁移整个项目,而是按模块或功能逐步迁移:
- 先在Application类中添加
@HiltAndroidApp - 逐步将Android组件迁移到
@AndroidEntryPoint - 将Dagger Module转换为Hilt Module
- 利用Hilt的预定义绑定简化代码
# 2. 保留Dagger组件
在迁移过程中,可以保留Dagger组件作为桥梁:
@Module
@InstallIn(ApplicationComponent::class)
object DaggerBridgeModule {
@Provides
@Singleton
fun provideLegacyDaggerComponent(): LegacyDaggerComponent {
return DaggerLegacyDaggerComponent.create()
}
}
2
3
4
5
6
7
8
9
10
# 3. 利用Hilt的测试支持
Hilt提供了优秀的测试支持:
@RunWith(AndroidJUnit4::class)
class UserViewModelTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Inject
lateinit var repository: TestRepository
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun getUsers_whenRepositoryReturnsUsers_updatesUiState() = runTest {
// 测试代码
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 结语
从Dagger到Hilt,我们看到依赖注入框架在Android开发中的进化之路。Hilt不仅保留了Dagger的强大功能,还针对Android开发场景进行了深度优化,大大简化了依赖注入的使用。
"代码应该是简单的,而不是复杂的。依赖注入帮助我们实现这一点。"
在现代Android开发中,掌握Hilt已经不再是加分项,而是必备技能。它帮助我们构建更加模块化、可测试和可维护的应用程序。
如果你还没有开始使用Hilt,我强烈建议你从今天就开始尝试。相信我,一旦你习惯了这种开发方式,你将再也回不去没有依赖注入的日子!😉
最后,记住:依赖注入不是银弹,它只是帮助我们写出更好代码的工具。合理使用它,而不是滥用它,才能真正发挥它的价值。
希望这篇文章能帮助你更好地理解和使用Hilt。如果你有任何问题或建议,欢迎在评论区留言交流!👋