包含多个 Gradle 模块的项目称为多模块项目。在作为单个 APK 发布且不包含功能模块的多模块项目中,通常具有一个可依赖于项目中大多数模块的 app
模块和一个其他模块通常可依赖的 base
或 core
模块。app
模块通常包含 Application
类,而 base
模块包含在项目中所有模块之间共享的所有通用类。
app
模块非常适合用来声明应用组件(例如,下图中的 ApplicationComponent
),应用组件可以提供其他组件可能需要的对象以及应用的单例。例如,像 OkHttpClient
这样的类、JSON 解析器、数据库的访问函数或可能在 core
模块中定义的 SharedPreferences
对象,都将由 app
模块中定义的 ApplicationComponent
提供。
在 app
模块中,还可以包含一些生命周期较短的其他组件。例如,在用户登录后,该模块中可能会包含具有用户专属配置的 UserComponent
(比如 UserSession
)。
在项目的不同模块中,您可以至少定义一个具有该模块的专属逻辑的子组件,如图 1 所示。
例如,在 login
模块中,您可以定义采用自定义 @ModuleScope
注解来限定作用域的 LoginComponent
组件,该组件可提供相应功能(例如 LoginRepository
)的常用对象。在该模块内,您还可以定义依赖于具有其他自定义作用域的 LoginComponent
的其他组件,例如将 @FeatureScope
用于 LoginActivityComponent
或 TermsAndConditionsComponent
,您可以在这两个组件中定义更加特定于功能的逻辑,例如 ViewModel
对象。
对于其他模块(例如 Registration
),您需要进行类似的设置。
一般而言,多模块项目的同级模块不应相互依赖。如果它们相互依赖,请考虑相应共享逻辑(它们之间的依赖关系)是否应该是父级模块的一部分。如果应该,请进行重构以将这些类移到父级模块;如果不应该,请创建一个可扩展父级模块的新模块,并使两个原始模块都扩展新模块。
最佳实践:在以下情况下,您通常需要在模块中创建一个组件:
对于
LoginActivityComponent
,您需要执行字段注入。对于
LoginComponent
,您需要限定对象的作用域。
如果情况并不是以上任何一种,并且您需要告知 Dagger 如何从相应模块提供对象,请使用 @Provides
或 @Binds
方法创建和提供 Dagger 模块,但前提是这些类不支持构造函数注入。
使用 Dagger 子组件实现
在 Android 应用中使用 Dagger 文档页面介绍了如何创建和使用子组件。但是,由于功能模块无法识别 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
。为了使其正常运行,您需要在功能模块中定义一个接口,该接口提供 MyApplication
需要实现的 FeatureComponent
。
在以下示例中,您可以定义一个 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 子组件的方法。项目中包含功能模块时,解决方案因模块之间相互依赖的方式而异。
包含功能模块时的组件依赖关系
包含功能模块时,模块之间相互依赖的方式通常是相反的。不是 app
模块包含功能模块,而是功能模块依赖于 app
模块。如需了解这些模块之间的依赖关系,请参见图 2。
在 Dagger 中,组件需要能够识别各自的子组件。此类信息包含在添加到父级组件的 Dagger 模块中(例如,在 Android 应用中使用 Dagger 中所述的 SubcomponentsModule
模块)。
遗憾的是,应用和功能模块之间依赖关系的反转使得子组件不会在 app
模块中显示,因为它不在构建路径中。例如,login
功能模块中定义的 LoginComponent
不能是 app
模块中定义的 ApplicationComponent
的子组件。
您可以使用 Dagger 的组件依赖关系机制来解决此问题。在此机制中,子级组件不是父级组件的子组件,而是依赖于父级组件。因此,这里不存在父级/子级关系;组件现在会依赖于其他内容来获取特定的依赖项。组件需要从图表中提供类型,以供依赖组件使用。
例如,名为 login
的功能模块需要构建一个 LoginComponent
,该组件依赖于 app
Gradle 模块中提供的 AppComponent
。
以下是作为 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 模块中,有一个需要注入 LoginViewModel
实例的 LoginActivity
:
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
依赖于可用且作用域限定为 AppComponent
的 UserRepository
。我们创建一个依赖于 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
添加到组件注解的 dependencies 参数中来指定对 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
访问 UserRepository,AppComponent
需要在其接口中提供 UserRepository。
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
所提供的依赖项。