Using Dagger in Android apps 笔记

4 阅读6分钟

Using Dagger in Android apps 笔记

developer.android.com/training/de…

最佳实践总结

  • 尽可能使用 @Inject 构造函数注入将类型添加到 Dagger 图中。当无法做到时: 使用 @Binds 告诉 Dagger 接口应该使用哪个实现。 使用 @Provides 告知 Dagger 如何提供项目不拥有的类。

  • 在一个组件中,你应该只声明一次模块。即一个 module 应该只被包含在一个 Component 中,也即不要重复引用。

  • 根据注解使用的生命周期命名作用域注解。例如包括 @ApplicationScope 、 @LoggedUserScope 和 @ActivityScope 。

全局对象依赖图

一个应用应该在合适的地方,如 Android 的 Application 类中缓存这个顶层的全局共用的对象图实例。

// Definition of the Application graph
@Component
interface ApplicationComponent { ... }

// appComponent lives in the Application class to share its lifecycle
class MyApplication: Application() {
    // Reference to the application graph that is used across the whole app
    val appComponent = DaggerApplicationComponent.create()
}

您不希望在 onCreate() 方法中创建 Activity 所需的依赖项,而是希望 Dagger 为您填充这些依赖项。对于字段注入,您需要将 @Inject 注解应用于您希望从 Dagger 图中获取的字段。

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    // 为了简化, LoginViewModel 不是一个 Android 架构组件的 ViewModel;
    // 它只是一个普通的类,充当 ViewModel 的角色。
    @Inject lateinit var loginViewModel: LoginViewModel
}

通过定义一个方法(方法名任意,但是通常是 inject)来告诉 dagger ,这个方法的参数,它有字段需要注入,请提供依赖。

@Component
interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is requesting.
    fun inject(activity: LoginActivity)
    
    fun inject(activity: RegistrationActivity)

}

注入时机,Activity 是在 onCreate() 方法中,并且在调用 super.onCreate() 之前,而对于 Fragment ,在 onAttach() 方法中,但可以在调用 super.onAttach() 之前或之后进行。

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Make Dagger instantiate @Inject fields in LoginActivity
        (applicationContext as MyApplication).appComponent.inject(this)
        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

// @Inject tells Dagger how to create instances of LoginViewModel
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

Dagger modules

Dagger 模块是一个带有 @Module 注解的类。在那里,你可以使用 @Provides 注解定义依赖,提供他人需要的依赖。

// @Module informs Dagger that this class is a Dagger Module
@Module
class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService::class.java)
    }
}

为了让 Dagger 图知道这个模块,你需要按照以下方式将其添加到 @Component 接口中:

// The "modules" attribute in the @Component annotation tells Dagger what Modules
// to include when building the graph
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    ...
}

Dagger scopes

在 Dagger 基础页面中提到,作用域是用来在组件中创建类型唯一实例的一种方式。即把一个实例作用域限定于组件生命周期中。 @Singleton 是 javax.inject 包中唯一的范围注解。你可以用它来注解 ApplicationComponent 以及在整个应用程序中想要重用的对象。

注意:只要 component 和 type 都使用相同的 scope 注解,你就可以在组件中获取该类型的唯一实例。 @Singleton 随 Dagger 库提供,通常用于注解应用程序组件,但你也可以创建一个具有不同名称的自定义注解(例如 @ApplicationScope )。你自定义的其他 scope 注释其实与 @Singleton 功能上是一样的,只是名字不同而已,@Singleton 只是内置的,仅此而已。

@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}

@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Module
class NetworkModule {
    // Way to scope types inside a Dagger Module
    @Singleton
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService { ... }
}

注意:使用作用域注解的模块只能用于带有相同作用域注解的组件中。

注意:在使用构造函数注入(使用 @Inject )时,在类中添加作用域注解,如上面代码中的 UserRepository 类,添加了 @Singleton 注解。而在使用 Dagger 模块时,在 @Provides 方法中添加它们。

Dagger subcomponents

Subcomponents 继承与扩展父 Component,Subcomponent 中的对象可以依赖父 Component 中的对象。 要创建子组件的实例,你需要父组件的实例。因此,父组件提供给子组件的对象仍然属于父组件的作用域。

比如我们创建一个子组件:

// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent
@Subcomponent
interface LoginComponent {

    // Factory that is used to create instances of this subcomponent
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    // This tells Dagger that LoginActivity requests injection from LoginComponent
    // so that this subcomponent graph needs to satisfy all the dependencies of the
    // fields that LoginActivity is injecting
    fun inject(loginActivity: LoginActivity)
}

注意,这儿我们虽然定义了一个子组件,但是并没有指定它的父组件的信息!同时,我们也定义了一个工厂方法,以方便创建这个子组件!

下面我们看看怎么样定义父子组件的关系:

  1. 创建一个新的 Dagger 模块(例如 SubcomponentsModule ),将子组件的类传递给注解的 subcomponents 属性。
// The "subcomponents" attribute in the @Module annotation tells Dagger what
// Subcomponents are children of the Component this module is included in.
// 包含这个 Module 的 Component,即是 subcomponents 的父组件!
@Module(subcomponents = LoginComponent::class)
class SubcomponentsModule {}
  1. 添加新模块(即 SubcomponentsModule )到 ApplicationComponent :
// Including SubcomponentsModule, tell ApplicationComponent that
// LoginComponent is its subcomponent.
@Singleton
@Component(modules = [NetworkModule::class, SubcomponentsModule::class])
interface ApplicationComponent {
    // This function exposes the LoginComponent Factory out of the graph so consumers
    // can use it to obtain new instances of LoginComponent
    fun loginComponent(): LoginComponent.Factory
}
  1. 如上代码,在接口中暴露创建 LoginComponent 实例的工厂。ApplicationComponent 的消费者需要知道如何创建 LoginComponent 的实例。父组件必须在它的接口中添加一个方法,让消费者能够通过父组件的实例来创建子组件的实例。

个人思考:子组件继承与扩展父组件,但是是在父组件中指明父子关系的(通过modules属性包含)。对父组件而言,子组件不是透明的。这与类的继承与扩展完全不同,父类是不需要知道有哪些子类的。并且子组件的创建也是通过在父组件中指定的工厂方法来实现的,即子组件的的创建离不开父组件。感觉子组件严格受到父组件的限制与控制!

最后,可以这样使用它:

class LoginActivity: Activity() {
    // Reference to the Login graph
    lateinit var loginComponent: LoginComponent

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // 注意创建:Creation of the login graph using the application graph
        loginComponent = (applicationContext as MyDaggerApplication)
                            .appComponent.loginComponent().create()

        // Make Dagger instantiate @Inject fields in LoginActivity
        loginComponent.inject(this)

        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

另外有一点需要特别注意的:LoginComponent 在 activity 的 onCreate() 方法中创建,当 activity 被销毁时,它也隐式地销毁了,即它的生命周期也终结了!

总结

所有 Component 其实都需要一个 Reference 来引用它,就像在 Application 类中,引用全局的 ApplicationComponent 对象依赖图一样。把 Component 看成一个普通的类对象,同样需要我们来创建与管理,没有魔法。

References: Using Dagger in Android apps (developer.android.com/training/de…)