什么是依赖注入
在阅读别人得项目代码时,经常会看到某一个成员变量或者构造函数的入参上被添加@Injcet注解,如果没有了解过依赖注入框架,此时都会很迷惑,这个注解得作用是什么,接下来就简单带大家了解一下,依赖注入以及Dagger2的作用。
官方解释
依赖项注入 (DI) 是一种在编程中广泛应用的技术,非常适合用于 Android 开发。遵循 DI 的原则可以为构造良好的应用架构奠定基础。
实现依赖项注入可为您带来以下优势:
- 重用代码
- 易于重构
- 易于测试
手动依赖注入
把类的实例传递给另一个对象得过程,就叫做依赖注入。
栗子:
在MVP架构中,我们有一个Presenter类,他通常需要持有Model的引用, 这其实就是把Model 注入给了Presenter层,这便是手动依赖注入。
Kotlin
class Presenter(model:Model){
private val model= Model()
}
class Model(){
fun getDataForNet(){
//do....
}
}
定义
自行创建、提供并管理不同类的依赖项,而不依赖于库。这称为手动依赖项注入或人工依赖项注入。
日常开发中,类通常通过以下三种方式获取所需的对象:
scss
1.类构造其所需的依赖项。例如 `private val model= Model()`
2.从其他地方抓取。某些 Android API(如 Context getter 和 getSystemService())的工作原理便是如此。
3.以参数形式提供。class Presenter(model:Model){}
整个过程,由两方完成,需求方
和被注入方
自动依赖注入
在编译时期,为被注入方
生成实例,通过容器接口生成的容器类进行管理
为什么需要自动依赖注入,明明一个new Model()
就能解决的问题,为什么还要单独设计个框架去做这件事呢?
我们增加一下MVP的难度,P层中 除了M层的引用,还需要做分页管理 PageLoader(),数据类型转换DataConvert()等等,由于业务和需求变大,我们可能需要维护很多对象,尤其是那些对象依赖层级较深的,不仅不便于管理,还会使项目耦合性变大,导致不易懂,当修改中间层引用时,导致耦合性变大,容易出现问题。
Dagger2介绍
官方释义
Dagger2是一个依赖注入框架,由Google维护,用于在Android中实现依赖注入。 Dagger 可以执行以下操作,使您无需再编写冗长乏味又容易出错的样板代码:
- 生成您在手动 DI 部分手动实现的 AppContainer 代码(应用图)。
- 为应用图中提供的类创建 factory。这就是在内部满足依赖关系的方式。
- 重复使用依赖项或创建类型的新实例,具体取决于您如何使用作用域配置该类型。
- 为特定流程创建容器,操作方法与上一部分中使用 Dagger 子组件为登录流程创建容器的方法相同。这样可以释放内存中不再需要的对象,从而提升应用性能。 只要您声明类的依赖项并指定如何使用注释满足它们的依赖关系,Dagger 便会在构建时自动执行以上所有操作。Dagger 生成的代码与您手动编写的代码类似。在内部,Dagger 会创建一个对象图,然后它可以参考该图来找到提供类实例的方式。对于图中的每个类,Dagger 都会生成一个 factory 类型类,它会使用该类在内部获取该类型的实例。
优点
- 基于注解的依赖配置,大大简化了依赖注入的使用和配置。开发者只需要使用@Inject、@Module、@Component等注解,即可声明依赖和关系,而不需要写任何代码来执行依赖注入。
- 运行期动态注入,提高了性能。Dagger2会在编译期生成代码,在运行期高效执行依赖注入,而不是使用反射,因此性能很高。
- 支持懒加载,按需创建。可以只在真正需要时创建实例,减少内存占用。
- 支持组件作用域的依赖注入,更加灵活。可以自定义组件的作用域,实现不同组件间的隔离。
- 完全静态、可预测、无运行时开销。编译期生成代码,不存在反射和动态代理开销,做到性能最优。
- 内置生命周期和多绑定支持,使用方便。
个人理解
Dagger2用于管理项目中的实例 ,通过注解和Javapot 在编译时生成被注入方
的实例模板,生成容器类,用于需求方
和被注入方
的关联绑定。
通常两种依赖注入方式:
- 构造
- 变量
Dagger2的使用
项目引入依赖
Java
implementation 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
记得引入 kapt的插件
ini
如果不自动生成代码,记得更新下gradle.properties 的配置 kapt.incremental.apt = false 后 rebulid一下
基础使用
假设开过过程中,有一个数据仓库类,数据来源于本地和网络,TestRepository
在创建时需要 TestRemoteDataSource
和 TestLocalDataSource
对象,通过在TestRepository
(需要注入的对象)和 TestRemoteDataSource
(被注入的对象)的构造上添加@Inject
注解,通知Dagger,就会自动创建实例
Java
//容器类,用于和需求方关联
@Component
interface TestContainer {
fun repository(): TestRepository
}
// 助需要注入的类
class TestLocalDataSource @Inject constructor() {
}
class TestRemoteDataSource @Inject constructor() {
}
// 构造注入
class TestRepository @Inject constructor(
val testRemoteDataSource: TestRemoteDataSource,
val testLocalDataSource: TestLocalDataSource) {}
// 在需要全局提供的对象的构造参数上 加上 @Inject 在全局提供的地方加上@Component ,
// build 模块 ,自动生成Dagger+ 接口名称
bash
build项目后,自动生成的容器/需求方的实例工厂类,在 app>bulid>source>kapt>debug 下面
@Inject 构造方法
@Inject通常用在构造方法
、类成员变量
。
当被@Inject 修饰的构造
需要参数时,参数本身的构造也必须被@Inject,为了创建关联,自动生成依赖。
被@Inject修饰的类构造没有参数时,自动生成了一个该类的工厂类,用于提供该类的实例和工厂本身的单例
被@Inject修饰的类构造有参数时,自动生成了一个该类的工厂类,根据参数,通过参数的工厂类,获取对应的对象。
@Component
@Component用在接口上,用于定义一个Component。
我们这时候只需通过Dagger+容器名,就可以获取到需要的对象实例
Kotlin
//调用
val testRepository = DaggerTestContainer.builder().build().repository()
@Inject 类成员变量
除了构造方法以外,@Inject还可以修饰成员变量
Kotlin
@Inject
lateinit var testRepository: TestRepository
还需要在TestContainer接口里面加一个方法
Kotlin
@Component
interface TestContainer {
//mainActivity = 需求方
fun inject(mainActivity: MainActivity)
}
除了@Inject 本身生成的类,@Component容器里面也会生成一个新的方法
Kotlin
//调用
DaggerTestContainer.create().inject(MainActivity.this)
@Module
日常开发中,我们会经常会使用第三方SDK,例如OkhttpClient进行网络数据请求,通常这种情况,我们是没办法对其构造进行@Inject的,那这时候,我们就需要通过@Module 进行实例的注入
Java
// 1 为Container创建 一个module
// 通过@Provides 可以提供多个实例
@Module
class BaseModule constructor(var context: Context){
@Provides
fun getOkhttpClient(): OkhttpClient {
return OkHttpClient.Builder().build()
}
@Provides
fun....
}
// 2 为Container绑定Module
@Component(modules = [BaseModule::class])
interface BaseContainer {
//提供一个接收注入类的实例
fun inject(baseActivity: BaseActivity)
}
class MainActivity : AppCompatActivity() {
@Inject
lateinit var okHttpClient: OkHttpClient
// 3 调用
DaggerBaseContainer.builder().baseModule(BaseModule(baseContext)).build().inject(this)
}
注:如果需求方没有@Inject属性, baseModule()会显示被作废的方法,无法关联
容器类
使用@Module + @Provides 会生成工厂类,专门生产@Provides所注释方法返回的实例,如果有多个Module 或者 Provides,则生产多个工厂类。
@Singleton
对于上面的OkhttpClient而言,在没有特殊情况下,项目中始终需要唯一配置好的Client,那这时候,我们就需要使用Singleton来修饰module中的@Provides方法。
@Singleton 用于修饰容器接口 和 被@Provides的方法。
注:如果模块中 的@Provides 中使用了@Singleton,容器类也要标记
@Qualifier
正常开发中,除了我们本身项目的服务以外,我们可能还需要调用一些第三方的api,例如百度的服务。
如果在一个类里面同时需要两个OkhttpClient 对应不同的服务,一个Module 是无法做到的,那我们如何区分呢?通过@Qualifier注解。
kotlin
//修饰注解
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class DevOkhttpClient
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class BaiDuOkhttpClient
kotlin
@Module
class BaseModule constructor(var context: Context) {
//修饰Provides
@Singleton
@Provides
@DevOkhttpClient
fun devOkhttpClient(): OkHttpClient {
return OkHttpClient.Builder().build()
}
@BaiDuOkhttpClient
@Singleton
@Provides
fun baiDuOkhttpClient(): OkHttpClient {
return OkHttpClient.Builder().build()
}
}
less
@Inject
@DevOkhttpClient
lateinit var devOkHttpClient: OkHttpClient
@BaiDuOkhttpClient
@Inject
lateinit var baiDuOkHttpClient: OkHttpClient
....
//绑定
DaggerBaseContainer.builder().baseModule(BaseModule(baseContext)).build().inject(this)
Dagger中模块的关系
上面我们描述了几种开发中的场景,对于实际的项目中,类与类之间的关联,会更为复杂。
所以我们需要构建更为复杂的组织关联。
相对于的类的特性,容器管理模块时,也有继承,依赖关系。
@dependencies
作用:
- 提供依赖关系:
dependencies
用于指定一个组件依赖于另一个组件或者容器,使得依赖的组件可以访问被依赖组件提供的依赖对象。 - 组件组合: 允许在一个应用中组合多个组件,每个组件负责管理不同的依赖。这有助于模块化和清晰地定义应用的不同部分。
例子:我们经常会在Application 中进行一些SDK的初始化,我们需要在不同的页面使用这些SDK,此时,我们可以创建一个关系图谱
AppModule
kotlin
@Module
class AppModule {
@Provides
fun proAppModule(): AppData {
return AppData("AppData")
}
}
data class AppData(val name: String)
ActivityModule
kotlin
@Module
class ActivityModule {
@Provides
fun proActivityData(): ActivityData {
return ActivityData("ActivityData")
}
}
data class ActivityData(val name: String)
容器类
kotlin
@Component(modules = [AppModule::class])
interface AppComponent {
fun provideSomeDependency(): AppData
}
@Component(dependencies = [AppComponent::class], modules = [ActivityModule::class])
interface ActivityComponent {
fun provideSomeOtherDependency(mainActivity: MainActivity)
fun provideSomeDependency(): AppData
}
MyApp 需求方
kotlin
class MyApp : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent.create()
}
}
最终需求方Activity
KOTLIN
class MainActivity : AppCompatActivity() {
lateinit var activityData: ActivityData
@Inject
lateinit var appData: AppData
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerActivityComponent
.builder()
.appComponent((application as MyApp).appComponent)
.build()
.provideSomeOtherDependency(this)
println("appData ${appData.name}" ) // AppData
println("activityData ${activityData.name}" ) // activityData
}
}
通过 依赖关系,在Activity中不仅可以拿到本身的依赖,也可以拿到全局的配置 注:依赖关系 属于作用域分配,模块中 不用使用了@Singleton,会冲突
@Subcomponent
作用: 子组件是继承并扩展父组件的对象图的组件。
上面的例子是由全局到局部,Subcomponent由局部到全局。 具体例子可以看一下Android开发者官网,有一个详细Demo的解释
区别
-
创建方式:
dependencies
通常用于描述独立的组件之间的依赖关系,这些组件通常位于不同的模块或者不同的层次结构中。subcomponent
通常用于描述父子组件之间的依赖关系,通常用于创建具有相同作用域的相关组件集合,用于控制生命周期。
-
依赖关系类型:
dependencies
用于描述两个独立的组件之间的依赖关系。一个组件可以通过dependencies
关键字声明它依赖于另一个组件,从而获取其他组件提供的依赖项。subcomponent
用于描述父子组件之间的依赖关系。子组件会继承父组件的依赖项,同时可以定义自己的额外依赖项。
-
实例化方式:
dependencies
用于通过@Component
注解的dependencies
属性来声明依赖关系,然后通过builder()
方法或者create()
方法来实例化依赖组件。subcomponent
则通过在父组件中定义一个方法来提供子组件的实例,然后通过该方法来获取子组件的实例。
-
作用域:
- 通过
dependencies
声明的依赖关系不会传递作用域。即使父组件和子组件具有相同的作用域,它们之间的依赖关系也不会传递作用域。 subcomponent
中的依赖关系会继承父组件的作用域。这意味着子组件中提供的依赖项会具有与父组件相同的作用域,从而确保依赖项在整个组件层次结构中的生命周期一致。
- 通过
总结
虽然理解起来,依赖注入会让人觉得开发繁琐,经常编译错误,但真的理解并掌握他的时候,你会发现,实例的管理会更加简单,代码的可维护性更易理解、测试和扩展会更加方便。