Vamos aprender Kotlin juntos: Conceito: 26. Injeção de dependência Injeção de dependência e o uso do Hilt em Kotlin, Série 2: Injeção manual de dependência
Nesta série de blogs, apresentaremos principalmente:
Dependency Injection
(Injeção de Dependência) Introdução ao conceito. Eu li muitas introduções sobre DI na Internet, mas estou confuso. Aqui, nós o apresentamos de uma maneira fácil de entender.- Uma introdução à injeção de dependência manual. Para tornar mais fácil para todos entenderem o Hilt, primeiro apresentamos como obter efeitos de injeção de dependência manualmente.
- Hilt annotations (anotações) introdução e casos de uso
- Como usar o Hilt no caso MVVM
Este blog se concentra na injeção de dependência manual.
Diretório de artigos
1 avaliação
No primeiro blog da série, apresentamos o conceito de injeção de dependência e por que ela é necessária.
Em termos simples, em um cenário como o objeto A precisa (depende de) outra instância do objeto B, transferir a tarefa de criar objetos para outros e usar dependências diretamente é chamado de injeção de dependência (DI). No blog anterior, também pegamos o exemplo de instanciar a classe Car para explicar como evitar o acoplamento entre esse objeto car e vários objetos dos quais ele depende, como rodas e motores.
Por exemplo, no exemplo a seguir, quando a instância Car for criada, uma nova instância Wheel será criada, que é o acoplamento. O objetivo do DI é evitar que isso aconteça.
class Car {
<!-- -->
private val wheelA = Wheel()
fun start() {
<!-- -->
engine.start()
}
}
fun main(args: Array) {
<!-- -->
val car = Car()
car.start()
}
Além disso, também listamos três implementações de injeção de dependência:
- injeção de construtor (injeção de construtor de classe): as dependências são fornecidas por meio do construtor de classe.
- injeção de setter (injeção de campo de classe): o cliente expõe um método setter que o injetor usa para injetar dependências.
- injeção de interface: Dependency fornece um método injetor que pode injetar dependências em qualquer cliente que o passe. Os clientes devem implementar uma interface que exponha um método setter que aceite dependências.
2 Definição de punho
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.
A partir da definição de Hilt, podemos concluir que Hilt realiza principalmente duas coisas:
- Fornece "containers" (contêineres) para armazenar várias dependências;
- Gerenciar automaticamente os "ciclos de vida" desses "containers" (containers).
Nos capítulos seguintes, explicaremos o significado e importância das duas coisas acima por meio da injeção manual de dependência.
3 Injeção de dependência manual: forneça um contêiner para gerenciar várias dependências
Como no blog anterior, também usamos alguns exemplos para explicar o conceito de injeção manual de dependência.
A figura a seguir é uma arquitetura básica do MVVM:
A flecha na figura acima é unidirecional, o que significa que uma ponta da flecha depende da outra ponta. Por exemplo, Activity/Fragment
depende de ViewModel
e ViewModel
depende de Repository
. Na arquitetura MVVM do Android, injeção de dependência significa injetar ViewModel
instâncias de (instância) em Activity/Fragment
classes e, pelo mesmo motivo, Repository
injetar instâncias de dentro de ViewModel
classes. Por analogia, instâncias de Model
e também precisam ser injetadas na classe.RemoteDataSource
Repository
Na verdade, o que costumamos fazer é Activity/Fragment
criar diretamente um novo nele ViewModel
. Parece conveniente, mas, na verdade, não é muito semelhante ao exemplo de acoplamento acima? Se tivermos apenas um Activity/Fragment
e um dependente ViewModel
, tudo bem, mas se o relacionamento for complicado, as vantagens da injeção de dependência são óbvias.
Por exemplo, quando for necessário implementar uma função de login do usuário, a arquitetura MVVM deve ser a seguinte:
Aqui, LoginActivity
depende de LoginViewModel
, LoginViewModel
depende de UserRepository
, UserRepository
depende de UserLocalDataSource
e UserRemoteDataSource
. UserRemoteDataSource
depende de Retrofit
.
Quando não estiver usando a ideia de injeção de dependência, LoginActivity
deve ficar mais ou menos assim:
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
// 实例化LoginViewModel
loginViewModel = LoginViewModel(userRepository)
}
}
Para satisfazer LoginViewModel
a instanciação de , precisamos passar um UserRepository
parâmetro de instância de userRepository
. Isso não acaba aí, porque UserRepository
por sua vez depende UserLocalDataSource
também UserRemoteDataSource
. Um toque após o outro. Então, se escrevermos LoginActivity
completamente, ficará mais ou menos assim:
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)
}
}
A seção "novo código" no código acima contém o relacionamento de intertravamento entre cada dependência. Assim, na LoginActivity
classe, criamos instâncias de todas as dependências relacionadas. Mas como imaginamos originalmente, LoginActivity
desde que existam objetos na classe loginViewModel
, os outros não precisam aparecer na LoginActivity
classe. O que precisamos é algo como este código limpo:
class LoginActivity: Activity() {
<!-- -->
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
<!-- -->
super.onCreate(savedInstanceState)
// 获取 loginViewModel 对象
loginViewModel = XXXX
}
}
O que podemos fazer é criar uma nova AppContainer
classe e jogar todo o código recém-adicionado nela:
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)
}
A AppContainer
classe recém criada não pode ser LoginActivity
utilizada na classe, pois AppContainer
essas "dependências" na classe precisam ser utilizadas globalmente em toda a aplicação (aplicação), então a AppContainer
instância da classe precisa ser colocada Application()
na subclasse de:
Criamos uma MyApplication
nova classe que herda de Application()
:
class MyApplication : Application() {
<!-- -->
val appContainer = AppContainer()
}
Dessa forma, LoginActivity
pode ser usado assim em:
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)
}
}
Até agora, LoginActivity
o estilo de codificação da injeção de dependência pode ser visto na classe.
O objeto que implementamos aqui loginViewModel
usa o método de injeção de dependência do Constructor Injection: loginViewModel = LoginViewModel(appContainer.userRepository)
.
Além disso, se em nosso aplicativo Android, além LoginActivity
da classe, outras classes também precisarem LoginViewModel
de objetos de instância da classe, não poderemos criar novos objetos de instância LoginActivity
da classe . LoginViewModel
Ainda é o jeito antigo, como uma das "dependências", precisamos colocar os LoginViewModel
objetos de instância implementados em "containers" (containers). Aqui, você precisa usar o padrão de design do padrão de fábrica, criar uma nova Factory
interface, LoginViewModelFactory
implementar essa interface na classe e retornar uma LoginViewModel
instância de:
interface Factory<T> {
<!-- -->
fun create(): T
}
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {
<!-- -->
override fun create(): LoginViewModel {
<!-- -->
return LoginViewModel(userRepository)
}
}
A próxima coisa é simples, colocamos esta nova LoginViewModelFactory
classe de fábrica em 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)
}
Em seguida, LoginActivity
ele pode ser usado diretamente na classe (em vez de criar um novo LoginViewModel
objeto de instância como antes):
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 Injeção Manual de Dependência: Gerenciamento Manual do Ciclo de Vida
Agora, muitos aplicativos Android oferecem suporte a vários usuários, portanto, precisamos expandir as funções do aplicativo acima: registrar diferentes informações de login do usuário. Isso requer LoginActivity
a adição de novas funções para atingir os seguintes objetivos:
- Mantenha o acesso ao objeto de instância da classe durante o login do usuário
LoginUserData
e libere o recurso após o logout; - Quando um novo usuário fizer login, crie um novo
LoginUserData
objeto de instância de classe.
Neste momento, precisamos adicionar uma LoginContainer
classe para armazenar LoginUserData
os objetos de instância da classe e LoginViewModelFactory
os objetos de instância da classe. (DICAS: As dependências são colocadas no container, ou seja, os objetos instância de várias classes)
class LoginContainer(val userRepository: UserRepository) {
<!-- -->
val loginData = LoginUserData()
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
Em seguida, LoginContainer
aplique ao anterior 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
}
Em seguida, de volta à LoginActivity
classe, precisamos obter o objeto de instância no estágio LoginActivity
da classe onCreate()
(login do usuário) e liberar os recursos correspondentes no estágio (saída do usuário):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 resumo
Aqui, resumimos o que a injeção de dependência manual fez:
- Primeiro, crie uma nova
AppContainer
classe e coloqueLoginViewModel
nela todas as dependências necessárias; - Para usar instâncias de em outros locais no aplicativo (exceto
LoginActivity
)LoginViewModel
, usamos o padrão de design de classe fábricaAppContainer
para implementar umloginViewModelFactory
objeto no contêiner de classe; - Por fim, para realizar o login e logout de diferentes usuários, criamos um novo
LoginContainer
contêiner de classe e o colocamosAppContainer
no contêiner de classe e, em seguida,LoginActivity
podemosonCreate()
obter as informações de login do usuário emloginData
, e depois que o usuário faz logout, ele está emLoginActivity
osonDestroy()
recursos de liberação.
É previsível que, quando nossas funções de aplicativo se tornarem cada vez mais complexas (agora é apenas uma das funções de login), a injeção manual de dependência se tornará insustentável. É por isso que o Hilt é usado.