包含多个 Gradle 模块的项目称为多模块项目。在作为单个 APK 发运且没有功能模块的多模块项目中,通常会有一个app
模块,它可以依赖项目的大多数模块,以及一个base
或core
模块,其他模块通常依赖于它。app
模块通常包含您的Application
类,而base
模块包含项目中所有模块共享的所有通用类。
app
模块是声明应用程序组件(例如,下图中的ApplicationComponent
)的好地方,该组件可以提供其他组件可能需要的对象以及应用的单例。例如,OkHttpClient
、JSON 解析器、数据库访问器或可能在core
模块中定义的SharedPreferences
对象等类将由在app
模块中定义的ApplicationComponent
提供。
在app
模块中,您还可以拥有其他生命周期较短的组件。例如,在登录后,可以有一个具有用户特定配置(例如UserSession
)的UserComponent
。
在项目的不同模块中,您可以定义至少一个子组件,该子组件具有特定于该模块的逻辑,如图 1 所示。
例如,在login
模块中,您可以使用自定义@ModuleScope
注释对LoginComponent
进行作用域限定,该注释可以提供特定于该功能的对象,例如LoginRepository
。在该模块中,您还可以拥有其他依赖于LoginComponent
且具有不同自定义作用域的组件,例如,@FeatureScope
用于LoginActivityComponent
或TermsAndConditionsComponent
,您可以在其中对更多特定于功能的逻辑(例如ViewModel
对象)进行作用域限定。
对于其他模块(例如Registration
),您将拥有类似的设置。
多模块项目的一般规则是,同一级别的模块不应该相互依赖。如果它们确实相互依赖,请考虑共享逻辑(它们之间的依赖项)是否应属于父模块的一部分。如果是,请重构以将类移动到父模块;如果不是,请创建一个扩展父模块的新模块,并让这两个原始模块都扩展新模块。
最佳实践是在以下情况下在模块中创建组件
您需要执行字段注入,例如
LoginActivityComponent
。您需要对对象进行作用域限定,例如
LoginComponent
。
如果以上两种情况都不适用,并且您需要告诉 Dagger 如何从该模块提供对象,请创建一个并公开一个 Dagger 模块,其中包含 @Provides
或 @Binds
方法(如果无法对这些类进行构造函数注入)。
使用 Dagger 子组件的实现
“[在 Android 应用中使用 Dagger](/training/dependency-injection/dagger-android#dagger-subcomponents)” 文档页面介绍了如何创建和使用子组件。但是,您不能使用相同的代码,因为特性模块不知道 app
模块。例如,如果您考虑典型的登录流程以及我们在上一页中编写的代码,它将不再编译
Kotlin
class LoginActivity: Activity() { ... 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) ... } }
Java
public class LoginActivity extends Activity { ... @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); ... } }
原因是 login
模块不知道 MyApplication
或者 appComponent
。要使其正常工作,您需要在特性模块中定义一个接口,该接口提供一个 FeatureComponent
,MyApplication
需要实现该接口。
在以下示例中,您可以定义一个 LoginComponentProvider
接口,该接口在 login
模块中为登录流程提供 LoginComponent
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
现在,LoginActivity
将使用该接口,而不是上面定义的代码片段
Kotlin
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { loginComponent = (applicationContext as LoginComponentProvider) .provideLoginComponent() loginComponent.inject(this) ... } }
Java
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { loginComponent = ((LoginComponentProvider) getApplicationContext()) .provideLoginComponent(); loginComponent.inject(this); ... } }
现在,MyApplication
需要实现该接口并实现必需的方法
Kotlin
class MyApplication: Application(), LoginComponentProvider { // Reference to the application graph that is used across the whole app val appComponent = DaggerApplicationComponent.create() override fun provideLoginComponent(): LoginComponent { return appComponent.loginComponent().create() } }
Java
public class MyApplication extends Application implements LoginComponentProvider { // Reference to the application graph that is used across the whole app ApplicationComponent appComponent = DaggerApplicationComponent.create(); @Override public LoginComponent provideLoginComponent() { return appComponent.loginComponent.create(); } }
这就是在多模块项目中使用 Dagger 子组件的方法。对于特性模块,由于模块之间相互依赖的方式不同,因此解决方案也不同。
特性模块的组件依赖项
使用“[特性模块](/studio/projects/dynamic-delivery#customize_delivery)”,模块之间通常依赖的方式是反向的。特性模块依赖于 app
模块,而不是 app
模块包含特性模块。有关模块结构的表示,请参见图 2。
在 Dagger 中,组件需要了解其子组件。此信息包含在添加到父组件的 Dagger 模块中(例如“[在 Android 应用中使用 Dagger](/training/dependency-injection/dagger-android#dagger-subcomponents)” 中的 SubcomponentsModule
模块)。
不幸的是,由于应用和特性模块之间的反向依赖关系,子组件在 app
模块中不可见,因为它不在构建路径中。例如,在 login
特性模块中定义的 LoginComponent
不能是 app
模块中定义的 ApplicationComponent
的子组件。
Dagger 具有称为“**组件依赖项**”的机制,您可以使用该机制来解决此问题。子组件不是父组件的子组件,而是依赖于父组件。这样一来,就没有父子关系;现在**组件**依赖于其他组件来获取某些**依赖项**。组件需要公开图中的类型,以便依赖组件使用它们。
例如:名为 login
的特性模块希望构建一个依赖于 app
Gradle 模块中提供的 AppComponent
的 LoginComponent
。
以下是属于 app
Gradle 模块的类和 AppComponent
的定义
Kotlin
// UserRepository's dependencies class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... } // UserRepository is scoped to AppComponent @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } @Singleton @Component interface AppComponent { ... }
Java
// UserRepository's dependencies public class UserLocalDataSource { @Inject public UserLocalDataSource() {} } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } } // UserRepository is scoped to AppComponent @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; } } @Singleton @Component public interface ApplicationComponent { ... }
在包含 app
Gradle 模块的 login
Gradle 模块中,您有一个 LoginActivity
,它需要注入 LoginViewModel
实例
Kotlin
// LoginViewModel depends on UserRepository that is scoped to AppComponent class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
// LoginViewModel depends on UserRepository that is scoped to AppComponent public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
LoginViewModel
依赖于 UserRepository
,该依赖项在 AppComponent
中可用并限定了作用域。让我们创建一个依赖于 AppComponent
以注入 LoginActivity
的 LoginComponent
Kotlin
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = [AppComponent::class]) interface LoginComponent { fun inject(activity: LoginActivity) }
Java
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = AppComponent.class) public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
通过将其添加到组件注释的依赖项参数中,指定对 AppComponent
的依赖关系。由于 LoginActivity
将由 Dagger 注入,因此将 inject()
方法添加到接口中。
创建 LoginComponent
时,需要传入 AppComponent
的实例。使用组件工厂来执行此操作
Kotlin
@Component(dependencies = [AppComponent::class]) interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent fun create(appComponent: AppComponent): LoginComponent } fun inject(activity: LoginActivity) }
Java
@Component(dependencies = AppComponent.class) public interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent LoginComponent create(AppComponent appComponent); } void inject(LoginActivity loginActivity); }
现在,LoginActivity
可以创建 LoginComponent
的实例并调用 inject()
方法。
Kotlin
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Gets appComponent from MyApplication available in the base Gradle module val appComponent = (applicationContext as MyApplication).appComponent // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this) super.onCreate(savedInstanceState) // Now you can access loginViewModel } }
Java
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Gets appComponent from MyApplication available in the base Gradle module AppComponent appComponent = ((MyApplication) getApplicationContext()).appComponent; // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this); // Now you can access loginViewModel } }
LoginViewModel
依赖于 UserRepository
;并且为了使 LoginComponent
能够从 AppComponent
访问它,AppComponent
需要在其实现接口中公开它
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
依赖组件的作用域规则与子组件的作用域规则相同。由于 LoginComponent
使用 AppComponent
的实例,因此它们不能使用相同的作用域注释。
如果要将 LoginViewModel
的作用域限定为 LoginComponent
,则可以像以前一样使用自定义 @ActivityScope
注释来执行此操作。
Kotlin
@ActivityScope @Component(dependencies = [AppComponent::class]) interface LoginComponent { ... } @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
@ActivityScope @Component(dependencies = AppComponent.class) public interface LoginComponent { ... } @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
最佳实践
ApplicationComponent
应始终位于app
模块中。如果需要在该模块中执行字段注入或需要为应用程序的特定流程对对象进行作用域限定,请在模块中创建 Dagger 组件。
对于旨在用作实用程序或帮助程序且不需要构建图(因此您需要 Dagger 组件)的 Gradle 模块,请创建并公开包含这些类的 @Provides 和 @Binds 方法的公共 Dagger 模块,这些类不支持构造函数注入。
要在使用特性模块的 Android 应用中使用 Dagger,请使用组件依赖项来访问
app
模块中定义的ApplicationComponent
提供的依赖项。