在Android应用中使用Dagger

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 中的依赖关系图。

LoginActivity depends on LoginViewModel, which depends on UserRepository,
  which depends on UserLocalDataSource and UserRemoteDataSource, which in turn
  depends on Retrofit.

图 1.示例代码的依赖关系图

在 Android 中,通常会创建一个位于应用程序类中的 Dagger 图,因为您希望在应用运行期间始终在内存中存在该图的实例。通过这种方式,该图将附加到应用生命周期。在某些情况下,您可能还希望在图中使用应用程序上下文。为此,您还需要将图放在Application类中。这种方法的一个优点是该图可用于其他 Android 框架类。此外,它通过允许您在测试中使用自定义Application类来简化测试。

由于生成图的接口使用@Component进行注释,因此您可以将其称为ApplicationComponentApplicationGraph。通常,您会在自定义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)。如果您有多个请求注入的类,则必须在组件中使用其确切类型声明所有这些类。例如,如果您有 LoginActivityRegistrationActivity 请求注入,则您将有两个 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,或者如何配置 GsonMoshi

@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 图的当前外观

Diagram of LoginActivity dependency graph

图 2. Dagger 注入 LoginActivity 的图表示

图的入口点是 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 以重用实例,原因如下:

  1. 流程结束后,LoginViewModel 的实例将保留在内存中。

  2. 您希望每个登录流程都有一个不同的 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 LoginComponentApplicationComponent的子组件,需要:

  1. 创建一个新的 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 {
    }
  2. 将新模块(即 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的实例。父组件必须在其接口中添加一个方法,以允许使用者根据父组件的实例创建子组件的实例。

  3. 在接口中公开创建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();
    }

为子组件分配作用域

如果构建项目,可以创建ApplicationComponentLoginComponent的实例。ApplicationComponent附加到应用程序的生命周期,因为希望只要应用程序在内存中就使用相同的图实例。

LoginComponent的生命周期是什么?需要LoginComponent的原因之一是需要在与登录相关的片段之间共享LoginViewModel的相同实例。但是,也希望每当有新的登录流程时都拥有LoginViewModel的不同实例。LoginActivityLoginComponent的正确生命周期:对于每个新的活动,都需要一个新的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的相同实例。可以通过创建一个自定义注解作用域并使用它来注解LoginComponentLoginViewModel来确保这一点。请注意,不能使用@Singleton注解,因为它已经被父组件使用,这会使对象成为应用程序单例(整个应用程序的唯一实例)。需要创建一个不同的注解作用域。

在这种情况下,可以将此作用域称为@LoginScope,但这并不是一个好习惯。作用域注解的名称不应明确说明其用途。相反,应该根据其生命周期命名,因为注解可以被兄弟组件(例如RegistrationComponentSettingsComponent)重用。这就是为什么应该将其称为@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 图表的外观。带有白点的类(UserRepositoryLoginRetrofitServiceLoginViewModel)是具有作用域限定于其各自组件的唯一实例的类。

Application graph after adding the last subcomponent

图 3. Android 应用示例构建的图的表示

让我们分解图表的各个部分

  1. NetworkModule(因此还有LoginRetrofitService)包含在ApplicationComponent中,因为已在组件中指定了它。

  2. UserRepository保留在ApplicationComponent中,因为它作用域限定于ApplicationComponent。如果项目发展壮大,则希望跨不同的功能(例如注册)共享相同的实例。

    因为UserRepositoryApplicationComponent的一部分,所以它的依赖项(即UserLocalDataSourceUserRemoteDataSource)也需要在此组件中,以便能够提供UserRepository的实例。

  3. 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而不是ApplicationComponentTestApplication

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 包含 Module1Module2,而 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. 重构模块并将公共模块提取到组件中。
  2. 创建一个包含两个模块共享的对象的新模块,并将其提取到组件中。

不进行这种重构会导致许多模块相互包含,缺乏清晰的组织结构,并且难以查看每个依赖项的来源。

最佳实践(选项 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):将 Module1Module2ModuleX 中的公共依赖项提取到一个名为 ModuleXCommon 的新模块中,该模块包含在组件中。然后创建另外两个模块,名为 ModuleXWithModule1DependenciesModuleXWithModule2Dependencies,分别包含特定于每个模块的依赖项。所有模块都在 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