Dagger2 使用全解析

Dagger是一个注入工具,何为注入,我们要生产一批机器人,每个机器人都有一个控制器,我们可以在机器人内部 new 出一个控制器:

class Robot {
    val controller = Controller()
}

上面的代码 Robot 和 Controller 耦合,修改一下上面的代码,从外部传入控制器,这就叫注入:

class Robot(val controller: Controller)

这样做的好处就是修改了控制器,但是不用修改机器人的代码,一般情况下,我们需要把控制器声明为接口,这样一个机器人就可以兼容不同的控制器

interface Controller {
    fun move()
    fun stop()
}

class BasicController : Controller {
    override fun move() {
        // ...
    }

    override fun stop() {
        // ...
    }
}

class AdvancedController : Controller {
    override fun move() {
        // ...
    }

    override fun stop() {
        // ..
    }
}

class Robot(val controller: Controller)

fun createRobot(controller: Controller) = Robot(controller)

fun test() {
    val basicRobot = createRobot(BasicController())
    val advancedRobot = createRobot(AdvancedController())
}

上面的代码就是精简版的Dagger,当然结构天差地别,但是思路差不多,下面开始讲Dagger。
Dagger是个注入框架,帮助我们来实现注入的功能,拿上面的例子来说,我们写好了 Robot 和 各种 Controller 的代码,Dagger 帮我们将他们联系起来,也就是实现函数 createRobot 的功能。
Dagger2 的功能是通过编译器生成中间代码来实现的,编译器可以为我们生成代码,但是要生成什么代码是需要我们指定的,拿上面的例子来说,我们需要为 Robot 注入一个 Controller,我们需要指定:

  • Controller 的构造方法
  • 需要注入的成员变量
  • 在什么地方注入

未使用 Dagger 之前,代码是这样的:

class Controller
class Robot(val controller: Controller)

// ...

val controller = Controller()
val robot = Robot(controller)

现在我们来改写代码,首先要指定 Controller 的构造方法,在 Controller 的构造函数添加 @Inject 注解:

class Controller @Inject constructor()

指定需要注入的变量

class Robot {
    @Inject lateinit var controller: Controller
} 

现在编译一下,等待 Dagger 生成中间代码,Dagger为我们生成以下的代码:

Controller_Factory.java

public final class Controller_Factory implements Factory<Controller> {
  private static final Controller_Factory INSTANCE = new Controller_Factory();

  @Override
  public Controller get() {
    return new Controller();
  }

  public static Factory<Controller> create() {
    return INSTANCE;
  }
}

Robot_MembersInjector.java

public final class Robot_MembersInjector implements MembersInjector<Robot> {
  private final Provider<Controller> controllerProvider;

  public Robot_MembersInjector(Provider<Controller> controllerProvider) {
    assert controllerProvider != null;
    this.controllerProvider = controllerProvider;
  }

  public static MembersInjector<Robot> create(Provider<Controller> controllerProvider) {
    return new Robot_MembersInjector(controllerProvider);
  }

  @Override
  public void injectMembers(Robot instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.controller = controllerProvider.get();
  }
}

非常好,Dagger 为我们生成了一个 Controller 的构造类 Controller_Factory,我们可以通过

Controller_Factory.create()

获取一个单例的 Controller 对象,或者通过:

Controller_Factory().get()
// or
Controller_Factory.create().get()

创建一个新的 Controller 对象,如果要注入到 Robot,需要使用 Robot_MembersInjector 的 injectMembers 的函数,改造后的最终代码是

class Controller @Inject constructor()

class Robot {
    @Inject lateinit var controller: Controller

    constructor() {
        val factory = Controller_Factory.create()
        val injector = Robot_MembersInjector.create(factory)
        injector.injectMembers(this)
    }
}

现在添加一个注入的成员变量 power:

class Controller @Inject constructor()

class Power @Inject constructor()

class Robot {
    @Inject lateinit var controller: Controller
    @Inject lateinit var power: Power

    constructor() {
        val controllerFactory = Controller_Factory.create()
        val powerFactory = Power_Factory.create()

        val injector = Robot_MembersInjector.create(controllerFactory, powerFactory)

        injector.injectMembers(this)
    }
}

