在Dagger 基础知识页面中,解释了 Dagger 如何帮助您自动化应用中的依赖项注入。使用 Dagger,您无需编写冗长且容易出错的样板代码。
最佳实践摘要
- 尽可能使用带有
@Inject
的构造函数注入将类型添加到 Dagger 图中。如果不是- 使用
@Binds
告诉 Dagger 接口应该具有哪个实现。 - 使用
@Provides
告诉 Dagger 如何提供项目不拥有的类。
- 使用
- 您应该只在一个组件中声明一次模块。
- 根据使用注释的生命周期命名作用域注释。示例包括
@ApplicationScope
、@LoggedUserScope
和@ActivityScope
。
添加依赖项
要在您的项目中使用 Dagger,请在build.gradle
文件中将这些依赖项添加到您的应用程序中。您可以在此 GitHub 项目中找到 Dagger 的最新版本。
Kotlin
plugins { id 'kotlin-kapt' } dependencies { implementation 'com.google.dagger:dagger:2.x' kapt 'com.google.dagger:dagger-compiler:2.x' }
Java
dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }
Android 中的 Dagger
考虑一个示例 Android 应用,其中包含图 1 中的依赖关系图。
在 Android 中,通常会创建一个位于应用程序类中的 Dagger 图,因为您希望在应用运行期间始终在内存中存在该图的实例。通过这种方式,该图将附加到应用生命周期。在某些情况下,您可能还希望在图中使用应用程序上下文。为此,您还需要将图放在Application
类中。这种方法的一个优点是该图可用于其他 Android 框架类。此外,它通过允许您在测试中使用自定义Application
类来简化测试。
由于生成图的接口使用@Component
进行注释,因此您可以将其称为ApplicationComponent
或ApplicationGraph
。通常,您会在自定义Application
类中保留该组件的一个实例,并在每次需要应用程序图时调用它,如下面的代码片段所示。
Kotlin
// 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() }
Java
// Definition of the Application graph @Component public interface ApplicationComponent { } // appComponent lives in the Application class to share its lifecycle public class MyApplication extends Application { // Reference to the application graph that is used across the whole app ApplicationComponent appComponent = DaggerApplicationComponent.create(); }
由于某些 Android 框架类(例如 Activity 和 Fragment)是由系统实例化的,因此 Dagger 无法为您创建它们。特别是对于 Activity,任何初始化代码都需要放在 onCreate()
方法中。这意味着您不能像在之前的示例中那样,在类的构造函数中使用 @Inject
注解(构造函数注入)。相反,您必须使用字段注入。
您希望 Dagger 为您填充 Activity 需要的依赖项,而不是在 onCreate()
方法中创建这些依赖项。对于字段注入,您只需将 @Inject
注解应用于您想要从 Dagger 图中获取的字段。
Kotlin
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject lateinit var loginViewModel: LoginViewModel }
Java
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject LoginViewModel loginViewModel; }
为简单起见,LoginViewModel
不是 Android 架构组件 ViewModel;它只是一个充当 ViewModel 的普通类。有关如何注入这些类的更多信息,请查看官方 Android Blueprints Dagger 实现 中的代码,位于 dev-dagger 分支。
Dagger 的一个考虑因素是注入的字段不能是私有的。它们至少需要具有包私有可见性,如前面的代码所示。
注入 Activity
Dagger 需要知道 LoginActivity
必须访问图才能提供其需要的 ViewModel
。在 Dagger 基础知识 页面中,您使用 @Component
接口通过公开具有所需返回类型的函数来从图中获取对象。在这种情况下,您需要告诉 Dagger 关于一个对象(此处为 LoginActivity
),该对象需要注入依赖项。为此,您公开一个函数,该函数将请求注入的对象作为参数。
Kotlin
@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) }
Java
@Component public interface ApplicationComponent { // This tells Dagger that LoginActivity requests injection so the graph needs to // satisfy all the dependencies of the fields that LoginActivity is injecting. void inject(LoginActivity loginActivity); }
此函数告诉 Dagger LoginActivity
想要访问图并请求注入。Dagger 需要满足 LoginActivity
需要的所有依赖项(具有其自身依赖项的 LoginViewModel
)。如果您有多个请求注入的类,则必须在组件中使用其确切类型声明所有这些类。例如,如果您有 LoginActivity
和 RegistrationActivity
请求注入,则您将有两个 inject()
方法,而不是一个涵盖两种情况的通用方法。通用的 inject()
方法不会告诉 Dagger 需要提供什么。接口中的函数可以具有任何名称,但是当它们接收要注入的对象作为参数时,将它们称为 inject()
是 Dagger 中的一种约定。
要在 Activity 中注入对象,您可以使用在您的 Application
类中定义的 appComponent
并调用 inject()
方法,传入请求注入的 Activity 实例。
使用 Activity 时,请在调用 super.onCreate()
之前在 Activity 的 onCreate()
方法中注入 Dagger,以避免与 Fragment 还原出现问题。在 super.onCreate()
中的还原阶段,Activity 将附加可能想要访问 Activity 绑定的 Fragment。
使用 Fragment 时,请在 Fragment 的 onAttach()
方法中注入 Dagger。在这种情况下,可以在调用 super.onAttach()
之前或之后进行。
Kotlin
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 ) { ... }
Java
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { // Make Dagger instantiate @Inject fields in LoginActivity ((MyApplication) getApplicationContext()).appComponent.inject(this); // Now loginViewModel is available super.onCreate(savedInstanceState); } } public class LoginViewModel { private final UserRepository userRepository; // @Inject tells Dagger how to create instances of LoginViewModel @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
让我们告诉 Dagger 如何提供其余依赖项以构建图
Kotlin
class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor( private val loginService: LoginRetrofitService ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } } public class UserLocalDataSource { @Inject public UserLocalDataSource() {} } public class UserRemoteDataSource { private final LoginRetrofitService loginRetrofitService; @Inject public UserRemoteDataSource(LoginRetrofitService loginRetrofitService) { this.loginRetrofitService = loginRetrofitService; } }
Dagger 模块
在此示例中,您使用的是 Retrofit 网络库。UserRemoteDataSource
依赖于 LoginRetrofitService
。但是,创建 LoginRetrofitService
实例的方法与您到目前为止所做的方法不同。它不是类实例化;它是调用 Retrofit.Builder()
并传入不同的参数以配置登录服务的结果。
除了 @Inject
注解之外,还有另一种方法可以告诉 Dagger 如何提供类的实例:Dagger 模块内的信息。Dagger 模块是一个用 @Module
注解的类。在那里,您可以使用 @Provides
注解定义依赖项。
Kotlin
// @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) } }
Java
// @Module informs Dagger that this class is a Dagger Module @Module public 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 public LoginRetrofitService provideLoginRetrofitService() { // Whenever Dagger needs to provide an instance of type LoginRetrofitService, // this code (the one inside the @Provides method) is run. return new Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService.class); } }
模块是一种语义上封装如何提供对象的信息的方法。如您所见,您调用了类 NetworkModule
来对提供与网络相关的对象的相关逻辑进行分组。如果应用程序扩展,您还可以添加如何在此处提供 OkHttpClient
,或者如何配置 Gson 或 Moshi。
@Provides
方法的依赖项是该方法的参数。对于前面的方法,LoginRetrofitService
可以不依赖任何依赖项,因为该方法没有参数。如果您声明了 OkHttpClient
作为参数,则 Dagger 需要从图中提供 OkHttpClient
实例以满足 LoginRetrofitService
的依赖项。例如
Kotlin
@Module class NetworkModule { // Hypothetical dependency on LoginRetrofitService @Provides fun provideLoginRetrofitService( okHttpClient: OkHttpClient ): LoginRetrofitService { ... } }
Java
@Module public class NetworkModule { @Provides public LoginRetrofitService provideLoginRetrofitService(OkHttpClient okHttpClient) { ... } }
为了使 Dagger 图知道此模块,您必须将其添加到 @Component
接口中,如下所示
Kotlin
// The "modules" attribute in the @Component annotation tells Dagger what Modules // to include when building the graph @Component(modules = [NetworkModule::class]) interface ApplicationComponent { ... }
Java
// The "modules" attribute in the @Component annotation tells Dagger what Modules // to include when building the graph @Component(modules = NetworkModule.class) public interface ApplicationComponent { ... }
向 Dagger 图添加类型的推荐方法是使用构造函数注入(即在类的构造函数上使用 @Inject
注解)。有时,这是不可能的,您必须使用 Dagger 模块。一个示例是当您希望 Dagger 使用计算结果来确定如何创建对象的实例时。每当它必须提供该类型的实例时,Dagger 都会运行 @Provides
方法内的代码。
这就是示例中 Dagger 图的当前外观
图的入口点是 LoginActivity
。因为 LoginActivity
注入 LoginViewModel
,所以 Dagger 会构建一个知道如何提供 LoginViewModel
实例的图,并递归地提供其依赖项。由于类构造函数上的 @Inject
注解,Dagger 知道如何做到这一点。
在 Dagger 生成的 ApplicationComponent
内部,存在一个工厂类型方法,用于获取其知道如何提供的全部类的实例。在此示例中,Dagger 将委托给包含在 ApplicationComponent
中的 NetworkModule
以获取 LoginRetrofitService
的实例。
Dagger 作用域
作用域在 Dagger 基础知识 页面中被提及,作为在组件中拥有唯一类型实例的一种方法。这就是所谓的 *将类型的作用域限定为组件的生命周期*。
因为您可能想要在应用程序的其他功能中使用 UserRepository
,并且可能不想每次需要时都创建一个新对象,所以您可以将其指定为整个应用程序的唯一实例。对于 LoginRetrofitService
也是如此:创建它可能很昂贵,而且您也希望重用该对象的唯一实例。创建 UserRemoteDataSource
的实例并不那么昂贵,因此将其作用域限定为组件的生命周期没有必要。
@Singleton
是随 javax.inject
包提供的唯一作用域注解。您可以使用它来注解 ApplicationComponent
和您想要在整个应用程序中重用的对象。
Kotlin
@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 { ... } }
Java
@Singleton @Component(modules = NetworkModule.class) public interface ApplicationComponent { void inject(LoginActivity loginActivity); } @Singleton public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } } @Module public class NetworkModule { @Singleton @Provides public LoginRetrofitService provideLoginRetrofitService() { ... } }
在将作用域应用于对象时,注意不要引入内存泄漏。只要作用域组件在内存中,创建的对象也在内存中。因为 ApplicationComponent
是在应用程序启动时(在 Application
类中)创建的,所以在应用程序被销毁时它也会被销毁。因此,UserRepository
的唯一实例始终保留在内存中,直到应用程序被销毁。
Dagger 子组件
如果您的登录流程(由单个 LoginActivity
管理)包含多个 Fragment,则应在所有 Fragment 中重用 LoginViewModel
的同一实例。@Singleton
不能注解 LoginViewModel
以重用实例,原因如下:
流程结束后,
LoginViewModel
的实例将保留在内存中。您希望每个登录流程都有一个不同的
LoginViewModel
实例。例如,如果用户注销,则希望使用与用户首次登录时不同的LoginViewModel
实例。
要将 LoginViewModel
的作用域限定为 LoginActivity
的生命周期,您需要为登录流程创建一个新组件(一个新的子图)和一个新的作用域。
让我们创建一个特定于登录流程的图。
Kotlin
@Component interface LoginComponent {}
Java
@Component public interface LoginComponent { }
现在,LoginActivity
应该从 LoginComponent
获取注入,因为它具有登录特定的配置。这消除了从 ApplicationComponent
类注入 LoginActivity
的责任。
Kotlin
@Component interface LoginComponent { fun inject(activity: LoginActivity) }
Java
@Component public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
必须能够访问 ApplicationComponent
中的对象,因为 LoginViewModel
依赖于 UserRepository
。告诉 Dagger 您希望新组件使用另一个组件的一部分的方法是使用 *Dagger 子组件*。新组件必须是包含共享资源的组件的子组件。
子组件是继承并扩展父组件对象图的组件。因此,父组件中提供的所有对象也在子组件中提供。这样,子组件中的对象可以依赖于父组件提供的对象。
要创建子组件的实例,需要一个父组件的实例。因此,父组件提供给子组件的对象仍然作用域限定于父组件。
在本例中,必须将LoginComponent
定义为ApplicationComponent
的子组件。为此,请使用@Subcomponent
注解LoginComponent
。
Kotlin
// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent @Subcomponent interface 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) }
Java
// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent @Subcomponent public interface 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 void inject(LoginActivity loginActivity); }
还必须在LoginComponent
内部定义一个子组件工厂,以便ApplicationComponent
知道如何创建LoginComponent
的实例。
Kotlin
@Subcomponent interface LoginComponent { // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory { fun create(): LoginComponent } fun inject(loginActivity: LoginActivity) }
Java
@Subcomponent public interface LoginComponent { // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory { LoginComponent create(); } void inject(LoginActivity loginActivity); }
要告诉 Dagger LoginComponent
是ApplicationComponent
的子组件,需要:
创建一个新的 Dagger 模块(例如
SubcomponentsModule
),并将子组件的类传递给注解的subcomponents
属性。Kotlin
// The "subcomponents" attribute in the @Module annotation tells Dagger what // Subcomponents are children of the Component this module is included in. @Module(subcomponents = LoginComponent::class) class SubcomponentsModule {}
Java
// The "subcomponents" attribute in the @Module annotation tells Dagger what // Subcomponents are children of the Component this module is included in. @Module(subcomponents = LoginComponent.class) public class SubcomponentsModule { }
将新模块(即
SubcomponentsModule
)添加到ApplicationComponent
。Kotlin
// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent { }
Java
// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules = {NetworkModule.class, SubcomponentsModule.class}) public interface ApplicationComponent { }
请注意,
ApplicationComponent
不再需要注入LoginActivity
,因为这个职责现在属于LoginComponent
,所以可以从ApplicationComponent
中删除inject()
方法。ApplicationComponent
的使用者需要知道如何创建LoginComponent
的实例。父组件必须在其接口中添加一个方法,以允许使用者根据父组件的实例创建子组件的实例。在接口中公开创建
LoginComponent
实例的工厂。Kotlin
@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 }
Java
@Singleton @Component(modules = { NetworkModule.class, SubcomponentsModule.class} ) public interface ApplicationComponent { // This function exposes the LoginComponent Factory out of the graph so consumers // can use it to obtain new instances of LoginComponent LoginComponent.Factory loginComponent(); }
为子组件分配作用域
如果构建项目,可以创建ApplicationComponent
和LoginComponent
的实例。ApplicationComponent
附加到应用程序的生命周期,因为希望只要应用程序在内存中就使用相同的图实例。
LoginComponent
的生命周期是什么?需要LoginComponent
的原因之一是需要在与登录相关的片段之间共享LoginViewModel
的相同实例。但是,也希望每当有新的登录流程时都拥有LoginViewModel
的不同实例。LoginActivity
是LoginComponent
的正确生命周期:对于每个新的活动,都需要一个新的LoginComponent
实例,以及可以使用该LoginComponent
实例的片段。
因为LoginComponent
附加到LoginActivity
的生命周期,所以必须像在Application
类中保留对applicationComponent
的引用一样,在活动中保留对组件的引用。这样,片段就可以访问它。
Kotlin
class LoginActivity: Activity() { // Reference to the Login graph lateinit var loginComponent: LoginComponent ... }
Java
public class LoginActivity extends Activity { // Reference to the Login graph LoginComponent loginComponent; ... }
请注意,变量loginComponent
没有使用@Inject
注解,因为不希望 Dagger 提供该变量。
可以使用ApplicationComponent
获取对LoginComponent
的引用,然后注入LoginActivity
,如下所示:
Kotlin
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) } }
Java
public class LoginActivity extends Activity { // Reference to the Login graph LoginComponent loginComponent; // Fields that need to be injected by the login graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { // Creation of the login graph using the application graph loginComponent = ((MyApplication) getApplicationContext()) .appComponent.loginComponent().create(); // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this); // Now loginViewModel is available super.onCreate(savedInstanceState); } }
LoginComponent
在活动的onCreate()
方法中创建,并且在活动被销毁时会隐式销毁。
LoginComponent
每次被请求时都必须提供LoginViewModel
的相同实例。可以通过创建一个自定义注解作用域并使用它来注解LoginComponent
和LoginViewModel
来确保这一点。请注意,不能使用@Singleton
注解,因为它已经被父组件使用,这会使对象成为应用程序单例(整个应用程序的唯一实例)。需要创建一个不同的注解作用域。
在这种情况下,可以将此作用域称为@LoginScope
,但这并不是一个好习惯。作用域注解的名称不应明确说明其用途。相反,应该根据其生命周期命名,因为注解可以被兄弟组件(例如RegistrationComponent
和SettingsComponent
)重用。这就是为什么应该将其称为@ActivityScope
而不是@LoginScope
的原因。
Kotlin
// Definition of a custom scope called ActivityScope @Scope @Retention(value = AnnotationRetention.RUNTIME) annotation class ActivityScope // Classes annotated with @ActivityScope are scoped to the graph and the same // instance of that type is provided every time the type is requested. @ActivityScope @Subcomponent interface LoginComponent { ... } // A unique instance of LoginViewModel is provided in Components // annotated with @ActivityScope @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
// Definition of a custom scope called ActivityScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope {} // Classes annotated with @ActivityScope are scoped to the graph and the same // instance of that type is provided every time the type is requested. @ActivityScope @Subcomponent public interface LoginComponent { ... } // A unique instance of LoginViewModel is provided in Components // annotated with @ActivityScope @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
现在,如果有两个需要LoginViewModel
的片段,则这两个片段都将获得相同的实例。例如,如果有一个LoginUsernameFragment
和一个LoginPasswordFragment
,则需要由LoginComponent
注入它们。
Kotlin
@ActivityScope @Subcomponent interface LoginComponent { @Subcomponent.Factory interface Factory { fun create(): LoginComponent } // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment // request injection from LoginComponent. The graph needs to satisfy // all the dependencies of the fields those classes are injecting fun inject(loginActivity: LoginActivity) fun inject(usernameFragment: LoginUsernameFragment) fun inject(passwordFragment: LoginPasswordFragment) }
Java
@ActivityScope @Subcomponent public interface LoginComponent { @Subcomponent.Factory interface Factory { LoginComponent create(); } // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment // request injection from LoginComponent. The graph needs to satisfy // all the dependencies of the fields those classes are injecting void inject(LoginActivity loginActivity); void inject(LoginUsernameFragment loginUsernameFragment); void inject(LoginPasswordFragment loginPasswordFragment); }
组件访问存在于LoginActivity
对象中的组件实例。LoginUserNameFragment
的示例代码如下所示:
Kotlin
class LoginUsernameFragment: Fragment() { // Fields that need to be injected by the login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onAttach(context: Context) { super.onAttach(context) // Obtaining the login graph from LoginActivity and instantiate // the @Inject fields with objects from the graph (activity as LoginActivity).loginComponent.inject(this) // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
Java
public class LoginUsernameFragment extends Fragment { // Fields that need to be injected by the login graph @Inject LoginViewModel loginViewModel; @Override public void onAttach(Context context) { super.onAttach(context); // Obtaining the login graph from LoginActivity and instantiate // the @Inject fields with objects from the graph ((LoginActivity) getActivity()).loginComponent.inject(this); // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
LoginPasswordFragment
也是如此。
Kotlin
class LoginPasswordFragment: Fragment() { // Fields that need to be injected by the login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onAttach(context: Context) { super.onAttach(context) (activity as LoginActivity).loginComponent.inject(this) // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
Java
public class LoginPasswordFragment extends Fragment { // Fields that need to be injected by the login graph @Inject LoginViewModel loginViewModel; @Override public void onAttach(Context context) { super.onAttach(context); ((LoginActivity) getActivity()).loginComponent.inject(this); // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
图 3 显示了带有新子组件的 Dagger 图表的外观。带有白点的类(UserRepository
、LoginRetrofitService
和LoginViewModel
)是具有作用域限定于其各自组件的唯一实例的类。
让我们分解图表的各个部分
NetworkModule
(因此还有LoginRetrofitService
)包含在ApplicationComponent
中,因为已在组件中指定了它。UserRepository
保留在ApplicationComponent
中,因为它作用域限定于ApplicationComponent
。如果项目发展壮大,则希望跨不同的功能(例如注册)共享相同的实例。因为
UserRepository
是ApplicationComponent
的一部分,所以它的依赖项(即UserLocalDataSource
和UserRemoteDataSource
)也需要在此组件中,以便能够提供UserRepository
的实例。LoginViewModel
包含在LoginComponent
中,因为它仅由LoginComponent
注入的类需要。LoginViewModel
不包含在ApplicationComponent
中,因为ApplicationComponent
中没有依赖项需要LoginViewModel
。同样,如果没有将
UserRepository
的作用域限定于ApplicationComponent
,Dagger 会自动将UserRepository
及其依赖项作为LoginComponent
的一部分包含在内,因为目前只有UserRepository
的使用位置。
除了将对象的作用域限定于不同的生命周期之外,创建子组件也是一种将应用程序的不同部分彼此隔离的良好实践。
构建应用程序以根据应用程序的流程创建不同的 Dagger 子图有助于在内存和启动时间方面实现更高性能和可扩展性的应用程序。
构建 Dagger 图表时的最佳实践
为应用程序构建 Dagger 图表时
创建组件时,应考虑哪个元素负责该组件的生命周期。在本例中,
Application
类负责ApplicationComponent
,而LoginActivity
负责LoginComponent
。仅在有意义时才使用作用域。过度使用作用域会对应用程序的运行时性能产生负面影响:只要组件在内存中,对象就在内存中,获取作用域对象也更昂贵。当 Dagger 提供对象时,它使用
DoubleCheck
锁定而不是工厂类型提供程序。
测试使用 Dagger 的项目
使用 Dagger 等依赖注入框架的优点之一是它使测试代码更容易。
单元测试
无需对单元测试使用 Dagger。测试使用构造函数注入的类时,无需使用 Dagger 来实例化该类。可以直接调用其构造函数,直接传入伪造或模拟的依赖项,就像它们没有注解一样。
例如,测试LoginViewModel
时
Kotlin
@ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... } class LoginViewModelTest { @Test fun `Happy path`() { // You don't need Dagger to create an instance of LoginViewModel // You can pass a fake or mock UserRepository val viewModel = LoginViewModel(fakeUserRepository) assertEquals(...) } }
Java
@ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } } public class LoginViewModelTest { @Test public void happyPath() { // You don't need Dagger to create an instance of LoginViewModel // You can pass a fake or mock UserRepository LoginViewModel viewModel = new LoginViewModel(fakeUserRepository); assertEquals(...); } }
端到端测试
对于集成测试,最佳实践是创建一个用于测试的TestApplicationComponent
。生产和测试使用不同的组件配置。
这需要对应用程序中的模块进行更多前期设计。测试组件扩展生产组件并安装不同的模块集。
Kotlin
// TestApplicationComponent extends from ApplicationComponent to have them both // with the same interface methods. You need to include the modules of the // component here as well, and you can replace the ones you want to override. // This sample uses FakeNetworkModule instead of NetworkModule @Singleton @Component(modules = [FakeNetworkModule::class, SubcomponentsModule::class]) interface TestApplicationComponent : ApplicationComponent { }
Java
// TestApplicationComponent extends from ApplicationComponent to have them both // with the same interface methods. You need to include the modules of the // Component here as well, and you can replace the ones you want to override. // This sample uses FakeNetworkModule instead of NetworkModule @Singleton @Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class}) public interface TestApplicationComponent extends ApplicationComponent { }
FakeNetworkModule
具有原始NetworkModule
的伪造实现。在那里,可以提供想要替换的任何内容的伪造实例或模拟。
Kotlin
// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService // that you can use in your tests. @Module class FakeNetworkModule { @Provides fun provideLoginRetrofitService(): LoginRetrofitService { return FakeLoginService() } }
Java
// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService // that you can use in your tests. @Module public class FakeNetworkModule { @Provides public LoginRetrofitService provideLoginRetrofitService() { return new FakeLoginService(); } }
在集成测试或端到端测试中,将使用创建TestApplicationComponent
而不是ApplicationComponent
的TestApplication
。
Kotlin
// Your test application needs an instance of the test graph class MyTestApplication: MyApplication() { override val appComponent = DaggerTestApplicationComponent.create() }
Java
// Your test application needs an instance of the test graph public class MyTestApplication extends MyApplication { ApplicationComponent appComponent = DaggerTestApplicationComponent.create(); }
然后,此测试应用程序将用于自定义TestRunner
中,用于运行工具测试。有关此方面的更多信息,请查看在 Android 应用中使用 Dagger 代码实验室。
使用 Dagger 模块
Dagger 模块是一种以语义方式封装如何提供对象的方法。可以在组件中包含模块,也可以在其他模块中包含模块。这功能强大,但很容易被滥用。
一旦将模块添加到组件或其他模块中,它就已经在 Dagger 图中;Dagger 可以提供该组件中的那些对象。在添加模块之前,请检查该模块是否已成为 Dagger 图的一部分,方法是检查它是否已添加到组件中,或者编译项目并查看 Dagger 是否可以找到该模块所需的依赖项。
最佳实践规定,模块应只在一个组件中声明一次(除了某些高级 Dagger 用例之外)。
假设您的图以这种方式配置。ApplicationComponent
包含 Module1
和 Module2
,而 Module1
包含 ModuleX
。
Kotlin
@Component(modules = [Module1::class, Module2::class]) interface ApplicationComponent { ... } @Module(includes = [ModuleX::class]) class Module1 { ... } @Module class Module2 { ... }
Java
@Component(modules = {Module1.class, Module2.class}) public interface ApplicationComponent { ... } @Module(includes = {ModuleX.class}) public class Module1 { ... } @Module public class Module2 { ... }
如果现在 Module2
依赖于 ModuleX
提供的类。一个不好的实践是将 ModuleX
包含在 Module2
中,因为 ModuleX
在图中被包含了两次,如下面的代码片段所示
Kotlin
// Bad practice: ModuleX is declared multiple times in this Dagger graph @Component(modules = [Module1::class, Module2::class]) interface ApplicationComponent { ... } @Module(includes = [ModuleX::class]) class Module1 { ... } @Module(includes = [ModuleX::class]) class Module2 { ... }
Java
// Bad practice: ModuleX is declared multiple times in this Dagger graph. @Component(modules = {Module1.class, Module2.class}) public interface ApplicationComponent { ... } @Module(includes = ModuleX.class) public class Module1 { ... } @Module(includes = ModuleX.class) public class Module2 { ... }
相反,您应该执行以下操作之一
- 重构模块并将公共模块提取到组件中。
- 创建一个包含两个模块共享的对象的新模块,并将其提取到组件中。
不进行这种重构会导致许多模块相互包含,缺乏清晰的组织结构,并且难以查看每个依赖项的来源。
最佳实践(选项 1):ModuleX 在 Dagger 图中只声明一次。
Kotlin
@Component(modules = [Module1::class, Module2::class, ModuleX::class]) interface ApplicationComponent { ... } @Module class Module1 { ... } @Module class Module2 { ... }
Java
@Component(modules = {Module1.class, Module2.class, ModuleX.class}) public interface ApplicationComponent { ... } @Module public class Module1 { ... } @Module public class Module2 { ... }
最佳实践(选项 2):将 Module1
和 Module2
在 ModuleX
中的公共依赖项提取到一个名为 ModuleXCommon
的新模块中,该模块包含在组件中。然后创建另外两个模块,名为 ModuleXWithModule1Dependencies
和 ModuleXWithModule2Dependencies
,分别包含特定于每个模块的依赖项。所有模块都在 Dagger 图中声明一次。
Kotlin
@Component(modules = [Module1::class, Module2::class, ModuleXCommon::class]) interface ApplicationComponent { ... } @Module class ModuleXCommon { ... } @Module class ModuleXWithModule1SpecificDependencies { ... } @Module class ModuleXWithModule2SpecificDependencies { ... } @Module(includes = [ModuleXWithModule1SpecificDependencies::class]) class Module1 { ... } @Module(includes = [ModuleXWithModule2SpecificDependencies::class]) class Module2 { ... }
Java
@Component(modules = {Module1.class, Module2.class, ModuleXCommon.class}) public interface ApplicationComponent { ... } @Module public class ModuleXCommon { ... } @Module public class ModuleXWithModule1SpecificDependencies { ... } @Module public class ModuleXWithModule2SpecificDependencies { ... } @Module(includes = ModuleXWithModule1SpecificDependencies.class) public class Module1 { ... } @Module(includes = ModuleXWithModule2SpecificDependencies.class) public class Module2 { ... }
辅助注入
辅助注入是一种 DI 模式,用于构造一个对象,其中一些参数可能由 DI 框架提供,而其他参数必须在创建时由用户传入。
在 Android 中,这种模式在详情屏幕中很常见,其中要显示的元素的 ID 仅在运行时才知道,而不是在 Dagger 生成 DI 图的编译时才知道。要了解有关 Dagger 辅助注入的更多信息,请参阅Dagger 文档。
结论
如果您还没有,请查看最佳实践部分。要了解如何在 Android 应用中使用 Dagger,请参阅在 Android 应用中使用 Dagger 的 codelab。