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)
}
注意,这儿我们虽然定义了一个子组件,但是并没有指定它的父组件的信息!同时,我们也定义了一个工厂方法,以方便创建这个子组件!
下面我们看看怎么样定义父子组件的关系:
- 创建一个新的 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 {}
- 添加新模块(即 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
}
- 如上代码,在接口中暴露创建 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…)