这时应该祭出 Componet 了,我们来声明一个 AppComponet:

@Component
interface AppComponent {
    fun inject(robot: Robot)
}

然后使用 Component 来注入变量,Dagger 会根据 AppComponent 生成一个 DaggerAppComponent:

class Robot {
    @Inject lateinit var controller: Controller
    @Inject lateinit var power: Power

    constructor() {
        DaggerAppComponent.builder().build().inject(this)
    }
}

我们来分析一下 DaggerAppComponent 的源码:

public final class DaggerAppComponent implements AppComponent {
  private MembersInjector<Robot> robotMembersInjector;

  private DaggerAppComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static AppComponent create() {
    return new Builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.robotMembersInjector =
        Robot_MembersInjector.create(Controller_Factory.create(), Power_Factory.create());
  }

  @Override
  public void inject(Robot robot) {
    robotMembersInjector.injectMembers(robot);
  }

  public static final class Builder {
    private Builder() {}

    public AppComponent build() {
      return new DaggerAppComponent(this);
    }
  }
}

DaggerAppComponent 为我们构造了 Robot_MembersInjector ,在 public void inject(Robot robot) 调用了 injectMembers 方法,如果我们把 Robot 的代码复制一遍,新建一个 Robot2 类,AppComponet 修改为:

@Component
interface AppComponent {
    fun inject(robot: Robot)
    fun inject(robot: Robot2)
}

DaggerAppComponent 有什么变化呢?它会多一个 robot2MembersInjector 成员变量,

@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {

this.robotMembersInjector =
    Robot_MembersInjector.create(Controller_Factory.create(), Power_Factory.create());

this.robot2MembersInjector =
    Robot2_MembersInjector.create(Controller_Factory.create(), Power_Factory.create());
}

@Override
public void inject(Robot robot) {
robotMembersInjector.injectMembers(robot);
}

@Override
public void inject(Robot2 robot) {
robot2MembersInjector.injectMembers(robot);
}

同理,在 Componet 添加多个 inject,会生成对应的 MembersInjector。现在我们注入的 Power 和 Controller 是不是单例的呢?不是的。来看 Robot_MembersInjector 的 injectMembers 函数:

@Override
public void injectMembers(Robot instance) {
if (instance == null) {
  throw new NullPointerException("Cannot inject members into a null reference");
}
instance.controller = controllerProvider.get();
instance.power = powerProvider.get();
}

对应的 MembersInjector 的 get 方法都是 new 出一个对象。现在我们想把 Power 注入变成单例的,先加个 *@Singleton*

@Singleton
class Power @Inject constructor()

编译一下,报错了...

Error:(11, 2) 错误: com.sw926.dagger2example.AppComponent (unscoped) may not reference scoped bindings:
@dagger.Component()
^
      @Singleton class com.sw926.dagger2example.Power

Component 是连接 Provider 和 Injector 的桥梁,*@Singleton* 是作用域,不在一个域看来不让连接,那么给 Component 也加上注解:

@Singleton
@Component
interface AppComponent {
    fun inject(robot: Robot)
}

编译通过了,再来看 DaggerAppComponent 的源码,powerProvider 部分改变了,变成了

this.powerProvider = DoubleCheck.provider(Power_Factory.create());

DoubleCheck 的源码不用贴了,作用就是能保证 Provider get 的时候返回的是单例,而且是安全的,而是是懒加载的,很完美。

作为一个严谨的程序,一个 Power 哪里够用,我们需要一个备用的,也就是说,需要两个单例的 Power,现在 Module 要登场了。Module 是构造方法的仓库,我们把 Power 的注解去掉,改为在 Module 中提供构造方法,然后在 Component 中指定 Module

class Power

@Module
class AppModule {

    @Provides
    @Singleton
    fun providePower() = Power()
}

@Singleton
@Component(modules = [(AppModule::class)])
interface AppComponent {
    fun inject(robot: Robot)
}

现在添加一个 BackUp Power

class Power(val name: String)

// 添加一个 BackUp 注解
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface BackUp {
}

// ...

@Module
class AppModule {

    @Singleton
    @Provides
    fun providePower(): Power = Power("main")

    @BackUp
    @Singleton
    @Provides
    fun provideBackUpPower(): Power = Power("backup")
}

