一起来学Kotlin:概念:26. Dependency Injection依赖注入以及Hilt在Kotlin中的使用,系列2:手动依赖注入
此系列博客中,我们将主要介绍:
Dependency Injection
(依赖注入) 概念介绍。网上看了许多关于 DI 的介绍,云里雾里。这里,我们通过通俗易懂地方式对其进行介绍。- 手动依赖注入介绍。为了让大家更容易理解 Hilt,我们先介绍如何通过手动的方式实现依赖注入效果。
- Hilt 注释(annotations)介绍及使用案例
- MVVM 案例中如何使用 Hilt
此博客主要介绍手动依赖注入。
文章目录
- 一起来学Kotlin:概念:26. Dependency Injection依赖注入以及Hilt在Kotlin中的使用,系列2:手动依赖注入
- 1 回顾
- 2 Hilt 的定义
- 3 手动依赖注入:提供容器来管理各种依赖
- 4 手动依赖注入:手动管理生命周期
- 5 总结
1 回顾
在系列的第一篇博客中,我们介绍了依赖注入的概念,以及为什么需要依赖注入。
简单来说,在场景诸如对象A需要(依赖于)另外一个对象B的实例,将创建对象的任务转移给其他人并直接使用依赖称为依赖注入(DI)。我们在上一篇博客也以实例化汽车 Car 类为例子,解释如何防止这个汽车对象和其依赖的各种对象,例如车轮、引擎等耦合。
比如,下面这个例子,Car 这个实例被创建的时候,会新创建一个 Wheel 的实例,这就是耦合。DI 的目的也就是防止这种情况的发生。
class Car {<!-- -->
private val wheelA = Wheel()
fun start() {<!-- -->
engine.start()
}
}
fun main(args: Array) {<!-- -->
val car = Car()
car.start()
}
此外,我们也罗列了依赖注入的三种实现方式:
- constructor injection(类的构造函数注入):依赖项是通过类构造函数提供的。
- setter injection(类字段注入):客户端公开一个 setter 方法,注入器使用它来注入依赖项。
- interface injection:依赖项提供了一个注入器方法,可以将依赖项注入传递给它的任何客户端。 客户端必须实现一个接口,该接口公开一个接受依赖项的 setter 方法。
2 Hilt 的定义
Hilt provides a standard way to use DI in your application by providing containers for every Android class in your project and managing their lifecycles automatically.
从 Hilt 的定义中,我们可以总结,Hilt主要完成的两件事:
- 提供了“containers”(容器)用来装各种依赖;
- 自动管理这些“containers”(容器)的“lifecycles”(生命周期)。
在下面的章节中,我们会通过手动依赖注入,来解释上面的这两件事的含义,以及意义。
3 手动依赖注入:提供容器来管理各种依赖
和上一篇博客一样,我们也通过一些例子来解释手动依赖注入的概念。
下图是 MVVM 的一个基本架构:
上图中的箭头是单方向的,意思就是箭头的一端依赖于另外一端。比如,Activity/Fragment
依赖于 ViewModel
,而 ViewModel
依赖于 Repository
。在安卓的 MVVM架构里,依赖注入的意思就是把 ViewModel
的实例(instance)注入到 Activity/Fragment
类中,同样的道理,Repository
的实例注入到 ViewModel
类中。以此类推,Model
和 RemoteDataSource
的实例也需要注入到 Repository
类中。
实际上,我们通常做的,就是在 Activity/Fragment
里面直接 new 一个 ViewModel
。看起来很方便,但实际上,这是不是和上面的耦合例子非常类似?如果我们只有一个 Activity/Fragment
和一个依赖的 ViewModel
,那没什么问题,但如果关系复杂了,依赖注入的优势就很明显了。
比如说,现在需要实现一个用户登录的功能时,用MVVM架构应该是下面这样的:
这里,LoginActivity
依赖于 LoginViewModel
,LoginViewModel
依赖于 UserRepository
,UserRepository
依赖于 UserLocalDataSource
以及 UserRemoteDataSource
。UserRemoteDataSource
依赖于 Retrofit
。
在没有使用依赖注入思想的时候,LoginActivity
大致上应该是这样的:
class LoginActivity: Activity() {<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
super.onCreate(savedInstanceState)
// 实例化LoginViewModel
loginViewModel = LoginViewModel(userRepository)
}
}
为了满足 LoginViewModel
的实例化,我们需要传入一个 UserRepository
的实例参数 userRepository
。这还没有结束,因为 UserRepository
又依赖于 UserLocalDataSource
以及 UserRemoteDataSource
。一环扣一环。所以,我们如果把 LoginActivity
写完整,大致是这样的:
class LoginActivity: Activity() {<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
super.onCreate(savedInstanceState)
/******新增代码 Begin ******/
val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
val remoteDataSource = UserRemoteDataSource(retrofit)
val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
/******新增代码 End ******/
loginViewModel = LoginViewModel(userRepository)
}
}
上面代码中 “新增代码” 段包含了各个依赖之间一环扣一环的关系。所以,在 LoginActivity
类中,我们创建了所有相关的依赖的实例。但是跟我们最初设想的在 LoginActivity
类中只要有 loginViewModel
对象就行,其他的不需要出现在 LoginActivity
类中。我们需要的类似是如下干净的代码:
class LoginActivity: Activity() {<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
loginViewModel = XXXX
}
}
我们可以做的,就是新建一个 AppContainer
类,把之前新增的代码都扔到里面去:
class AppContainer {<!-- -->
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
}
这样新建的 AppContainer
类还不可以在 LoginActivity
类中使用,因为 AppContainer
类中的这些“依赖”,需要在整个应用(application )全局中使用,所以需要把 AppContainer
类的实例放到 Application()
的子类中:
我们新建一个 MyApplication
类,继承自 Application()
:
class MyApplication : Application() {<!-- -->
val appContainer = AppContainer()
}
这样一来,在 LoginActivity
中就可以这么用了:
class LoginActivity: Activity() {<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
// loginViewModel = XXXX 改成下面代码:
val appContainer = (application as MyApplication).appContainer
loginViewModel = LoginViewModel(appContainer.userRepository)
}
}
到此为止,在 LoginActivity
类中已经可以看出依赖注入的编码风格了。
我们在这里实现的 loginViewModel
对象,使用的就是构造函数注入(Constructor Injection)的依赖注入方式:loginViewModel = LoginViewModel(appContainer.userRepository)
。
更进一步的,假如在我们的安卓应用程序中,除了 LoginActivity
类,其他类也需要 LoginViewModel
的实例对象,那么我们就不能在 LoginActivity
类中新建 LoginViewModel
的实例对象了。还是老办法,作为其中的一个“依赖”,我们要把实现 LoginViewModel
的实例对象放到 “containers”(容器)中。在此,需要用到工厂模式的设计模式,新建一个 Factory
接口,然后在 LoginViewModelFactory
类中实现这个接口,并返回一个 LoginViewModel
的实例:
interface Factory<T> {<!-- -->
fun create(): T
}
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {<!-- -->
override fun create(): LoginViewModel {<!-- -->
return LoginViewModel(userRepository)
}
}
接下来的事情就简单了,我们把这个新建的 LoginViewModelFactory
工厂类,放到 AppContainer
中:
class AppContainer {<!-- -->
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
// 新建一个 loginViewModelFactory 对象,在整个application范围内都可以使用
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
然后在 LoginActivity
类中就可以直接使用了(而不是像之前那样,新建一个 LoginViewModel
的实例对象):
class LoginActivity: Activity() {<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
// loginViewModel = XXXX 改成下面代码:
val appContainer = (application as MyApplication).appContainer
//loginViewModel = LoginViewModel(appContainer.userRepository) 替换成下面代码:
loginViewModel = appContainer.loginViewModelFactory.create()
}
}
4 手动依赖注入:手动管理生命周期
现在很多安卓应用都支持多用户,所以我们需要扩充上面的应用功能:记录不同的用户登录信息。这就需要在 LoginActivity
中增加新的功能,达到下面的目的:
- 在这个用户登录期间保持对
LoginUserData
类实例对象的访问,退出登录后释放资源; - 当新用户登录后,重新新建一个
LoginUserData
类实例对象。
这时我们需要添加一个 LoginContainer
类,用来存储 LoginUserData
类的实例对象和 LoginViewModelFactory
类的实例对象。(TIPS: container 中放的是依赖,也就是各种类的实例对象)
class LoginContainer(val userRepository: UserRepository) {<!-- -->
val loginData = LoginUserData()
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
然后把 LoginContainer
应用到之前的 AppContainer
中:
class AppContainer {<!-- -->
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
val userRepository = UserRepository(localDataSource, remoteDataSource)
//val loginViewModelFactory = LoginViewModelFactory(userRepository)
// loginViewModelFactory 的实现已经放到LoginContainer中,此处不再需要
// 新建一个loginContainer变量,类型是LoginContainer,初始值是null
// 因为当用户退出时,其值时null
var loginContainer: LoginContainer? = null
}
接下来回到 LoginActivity
类中,我们需要在 LoginActivity
类的 onCreate()
阶段(用户登录),通过 loginContainer
拿到 LoginUserData
的实例对象,而在 onDestroy()
阶段(用户退出)释放相应的资源:
class LoginActivity: Activity() {<!-- -->
private lateinit var loginViewModel: LoginViewModel
private lateinit var loginData: LoginUserData
private lateinit var appContainer: AppContainer
override fun onCreate(savedInstanceState: Bundle?) {<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
// loginViewModel = XXXX 改成下面代码:
appContainer = (application as MyApplication).appContainer
//loginViewModel = LoginViewModel(appContainer.userRepository) 替换成下面代码:
//loginViewModel = appContainer.loginViewModelFactory.create() 替换成下面代码:
// 用户登录,实例化LoginContainer,得到appContainer中的loginContainer 对象
appContainer.loginContainer = LoginContainer(appContainer.userRepository)
// loginViewModel 对象的获取比原来多了一层 loginContainer 对象
loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
loginData = appContainer.loginContainer.loginData
}
override fun onDestroy() {<!-- -->
// 用户退出,释放资源
appContainer.loginContainer = null
super.onDestroy()
}
}
5 总结
这里,我们总结一下手动依赖注入都干了些啥:
- 首先新建了一个
AppContainer
类,把LoginViewModel
需要的各种依赖一股脑都放进去; - 为了在应用的其他地方(除了
LoginActivity
以外)使用LoginViewModel
的实例,我们用工厂类的设计模式在AppContainer
类容器中实现了一个loginViewModelFactory
对象; - 最后为了实现不同用户的登录和登出,我们又新建了一个
LoginContainer
类容器,并放到AppContainer
类容器中,然后在LoginActivity
中可以在onCreate()
中拿到用户登录的信息loginData
,并且在用户登出以后,即在LoginActivity
的onDestroy()
中释放资源。
可以预见的,当我们的应用功能越来越复杂的时候(现在只是其中的一个登陆功能),手动依赖注入将会变得不可维护。这就是 Hilt 被使用的原因。