Dagger2 使用全解析
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 好几年了,终于把思路理的比较清晰了,在此抛砖引玉,如果错误,欢迎指正。