class Robot {
    @Inject lateinit var controller: Controller

    @Inject lateinit var power: Power
    @field:[Inject BackUp] lateinit var backUpPower: Power

    constructor() {
        DaggerAppComponent.builder().build().inject(this)
    }
}

在注入 Power的时候,默认是 main, 如果添加了 @BackUp 注解,就是 backup,Robot_MembersInjector 会有三个 Provider

  @Override
  public void injectMembers(Robot instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.controller = controllerProvider.get();
    instance.power = powerProvider.get();
    instance.backUpPower = backUpPowerProvider.get();
  }

是时候来验证一下是否是单例了,我们来创建两个 Robot ,看他们的 Power 和 BackUpPower 是否一样:

val robot1 = Robot()
val robot2 = Robot()

Log.d("Dagger2Test", "robot1 :(${robot1.power}, ${robot1.backUpPower}), \nrobot2: (${robot2.power}, ${robot2.backUpPower}")

运行结果

robot1 :(com.sw926.dagger2example.Power@5f1ad48, com.sw926.dagger2example.Power@862e7e1),
robot2: (com.sw926.dagger2example.Power@dc97906, com.sw926.dagger2example.Power@24c8bc7

说好的单例呢,怎么能骗人呢?大神们当然不会骗人,那肯定是我们的使用方式不对了,我们再来看看代码:

@Singleton
@Provides
fun providePower(): Power = Power("main")

// ...

@Singleton
@Component(modules = [(AppModule::class)])
interface AppComponent {

    fun inject(robot: Robot)
}

我们把注入分为三个部分:

  • 提供者(Provider)
  • 接受者,Robot 中的成员变量 power
  • 提供者和接受者直接的桥梁、纽带,也就 AppComponent

Dagger 中,使用 Scope 注解的 Provider 提供的对象在作用域内唯一,这个唯一性由谁来控制呢?当然是 Component,每个 Component 只能确保自己注入时的作用域唯一,上面的例子每个 Robot 都创建了一个 AppComponent,所以注入的对象不相同,如果我们把 AppComponent 放在 Application 中创建,
那么注入的对象就是全局唯一对象了:

class App : Application() {

    companion object {
        lateinit var appComponent: AppComponent
    }

    override fun onCreate() {
        super.onCreate()
        appComponent = DaggerAppComponent.builder().build()
    }

}

// ...

class Robot {
    @Inject lateinit var controller: Controller

    @Inject lateinit var power: Power
    @field:[Inject BackUp] lateinit var backUpPower: Power

    constructor() {
        App.appComponent.inject(this)
    }
}

运行结果

robot1 :(com.sw926.dagger2example.Power@862e7e1, com.sw926.dagger2example.Power@dc97906),
robot2: (com.sw926.dagger2example.Power@862e7e1, com.sw926.dagger2example.Power@dc97906

如果同一个作用域内希望获取两个 Power,那么必须要起个名字区分一下,Qualifier 就是用来区别作用域内的两个对象,我们也可以用 @Named,相当于为第二个 Power起了一个名字:

@Module
class AppModule {

    @Singleton
    @Provides
    fun providePower(): Power = Power("main")

    @Named("backup")
    @Provides
    @Singleton
    fun provideBackUpPower(): Power = Power("backup")
}

class Robot {
    @Inject lateinit var controller: Controller

    @Inject lateinit var power: Power
    @field:[Inject Named("backup")] lateinit var backUpPower: Power

    constructor() {
        App.appComponent.inject(this)
    }
}

现在我们有了一个全局的 AppComponent,放在 Application 里面,管理 App 全局唯一的对象,现在我想有一个 Activity 生命周期的 Component,放在 每个 Activity 里面,Activity 的生命周期肯定在 App 的声明周期里面,所以 ActivityComponent 需要能够注入 AppComponent 注入的对象,现在 AppComponent 能够注入 Power BackUpPower,那么 ActivityComponent 也需要能够注入,这是需要用到 dependencies:

@ForActivity
@Component(dependencies = [(AppComponent::class)], modules = [(ActivityModule::class)])
interface ActivityComponent {

    fun inject(mainActivity: MainActivity)
}

我们为 ActivityComponent 设置了一个作用域 @ForActivity,ActivityComponent 依赖于 AppComponent,现在来看看这样做有什么用。

注入一个对象需要一个 Provider,Provider 有以下几种形式:

  • 指定类的构造函数
    kotlin class Controller @Inject constructor()
  • 使用 Provider 函数
    kotlin @Singleton @Provides fun providePower(): Power = Power("main")
  • 从 dependencies 读取

前两种不用说了,来说说第三种,ActivityComponent 的 module 是 ActivityModule

@Module
class ActivityModule {

    @ForActivity
    @Provides
    fun providePowerName(@Named("backup") power: Power): String {
        return power.name
    }
}

providePowerName 需要参数 *@Named("backup") power: Power*,这个 power 哪里找?当然是 Dagger 帮我们找,Provider 的三种形式,第一种没有,ActivityModule 里面没有,AppModule 里面有,但是怎么建立连接呢,很简单,在 AppComponent 写一个函数就行

@Singleton
@Component(modules = [(AppModule::class)])
interface AppComponent {

    fun inject(robot: Robot)

    @Named("backup")
    fun getBackUpPower(): Power
}

为什么写一个函数就行,源码我也没看过,就当做这是 Dagger 的协议吧,编译后会生成对应的函数

@Override
public Power getBackUpPower() {
  return provideBackUpPowerProvider.get();
}

现在 ActivityComponent 的 module ActivityModule 找到了对应的 Provider,就可以正常提供 Power Name 了。

有了 AppComponent、ActivityComponent,下面就要添加 FragmentComponent了,Fragment 依赖于 Activity,那么我们这样做,FragmentComponent 只能由 ActivityComponent 创建,这就要用到 SubComponent,FragmentComponent 使用 @Subcomponent 注解,同时必须注明一个 Builder:

@ForFragment
@Subcomponent(modules = [(FragmentModule::class)])
interface FragmentComponent {

    @Subcomponent.Builder
    interface Builder {
        fun build(): FragmentComponent
    }

}

ActivityComponent 改写为:

@ForActivity
@Component(dependencies = [(AppComponent::class)], modules = [(ActivityModule::class)])
interface ActivityComponent {

    fun inject(mainActivity: MainActivity)

    fun fragmentComponent(): FragmentComponent.Builder

}

在 ActivityModule 里面指明 subcomponents :

@Module(subcomponents = [(FragmentComponent::class)])
class ActivityModule {

    @ForActivity
    @Provides
    fun providePowerName(@Named("backup") power: Power): String {
        return power.name
    }
}

编译完成之后我们就可以使用 ActivityComponent 创建一个 FragmentComponent 了:

val activityComponent = DaggerActivityComponent.builder().appComponent(App.appComponent).build()
activityComponent.inject(this)

val fragmentComponent = activityComponent.fragmentComponent().build()

最后说一下 Module 的 includes,也就是一个 Module 可以包含一组 Module

@Module
class ActivityModule2 {

    @Provides
    fun provideException(): Exception {
        return Exception("test Exception ")
    }
}

@Module(subcomponents = [(FragmentComponent::class)], includes = [(ActivityModule2::class)])
class ActivityModule {

    @ForActivity
    @Provides
    fun providePowerName(@Named("backup") power: Power): String {
        return power.name
    }
}

class MainActivity : AppCompatActivity() {

    @Inject lateinit var exception: Exception

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val activityComponent = DaggerActivityComponent.builder().appComponent(App.appComponent).build()
        activityComponent.inject(this)

        Log.d("Dagger2Test", "Exception: $exception")
    }
}

运行结果:

D/Dagger2Test: Exception: java.lang.Exception: test Exception 

以上,使用 Dagger 好几年了,终于把思路理的比较清晰了,在此抛砖引玉,如果错误,欢迎指正。

Dagger2 使用全解析的更多相关文章

  1. Google Maps地图投影全解析(3):WKT形式表示

    update20090601:EPSG对该投影的编号设定为EPSG:3857,对应的WKT也发生了变化,下文不再修改,相对来说格式都是那样,可以到http://www.epsg-registry.or ...

  2. C#系统缓存全解析(转载)

    C#系统缓存全解析 对各种缓存的应用场景和方法做了很详尽的解读,这里推荐一下 转载地址:http://blog.csdn.net/wyxhd2008/article/details/8076105

  3. 【凯子哥带你学Framework】Activity界面显示全解析

    前几天凯子哥写的Framework层的解析文章<Activity启动过程全解析>,反响还不错,这说明“写让大家都能看懂的Framework解析文章”的思想是基本正确的. 我个人觉得,深入分 ...

  4. iOS Storyboard全解析

    来源:http://iaiai.iteye.com/blog/1493956 Storyboard)是一个能够节省你很多设计手机App界面时间的新特性,下面,为了简明的说明Storyboard的效果, ...

  5. 【转载】Fragment 全解析(1):那些年踩过的坑

    http://www.jianshu.com/p/d9143a92ad94 Fragment系列文章:1.Fragment全解析系列(一):那些年踩过的坑2.Fragment全解析系列(二):正确的使 ...

  6. (转)ASP.NET缓存全解析6:数据库缓存依赖

    ASP.NET缓存全解析文章索引 ASP.NET缓存全解析1:缓存的概述 ASP.NET缓存全解析2:页面输出缓存 ASP.NET缓存全解析3:页面局部缓存 ASP.NET缓存全解析4:应用程序数据缓 ...

  7. jQuery&nbsp;Ajax&nbsp;实例&nbsp;全解析

    jQuery Ajax 实例 全解析 jQuery确实是一个挺好的轻量级的JS框架,能帮助我们快速的开发JS应用,并在一定程度上改变了我们写JavaScript代码的习惯. 废话少说,直接进入正题,我 ...

  8. ARM内核全解析,从ARM7,ARM9到Cortex-A7,A8,A9,A12,A15到Cortex-A53,A57

    转自: ARM内核全解析,从ARM7,ARM9到Cortex-A7,A8,A9,A12,A15到Cortex-A53,A57 前不久ARM正式宣布推出新款ARMv8架构的Cortex-A50处理器系列 ...

  9. jQuery Ajax 全解析

    转自:http://www.cnblogs.com/qleelulu/archive/2008/04/21/1163021.html 本文地址: jQuery Ajax 全解析 本文作者:QLeelu ...

随机推荐

  1. nginx 防火墙、权限问题

      1.nginx安装,配置完成之后,尝试访问没有响应,主机可以ping通,/var/log/nginx/access.log日志没有查到任何记录 解决方法:查看linux防火墙,关闭 命令:ipta ...

  2. 查看三种MySQL字符集的方法

    查看MySQL字符集的命令是我们经常会使用到的,下文就介绍了其中的三种查看MySQL字符集的命令,供您参考学习. 作者:佚名来源:互联网|2010-10-09 11:36 移动端 收藏 分享 CTO训 ...

  3. Python3 的分支与循环

    1:条件分支 if 条件 : 语句 else: 语句 2.缩写 else: if : 可以简写为 elif ,因此Python 可以有效的避免"悬挂else" 举例: #悬挂els ...

  4. sqlite3使用事务处理[zz]

    <!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-al ...

  5. IOS学习笔记27—使用GDataXML解析XML文档

    http://blog.csdn.net/ryantang03/article/details/7868246

  6. web.xml 报错

    1.The markup in the document following the root element must be well-formed. 原因是配置时没有 放在根下 <web-a ...

  7. SDP(3):ScalikeJDBC- JDBC-Engine:Fetching

    ScalikeJDBC在覆盖JDBC基本功能上是比较完整的,而且实现这些功能的方式比较简洁,运算效率方面自然会稍高一筹了.理论上用ScalikeJDBC作为一种JDBC-Engine还是比较理想的:让 ...

  8. python3 第十三章 - 数据类型之tuple(元组)

    元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可: language = ('c', 'c++', 'py ...

  9. finally中关闭资源

    对finally中关闭资源是否还要使用try...catch老是感到迷惑,现在存个例子,省的忘了 public StringBuilder readTxtFile(File file){ String ...

  10. JAVA中比较两个文件夹不同的方法

    JAVA中比较两个文件夹不同的方法,可以通过两步来完成,首先遍历获取到文件夹下的所有文件夹和文件,再通过文件路径和文件的MD5值来判断文件的异同.具体例子如下: public class TestFo ...