在多模块应用中使用 Dagger

包含多个 Gradle 模块的项目称为多模块项目。在作为单个 APK 发运且没有功能模块的多模块项目中,通常会有一个app模块,它可以依赖项目的大多数模块,以及一个basecore模块,其他模块通常依赖于它。app模块通常包含您的Application类,而base模块包含项目中所有模块共享的所有通用类。

app模块是声明应用程序组件(例如,下图中的ApplicationComponent)的好地方,该组件可以提供其他组件可能需要的对象以及应用的单例。例如,OkHttpClient、JSON 解析器、数据库访问器或可能在core模块中定义的SharedPreferences对象等类将由在app模块中定义的ApplicationComponent提供。

app模块中,您还可以拥有其他生命周期较短的组件。例如,在登录后,可以有一个具有用户特定配置(例如UserSession)的UserComponent

在项目的不同模块中,您可以定义至少一个子组件,该子组件具有特定于该模块的逻辑,如图 1 所示。

图 1. 多模块项目中 Dagger 图的示例

例如,在login模块中,您可以使用自定义@ModuleScope注释对LoginComponent进行作用域限定,该注释可以提供特定于该功能的对象,例如LoginRepository。在该模块中,您还可以拥有其他依赖于LoginComponent且具有不同自定义作用域的组件,例如,@FeatureScope用于LoginActivityComponentTermsAndConditionsComponent,您可以在其中对更多特定于功能的逻辑(例如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。要使其正常工作,您需要在特性模块中定义一个接口,该接口提供一个 FeatureComponentMyApplication 需要实现该接口。

在以下示例中,您可以定义一个 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。

图 2. 包含特性模块的项目中的 Dagger 图示例

在 Dagger 中,组件需要了解其子组件。此信息包含在添加到父组件的 Dagger 模块中(例如“[在 Android 应用中使用 Dagger](/training/dependency-injection/dagger-android#dagger-subcomponents)” 中的 SubcomponentsModule 模块)。

不幸的是,由于应用和特性模块之间的反向依赖关系,子组件在 app 模块中不可见,因为它不在构建路径中。例如,在 login 特性模块中定义的 LoginComponent 不能是 app 模块中定义的 ApplicationComponent 的子组件。

Dagger 具有称为“**组件依赖项**”的机制,您可以使用该机制来解决此问题。子组件不是父组件的子组件,而是依赖于父组件。这样一来,就没有父子关系;现在**组件**依赖于其他组件来获取某些**依赖项**。组件需要公开图中的类型,以便依赖组件使用它们。

例如:名为 login 的特性模块希望构建一个依赖于 app Gradle 模块中提供的 AppComponentLoginComponent

以下是属于 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 以注入 LoginActivityLoginComponent

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 提供的依赖项。