1. 程式人生 > >Dagger2 使用全解析

Dagger2 使用全解析

ember factor 控制器 oid 好處 人的 field 會有 幫我

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 使用全解析