kotlin使用Dagger2的过程全纪录

前言

Dagger2作为依赖注入神器,相信很多朋友都听说过它的大名。只不过它的有些概念,理解起来并不是那么清晰,并且在使用的过程中,也比较迷糊。

Dagger2有Google接手开发的一个基于JSR-330标准的依赖注入框架,它会在编译期间自动生成相关代码,负责依赖对象的创建,达到解耦目的。

下面将详细介绍关于kotlin使用Dagger2的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

kotlin中配置Dagger2

在app模块的build.gradle文件中进行如下配置,关于kapt的相关知识

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
 ...
}
dependencies {
 ...
 implementation 'com.google.dagger:dagger:2.11'
 kapt 'com.google.dagger:dagger-compiler:2.11'
}

相关常用注解:

  • @Inject
  • @Component
  • @Module
  • @Provides
  • @Qualifier和@Named
  • @Scope和@Singleton

@Inject

@Inject注解只是JSR-330中定义的注解,在javax.inject包中。 这个注解本身并没有作用,它需要依赖于注入框架才具有意义,可以用来标记构造函数、属性和方法。

标记构造函数

被标记的构造函数可以有0个或多个依赖作为参数。

同一个类中最多只可以标记一个构造函数。

class People @Inject constructor(val name:String = "Tom")

注意在kotlin中这种写法是不被允许的,因为这等价于java中的多个构造方法People(String name), People() 正确的写法应该是这样:

data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
}

标记属性

被标记的属性不能是final的,kotlin中不能是val。

被注入进的属性不能用private修饰(是Dagger2不支持,而非@Inject不支持)。

 @Inject
 lateinit var people:People

标记方法

被标记的方法可以有0个或多个依赖作为参数。

方法不能是抽象的。

class HomeActivity : AppCompatActivity() {
 private lateinit var people:People
 @Inject
 fun setPeople(people:People){
 this.people = people
 }
}

这种方法注入和属性注入并没有什么本质上的不同,实现效果也基本一样。还有一种做法是@Inject标记被注入类的某个方法,该方法会在类的构造方法之后接着被调用:

data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
 init {
 println("init:$name")
 }

 @Inject
 fun hello(){
 println("hello:$name")
 }
}

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people:People
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_home)

 //执行相关注入操作
 ...
 println(people.toString())
 }
}

运行结果是这样的:

01-02 11:57:30.995 16601-16601/? I/System.out: init:Tom
01-02 11:57:30.995 16601-16601/? I/System.out: hello:Tom
01-02 11:57:30.995 16601-16601/? I/System.out: People(name=Tom)

@Component

可以理解为一个注射器,可以算是Dagger2中最核心的一个注解,用来标记一个接口或者抽象类。使用@Component标记的接口,会在编译时自动生成一个Dagger+类名的实现类实现依赖注入。在Component中一般可以定义两种方法:

Members-injection methods:

该方法有一个参数,表示需要注入到的类,提醒Dagger在该类中寻找需要被注入的属性(被@Inject标记)。

void inject(SomeType someType);//无返回值
SomeType injectAndReturn(SomeType someType);//返回它的参数类型

等价于:

MembersInjector<SomeType> getMembersInjector();//使用MembersInjector.injectMembers方法注入

Provision methods:

该方法没有参数,返回一个需要被注入(或被提供)的依赖。一般用于为其他Component提供依赖的时候。

SomeType getSomeType();
Provider<SomeType> getSomeTypeProvider();//可以通过Provider.get访问任意次
Lazy<SomeType> getLazySomeType();//通过Lazy.get第一次访问时创建实例,并在之后的访问中都访问同一个实例
@Component
interface HomeComponent {
 fun inject(activity: HomeActivity)
 fun injectAndReturn(activity: HomeActivity): HomeActivity
 fun getInjectors(): MembersInjector<HomeActivity>

 fun getPeople():People
}

事实上,了解到这里我们已经可以使用最简单的Dagger2用法,毕竟有了依赖和注射器,只需要注入就可以了,我们来看一个最简单的Dagger2实例,只使用@Inject和@Component来完成注入。

第一步:在需要被注入的类的构造方法上添加注解@Inject

class People @Inject constructor() {
 fun hello(){
  println("hello")
 }
}

第二步:编写一个注射器接口

@Component
interface HomeComponent {
 fun inject(activity: HomeActivity)
}

第三步:注入

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people:People
 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_home)

  DaggerHomeComponent.builder()
    .build()
    .inject(this)//会在这句代码时执行注入的操作
  people.hello()
 }
}

03-01 14:30:23.425 3256-3256/? I/System.out: hello
//大功告成

