Kotlin 39. Dependency Injection依赖注入以及Hilt在Kotlin中的使用,系列2:手动依赖注入

一起来学Kotlin:概念:26. Dependency Injection依赖注入以及Hilt在Kotlin中的使用,系列2:手动依赖注入

此系列博客中,我们将主要介绍:

  • Dependency Injection(依赖注入) 概念介绍。网上看了许多关于 DI 的介绍,云里雾里。这里,我们通过通俗易懂地方式对其进行介绍。
  • 手动依赖注入介绍。为了让大家更容易理解 Hilt,我们先介绍如何通过手动的方式实现依赖注入效果。
  • Hilt 注释(annotations)介绍及使用案例
  • MVVM 案例中如何使用 Hilt

此博客主要介绍手动依赖注入。



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 类中。以此类推,ModelRemoteDataSource 的实例也需要注入到 Repository 类中。

实际上,我们通常做的,就是在 Activity/Fragment 里面直接 new 一个 ViewModel。看起来很方便,但实际上,这是不是和上面的耦合例子非常类似?如果我们只有一个 Activity/Fragment 和一个依赖的 ViewModel,那没什么问题,但如果关系复杂了,依赖注入的优势就很明显了。

比如说,现在需要实现一个用户登录的功能时,用MVVM架构应该是下面这样的:

请添加图片描述

这里,LoginActivity 依赖于 LoginViewModelLoginViewModel 依赖于 UserRepositoryUserRepository 依赖于 UserLocalDataSource 以及 UserRemoteDataSourceUserRemoteDataSource 依赖于 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,并且在用户登出以后,即在 LoginActivityonDestroy() 中释放资源。

可以预见的,当我们的应用功能越来越复杂的时候(现在只是其中的一个登陆功能),手动依赖注入将会变得不可维护。这就是 Hilt 被使用的原因。

猜你喜欢

转载自blog.csdn.net/zyctimes/article/details/129218481