当然,上面这种只是最简单的用法,如果需要传入一些非自定义类的实例就不适用了,毕竟你不能在第三方的类中加入@Inject注解。此时就需要用到@Module和@Provides注解。

@Module

用来标记类,为Component提供依赖,相当于告诉Component,如果需要依赖可以来找我,当然前提是在Component中配置了该Module。同时Module可以通过includes依赖其他的Module。

@Provides

用来标记Module中的方法,该方法的返回类型是你需要提供的依赖类型。

举个自己项目中的例子,我需要在presenter中创建一个pl2303对象,pl2303对象的创建又需要context和pl2303Interface,所以我们需要提供三个依赖,因为context在其他地方也要用,我们单独提出来:

@Module
class ContextModule(private var mContext: Context) {
 @Provides
 fun getContext() = mContext
}

pl2303Interface只有这一个地方要用:

@Module(includes = arrayOf(ContextModule::class))
class Pl2303Module(private var pl2303Interface: ActivityCallBridge.PL2303Interface) {
 @Provides
 fun providePl2303(mContext: Context): Pl2303 {
  return Pl2303(mContext, pl2303Interface)
 }
}

其中includes可以是多个,我们这里把ContextModule加进来,这样创建pl2303就只差一个pl2303Interface,这是个接口对象,不能new,从构造函数注入进来。接下来创建注射器:

@Component(modules = arrayOf(Pl2303Module::class))
interface MainPresenterComponent {
 fun inject(presenter: MainPresenter)
}

最后注入:

class MainPresenter(val view: MainContract.View) : MainContract.Presenter, ActivityCallBridge.PL2303Interface, LifecycleObserver {
 @Inject lateinit var pl2303: Pl2303
 init {
  DaggerMainPresenterComponent.builder()
    .contextModule(ContextModule(view.context))
    .pl2303Module(Pl2303Module(this))
    .build()
    .inject(this)
 }
}

如果在大型项目中,一个Component有很多的Module,那么不需要传入参数的Module是可以省略的,看一下官方的注释文档:

public static void main(String[] args) {
  OtherComponent otherComponent = ...;
  MyComponent component = DaggerMyComponent.builder()
   // required because component dependencies must be set(必须的)
   .otherComponent(otherComponent)
   // required because FlagsModule has constructor parameters(必须的)
   .flagsModule(new FlagsModule(args))
   // may be elided because a no-args constructor is visible(可以省略的)
   .myApplicationModule(new MyApplicationModule())
   .build();
  }

@Named和@Qualifier

@Named是@Qualifier的一个实现。有时候我们会需要提供几个相同类型的依赖(比如继承于同一父类),如果不做处理的话编译器就不知道我们需要的具体是哪一个依赖而报错,比如这样:

abstract class Animal
class Dog : Animal() {
 override fun toString(): String {
  return "dog"
 }
}

class Cat : Animal() {
 override fun toString(): String {
  return "cat"
 }
}

@Module
class AnimalModule {
 @Provides
 fun provideDog(): Animal = Dog()
 @Provides
 fun provideCat(): Animal = Cat()
}
data class Pet @Inject constructor(val pet: Animal)

这时候就需要标记一下来告诉编译器我们需要的是哪个依赖:

@Module
class AnimalModule {
 @Provides
 @Named("dog")
 fun provideDog(): Animal = Dog()

 @Provides
 @Named("cat")
 fun provideCat(): Animal = Cat()
}
data class Pet @Inject constructor(@Named("dog") val pet: Animal)

上面我们说了@Named只是@Qualifier的一个实现而已,所以我们也可以用@Qualifier来达到一样的效果,实际使用中也更推荐使用@Qualifier的方式,因为@Named需要手写字符串来进行标识,容易出错。

使用@Qualifier需要注意:

  • 创建一个自定义的Qualifier至少需要@Qualifier, @Retention(RUNTIME)这两个注解。
  • 可以有自己的属性。

我们可以看一下@Named的源码来加深一下理解:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
 /** The name. */
 String value() default "";
}

下面我们比葫芦画瓢来改造一下上面的例子:

@Module
class AnimalModule {
 @Provides
 @DogAnim
 fun provideDog(): Animal = Dog()
 @Provides
 @CatAnim
 fun provideCat(): Animal = Cat()
}

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class DogAnim
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CatAnim
data class Pet @Inject constructor(@CatAnim val pet: Animal)

经测试依然是可以运行的。

Pet(pet=cat)

@Scope和@Singleton

A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type

@Scope是用来标记包含可注入构造函数的类或者提供注入依赖对象的类,简单来说,可以用来标记包含@Inject构造函数的类或者@Module类。

@Scope是用来管理依赖的生命周期的。它和@Qualifier一样是用来自定义注解的,而@Singleton和@Named类似,是@Scope的默认实现。

如果一个注射器和创建依赖对象的地方没有标记@Scope,那么每次注入时都会创建一个新的对象,如果标记了@Scope,则在规定的生命周期内会使用同一个对象,特别注意是在规定的生命周期内单例,并不是全局单例,或者可以理解为在@Component内单例。

还是借助上面的例子:

data class People constructor(val name: String) {
 @Inject
 constructor() : this("Tom")
 init {
  println("init:$name")
 }

 @Inject
 fun hello(){
  println("hello:$name")
 }
}

class HomeActivity : AppCompatActivity() {
 @Inject
 lateinit var people: People
 @Inject
 lateinit var people_2: People
 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_home)
  DaggerHomeComponent.builder()
    .build()
    .inject(this)
  println("people===people_2:${people===people_2}")
 }
}

运行结果:

people===people_2:false

说明确实是两个不同的对象,接下来我们改造一下:

@Singleton
data class People constructor(val name: String) {
 ...//和之前一样
}

@Singleton
@Component(modules = arrayOf(AnimalModule::class))
interface HomeComponent {
 fun inject(activity: HomeActivity)
}
...//HomeActivity代码和之前一样

再次看下运行结果:

people===people_2:true

说明这次两次都是访问的同一个对象。上面提到这只是一个局部单例,那么怎么实现一个全局单例呢,很简单,只要保证标记的Component在全局只初始化一次即可,比如在Application中初始化,篇幅限制代码就不贴了,有兴趣的骚年可以自己实践一下。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • dagger2使用方法教程之简明讲解

    前言 Dagger 这个库的取名不仅仅来自它的本意"匕首",同时也暗示了它的原理.Jake Wharton 在对 Dagger 的介绍中指出,Dagger 即 DAG-er,这里的 DAG 即数据结构中的 DAG--有向无环图(Directed Acyclic Graph).也就是说,Dagger 是一个基于有向无环图结构的依赖注入库,因此Dagger的使用过程中不能出现循环依赖. Android开发从一开始的MVC框架,到MVP,到MVVM,不断变化.现在MVVM的data-bind

  • 详解Kotlin 中使用和配置 Dagger2

    前言 陆陆续续几篇文章已经讲解了项目中 Kotlin 如何配置.简单语法.DataBinding 配置,接下来就要说到 Kotlin 中的 Dagger2 了. 配置 Dagger2 项目中使用 Dagger2 ,首先还是添加依赖.同样的,因为要使用到注解处理,所以和 DataBinding 一样要添加 kapt 插件: apply plugin: 'com.android.application' ... apply plugin: 'kotlin-kapt' // kapt 插件 ... k

  • Kotlin中使用Dagger2可能遇到的坑解决

    Dagger2是什么? Dagger2是一款基于Java注解,在编译阶段完成依赖注入的开源库,主要用于模块间解耦,方便进行测试. 一.Kotlin Dagger2 配置 build.gradle apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' ... dependencies { ... //dagger2 compile rootProject.ext.

  • 详解Dagger2在Android开发中的新用法

    本文假设读者已经有一定Dagger2使用经验 使用疑惑 之前工作中一直在使用dagger2进行开发,用起来确实很爽,但是我从我第一次使用我就一直有一个问题或者说疑问(本人才疏学浅脑子不够使),通常情况下我们有如下清单 MyApplication,MyAppComponent,MyAppModule ActActivity,ActComponent,ActModule 简单解释下,MyAppModule提供全局单例功能,比如打印日志,ActModule提供Activity级别的功能比如发起网络请求

  • Dagger2新手入门与使用基础教程

    前言 前段时间,公司项目使用到了Dagger2,之前自己倒是听说过Dagger2,但是一直没有去使用,主要是因为入门难度相对于Rxjava,Retrofit要高不少,个人觉得这个难度并不是指的使用难度,而是一个理解难度.很多人就是代码照搬,写一个Component,一个Module就说自己会用Dagger2了,很可能你都不知道为什么你要去写这些.那么本篇文章将告诉你,Dagger2既然增加了代码量,那么,我们为什么还要去使用它. 什么是Dagger2 Dagger2是Dagger的升级版,是An

  • kotlin使用Dagger2的过程全纪录

    前言 Dagger2作为依赖注入神器,相信很多朋友都听说过它的大名.只不过它的有些概念,理解起来并不是那么清晰,并且在使用的过程中,也比较迷糊. Dagger2有Google接手开发的一个基于JSR-330标准的依赖注入框架,它会在编译期间自动生成相关代码,负责依赖对象的创建,达到解耦目的. 下面将详细介绍关于kotlin使用Dagger2的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. kotlin中配置Dagger2 在app模块的build.gradle文件中进

  • ubuntu 16.04安装的过程全纪录

    这篇文章记录我安装ubuntu的过程,由于安装过程中没有截图,所以本文的图片都来源网络. 1 制作 Ubuntu 16.04 LTS U 盘启动盘 (1) 到官网到下载 Ubuntu 16.04 LTS系统安装镜像 https://www.ubuntu.com/download/desktop (2) 使用 UltraISO 制作 U 盘启动盘.首先插入U盘,接着使用 UltraISO 打开 Ubuntu 16.04 LTS 的 ISO 镜像,然后依次点击 UltraISO 菜单栏的"启动&qu

  • Windows下Apache + PHP SESSION丢失的解决过程全纪录

    今天的工作是迁移一个PHP站点至新服务器. 创建一台Windows Server 2008虚拟机,并在其上停掉net stop http服务(避免争抢80端口),安装配置 Apache + PHP,迁移站点至新服务器,站点正常打开,Apache + PHP运行正常,连接MySql Server正常,一切都美好,本想着半小时完成的工作,紧接着噩梦开始了. 该站点使用单点登录,登录过程包含JavaScript异步操作,包含SOAP调用Web Service.登录却直接跳回了登录页面,没有任何错误提示

  • apache的源码安装详细过程全纪录

    最近要开始学习nagios监控方面的知识了,但是nagios与apache结合的比较紧密,所以本篇文章就先把apache的源码安装学习下. 我们现在分以下步骤进行安装apache: 1. 安装编译环境 2. 卸载原有apache 3. 下载解压源码包 4. 安装apache 5. 测试apache 6. 查看apache安装生成的目录 7. 查看apache的配置文件 8. apache加入系统服务 一.安装编译环境 在安装apache之前,我们需要安装编译apache时所需要的相关软件包,如下

  • jQuery焦点图切换简易插件制作过程全纪录

    首页经常是需要一个焦点图切换的效果,最近做的项目也正好需要,所以在网上搜索,后面查到了一个半成品的插件,这里我自己修改了一下. js文件夹下面有两个文件夹jquery.jslide.js与jquery.jslides.js,前面一个是我改写的,第二个是原作者的文件.下图是效果图: 一.静态效果 <div class="slide_wrap"> <ul id="slides2" class="slide"> <li s

  • Redis 2.8-4.0过期键优化过程全纪录

    前言 之前 白馨(陌陌-技术保障部存储工程师 )在Redis技术交流群里,总结了一下Redis从2.8~4.0关于过期键相关的fix记录,非常有帮助,但有些东西未尽详细,本文将进行详细说明. 先从一个问题来看,运行环境如下: Redis: 2.8.19 db0:keys=10000000,expires=10000000 主从结构 从下图中可以看到,在从节点get hello非空,在主节点get hello为空,之后从节点get hello为空,经排查主从同步offset基本正常,但出现了主从不

  • JWT登录认证实战模拟过程全纪录

    目录 Token 认证流程 Token 认证优点 JWT 结构 JWT 基本使用 实战:使用 JWT 登录认证 附:为什么使用jwt而不使用session 总结 Token 认证流程 作为目前最流行的跨域认证解决方案,JWT(JSON Web Token) 深受开发者的喜爱,主要流程如下: 客户端发送账号和密码请求登录 服务端收到请求,验证账号密码是否通过 验证成功后,服务端会生成唯一的 token,并将其返回给客户端 客户端接受到 token,将其存储在 cookie 或者 localStro

  • SpringBoot项目集成xxljob实现全纪录

    目录 xxljob介绍 代码配置过程 1.引入xxl-job的依赖 2.编写配置文件 3. 编写配置类 4.新建Job文件夹,将自己写的类放到此文件夹下 5. 编写业务代码 登录xxl-Job并配置 1.执行器管理--新增执行器 2.任务管理--新增任务 测试: 断点调试 查看调度日志: xxljob介绍 XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展.现已开放源代码并接入多家公司线上产品线,开箱即用. 被称为任务调度中心,可做定时任务. 优点特性如下

  • Android中AndroidStudio&Kotlin安装到运行过程及常见问题汇总

    工具:Android Studio 语言:Kotlin 1.Android Studio的安装与配置 AndoridStudio官方网址:直接点击即可进入AS官网下载页面 1.1.android studio的下载与安装 下载好AS的文件后,直接打开androidstudio的exe文件,弹出安装欢迎对话框如下图所示 点击Next 勾选Android Virtual Devide(AVD)安卓虚拟设备,这样之后可以在电脑上生成一个虚拟的安卓机以方便我们运行编写好的程序.如果准备用真机进行测试的,

随机推荐