Dagger2 的使用与基本原理

严格来讲,Dagger2 并不是 Jetpack 中的一员,学习 Dagger2 的使用方法和简单原理是为了更好的掌握基于 Dagger2 封装的 Hilt。

1、理论知识

1.1 依赖注入

既然我们说了 Dagger2 是一个依赖注入框架,那么还是需要了解下,什么是依赖注入,为什么要用依赖注入。

类通常需要引用其他类。例如,Car 类可能需要引用 Engine 类,像 Engine 这种被依赖的类称为依赖项。一个类可以通过三种方式获取依赖项的对象:

  1. 类构造其所需的依赖项。例如在 Car 类中创建并初始化自己所需的 Engine 实例
  2. 从其他地方抓取。如 Android Context 中的 getSystemService()
  3. 以参数形式提供。应用可以在构造一个类时提供这些依赖项,或者将这些依赖项传入需要依赖项的函数,如在创建 Car 对象时将 Engine 对象通过 Car 的构造方法或 setter 方法传入

第三种方式就是依赖注入(DI,Dependency Injection)。使用依赖注入时,一个类不必再自己获取依赖项,而是由外部提供。比如说:

/**
* 代码示例1:
* 在 Car 类内部,由 Car 自己创建依赖项 Engine 的实例
*/
class Car {

    private Engine engine = new Engine();

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}

按照上述代码,Car 对 Engine 是一个强依赖关系,不利于扩展和测试,在使用依赖注入的方式后:

/**
* 示例代码2:
* 使用依赖注入方式创建对象
*/
class Car {

    private final Engine engine;

	// 依赖注入方式一:构造方法注入
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }

	// 依赖注入方式二:字段(setter方法)注入
	public void setEngine(Engine engine) {
        this.engine = engine;
    }
}

class MyApp {
    public static void main(String[] args) {
    	// 依赖注入框架实际上干的就是创建对象并交给需要它的类这件事
        Engine engine = new Engine();
        Car car = new Car(engine);
        car.start();
    }
}

实现了控制反转(IoC,Inversion of Control),将原本的正向控制,即在 Car 类中主动去获取/创建所需要的外部资源 Engine,改成了被动等待外部获取到一个 Engine 的实例然后注入到 Car 中。这样做的好处有:

  • 重用类以及分离依赖项:更容易换掉依赖项的实现。由于控制反转,代码重用得以改进,并且类不再控制其依赖项的创建方式,而是支持任何配置
  • 易于重构:依赖项在创建对象时或编译时可以进行检查,而不是作为实现详情隐藏
  • 易于测试:类不管理其依赖项,因此在测试时,可以传入不同的实现以测试不同用例

1.2 初识 Dagger2

Dagger2 可以使得项目的修改与重构更方便,Google App Store 中 Top 10000 的应用中,有 74% 使用了 Dagger:

Why Dagger

Dagger2 在编译时会自动生成代码,这些生成的代码与原本需要手动编写的注入代码类似,这样就无需我们再手动编写冗长乏味又容易出错的异步代码了。它会完成以下工作:

  • 构建并验证依赖关系图,确保每个对象的依赖关系都能满足且图中不存在依赖循环,简言之就是根据依赖关系生成有向无环图
  • 为有向无环图中的类创建工厂以满足依赖关系,并通过该工厂生产实际对象
  • 通过作用域管理对象的生命周期,并且决定是重复使用依赖项还是创建新的实例
  • 为特定流程创建容器,这样在对象的生命周期结束后,可以及时释放内存中不再需要的对象,提升性能

在 Dagger2 中有很多注解,它们扮演了非常重要的角色,在介绍它们的用法之前,先来认识一下它们:

注解描述
@Inject指示 Dagger 如何实例化一个对象,可以作用在构造方法或需要注入的字段上
@Module + @Provides指示 Dagger 以非构造方法的方式实例化一个对象
@Singleton / @Scope作用域,用来标记依赖项的生命周期(作用域),还有一个作用是标记单例
@Component组件,创建一个 Dagger 容器,作为获取依赖项的入口
@Subcomponent子组件,用来定义更加细致的作用域

下面就开始逐一介绍上述注解的使用方法,在每一节介绍完使用方法后,我们会稍微看一下 Dagger2 通过 APT 生成的代码,了解一下内部实现以便更好的使用和理解 Dagger2 框架,如果你觉得篇幅过长阅读体验不佳,可以略过实现细节部分,只看每一小节前面的使用方法即可。

2、基础使用

添加依赖:

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}

2.1 @Inject + @Component

@Inject 的作用是告诉 Dagger2 框架如何创建一个对象(其实是提供一个创建某种类型对象的方法),@Component 则会创建一个容器,Dagger2 会自动为 @Component 修饰的接口创建一个实现类,作为获取注入对象的入口。@Inject 与 @Component 配合即可实现最简单的注入,有两种注入方式,分别是构造方法注入和字段注入。

2.1.1 构造方法注入

使用方法

假设现有依赖关系如下图:

依赖关系
UserRepository 对象的创建依赖于 UserLocalDataSource 和 UserRemoteDataSource:

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }

    ...
}

需要对有向无环图中的所有类的构造方法都添加 @Inject 注解以生成对应的工厂类。首先是 UserRepository 的构造方法:

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

这样 Dagger2 就知道了两件事:

  1. 如何使用被 @Inject 注解的构造方法创建 UserRepository 对象
  2. UserRepository 的依赖项为 UserLocalDataSource 和 UserRemoteDataSource

此时 Dagger2 还不知道如何创建 UserRepository 的两个依赖项,所以类似的也使用 @Inject 标记依赖项的构造方法:

public class UserLocalDataSource {
    @Inject
    public UserLocalDataSource() { }
}
--------------------------------------------
public class UserRemoteDataSource {
    @Inject
    public UserRemoteDataSource() { }
}

仅添加 @Inject 还不能完成注入,还需要用 @Component 注解修饰一个接口,并在接口内定义获取指定对象(即 UserRepository)的方法。这样 @Component 会让 Dagger2 生成一个容器,容器中包含满足所提供类型所需要的所有依赖项,这就是Dagger 组件,组件中包含一个有向无环图,告知如何提供一个对象及其依赖项:

@Component
public interface ApplicationComponent {
	UserRepository userRepository();
}

此时编译项目让 APT 生成代码,会生成 ApplicationComponent 的实现类 DaggerApplicationComponent,我们通过实现类的 create() 拿到实例再调用接口方法 userRepository() 即可拿到 UserRepository 对象:

ApplicationComponent component = DaggerApplicationComponent.create();
UserRepository userRepository1 = component.userRepository();
实现细节

我们来看一下 Dagger2 使用 APT 生成的代码是什么样子的。首先代码生成的路径是:build/generated/ap_generated_sources/[release/debug]/out/[package name](使用的 Dagger2 版本不同,或者 AGP 版本不同可能会让路径也有细微差异),生成的文件有 4 个,除了 @Component 的接口实现类 DaggerApplicationComponent 之外,其余 3 个都是被 @Inject 标记了构造方法的类对应的工厂类,我们先来看 UserRepository 的工厂类:

public final class UserRepository_Factory implements Factory<UserRepository> {
  // 持有两个依赖项的 Provider
  private final Provider<UserLocalDataSource> userLocalDataSourceProvider;
  private final Provider<UserRemoteDataSource> userRemoteDataSourceProvider;

  public UserRepository_Factory(Provider<UserLocalDataSource> userLocalDataSourceProvider,
      Provider<UserRemoteDataSource> userRemoteDataSourceProvider) {
    this.userLocalDataSourceProvider = userLocalDataSourceProvider;
    this.userRemoteDataSourceProvider = userRemoteDataSourceProvider;
  }

  // 通过构造方法生成 UserRepository 对象,依赖项通过对应的 Provider 的 get() 获取
  @Override
  public UserRepository get() {
    return new UserRepository(userLocalDataSourceProvider.get(), userRemoteDataSourceProvider.get());
  }

  public static UserRepository_Factory create(
      Provider<UserLocalDataSource> userLocalDataSourceProvider,
      Provider<UserRemoteDataSource> userRemoteDataSourceProvider) {
    return new UserRepository_Factory(userLocalDataSourceProvider, userRemoteDataSourceProvider);
  }

  public static UserRepository newInstance(UserLocalDataSource userLocalDataSource,
      UserRemoteDataSource userRemoteDataSource) {
    // 通过构造方法生成 UserRepository 对象,依赖项通过方法参数获取
    return new UserRepository(userLocalDataSource, userRemoteDataSource);
  }
}

可以看到工厂类实现了 Factory 接口,且持有依赖项的 Provider 对象,这里 Factory 其实是继承自 Provider 的:

public interface Factory<T> extends Provider<T> {
}
---------------------------------------------------
public interface Provider<T> {
    T get();
}

而 get() 实际上就是获取指定类的对象并向外提供,获取方式正是 @Inject 标记的方法创建对象的方式。至于两个依赖项的工厂,也是大同小异,仅贴出一个:

public final class UserLocalDataSource_Factory implements Factory<UserLocalDataSource> {
  private static final UserLocalDataSource_Factory INSTANCE = new UserLocalDataSource_Factory();

  // 生产 UserLocalDataSource 对象的方法,生产方式是 @Inject 指定的,通过
  // UserLocalDataSource 的构造方法
  @Override
  public UserLocalDataSource get() {
    return new UserLocalDataSource();
  }

  public static UserLocalDataSource_Factory create() {
    return INSTANCE;
  }

  public static UserLocalDataSource newInstance() {
    return new UserLocalDataSource();
  }
}

总结一下 @Inject 注解生成的工厂类:

  1. 内部持有依赖项的 Provider 对象来维护依赖关系
  2. 通过 get() 获取指定类的对象后向外提供该对象,获取对象的方式是由 @Inject 标记的方法决定的

再来看由 @Component 生成的接口实现类:

public final class DaggerApplicationComponent implements ApplicationComponent {
  private DaggerApplicationComponent() {

  }

  public static Builder builder() {
    return new Builder();
  }

  // 调用构建者的 build() 得到 ApplicationComponent 的实现类对象
  public static ApplicationComponent create() {
    return new Builder().build();
  }

  // 接口内方法的实现:调用 UserRepository 的构造方法返回一个实例
  @Override
  public UserRepository userRepository() {
    return new UserRepository(new UserLocalDataSource(), new UserRemoteDataSource());}

  public static final class Builder {
    private Builder() {
    }

    public ApplicationComponent build() {
      return new DaggerApplicationComponent();
    }
  }
}

2.1.2 字段注入

有时,构造方法注入并不能满足所有的使用场景。比如说,对于 Android 系统中的 Activity 而言,它们的实例化都是由系统完成的,其余角色无法通过调用构造方法创建一个 Activity 实例,也更不可能在构造方法上加 @Inject 注解指定如何实例化及其依赖项了:

/**
* LoginActivity 依赖 LoginViewModel,但是你不能用构造方法注入,因为
* 只有 Android 系统才能调用 Activity 的构造方法去实例化一个 Activity
*/
public class LoginActivity extends Activity {

	LoginViewModel loginViewModel;

    @Inject
    public LoginActivity(LoginViewModel loginViewModel) {
    	this.loginViewModel = loginViewModel;
    }
}

此时,无法通过构造方法注入,所以需要 @Inject 的第二种使用方式 —— 字段注入。

使用方法

首先,还是要用 @Inject 修饰 UserRepository、UserLocalDataSource、UserRemoteDataSource 的构造方法,这一点是没有变的,需要变化的是 @Component 修饰的接口:

@Component
public interface ApplicationComponent {
//        UserRepository userRepository();
	/**
    * 告诉 Dagger,MainActivity 请求注入,所以依赖图必须满足所有 MainActivity 正在注入的字段的依赖关系。
    * 参数不能使用多态,只能传入被注入的那个类,比如这里就不能传 Activity,因为 Dagger 不知道要提供的具体内容。
    */
    void injectActivity(MainActivity mainActivity);
}

最终获取对象的方式也有所改变:

public class MainActivity extends AppCompatActivity {

	// 用 @Inject 标记被注入的字段,表示需要 Dagger2 从依赖图中提供一个 LoginViewModel 的实例
    @Inject
    UserRepository userRepository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	// 调用接口内定义的注入方法
        DaggerApplicationComponent.create().injectActivity(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

需要注意的使用细节:

  1. 使用 Activity 时,应该在 onCreate() 的 super.onCreate() 之前注入 Dagger,因为在 super.onCreate() 的恢复阶段,Activity 会附加可能需要访问 Activity 绑定的 Fragment
  2. 使用 Fragment 时,应在 onAttach() 中注入 Dagger,在 super.onAttach() 的前后都可
实现细节

字段注入修改了接口内方法的定义方式,所以实现类也跟随发生了变化:

public final class DaggerApplicationComponent implements ApplicationComponent {

  ...

  private UserRepository getUserRepository() {
    return new UserRepository(new UserLocalDataSource(), new UserRemoteDataSource());}

  // 实现接口方法
  @Override
  public void injectActivity(MainActivity mainActivity) {
    injectMainActivity(mainActivity);}

  private MainActivity injectMainActivity(MainActivity instance) {
    // 将需要注入的 Activity 和对象交给 MainActivity_MembersInjector
    MainActivity_MembersInjector.injectUserRepository(instance, getUserRepository());
    return instance;
  }
}

我们看到执行字段注入的工作实际上是由 MainActivity_MembersInjector 来完成的:

public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
  // 持有所需类的 Provider 对象
  private final Provider<UserRepository> userRepositoryProvider;

  public MainActivity_MembersInjector(Provider<UserRepository> userRepositoryProvider) {
    this.userRepositoryProvider = userRepositoryProvider;
  }

  public static MembersInjector<MainActivity> create(
      Provider<UserRepository> userRepositoryProvider) {
    return new MainActivity_MembersInjector(userRepositoryProvider);}

  @Override
  public void injectMembers(MainActivity instance) {
    injectUserRepository(instance, userRepositoryProvider.get());
  }

  // 将 userRepository 赋值给 MainActivity 中的 userRepository 字段(不能是 private 的)
  public static void injectUserRepository(MainActivity instance, UserRepository userRepository) {
    instance.userRepository = userRepository;
  }
}

整个流程也很简单:

  1. 定义接口方法时将需要注入的 Activity 填写到方法参数上
  2. 接口实现类能根据依赖关系生成一个带注入类的对象,并将其与待注入的 Activity 一起传给 MembersInjector
  3. MembersInjector 将对象赋值给 Activity 中对应字段

2.2 @Module + @Provides

即便 @Inject 提供了两种注入方式,但仍不能满足我们所有的使用场景,比如你的依赖项中包含一个第三方框架提供的对象,你无法修改该框架的代码,去该对象所在类的构造方法上加一个 @Inject,或者该对象根本就不是通过构造方法创建对象的,这时候 @Module 就派上用场了。

2.2.1 使用方法

@Module 用来修饰一个类,这个类就是一个 Dagger 模块,模块内可以定义多个被 @Provides 修饰的返回值类型为 T 的方法,用来告诉 Dagger 如何创建一个 T 类型的对象。比如说我们在 NetworkModule 中定义一个通过 Retrofit 创建 LoginRetrofitService 对象的方法:

@Module
public class NetworkModule {

	// @Provides 告诉 Dagger2 如何提供项目中所不具备的类(第三方库中的类)对象,
	// 即创建一个该方法返回值类型的实例,方法参数是该类型的依赖
    @Provides
    public LoginService provideLoginService(OkHttpClient okHttpClient) {
    	// 当 Dagger 需要提供 LoginService 类型的实例时,就会运行到 @Provides 方法内的代码
        return new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl("https://example.com")
                .build()
                .create(LoginService.class);
    }

	// provideLoginService() 依赖 OkHttpClient,需要提供一下创建方法
	@Provides
    public OkHttpClient provideOkHttpClient() {
        return new OkHttpClient.Builder()
                .build();
    }
}

为了将模块中的依赖关系添加到 Dagger2 的有向无环图中,需要将其添加到 @Component 中:

// modules 可以指定多个
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    void injectActivity(MainActivity mainActivity);
}

最后还是用字段注入方式注入 LoginService:

public class MainActivity extends AppCompatActivity {

    @Inject
    LoginService loginService;
	
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        DaggerApplicationComponent.create().injectActivity(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

2.2.2 实现细节

使用了 @Module + @Provides 组合后,Dagger2 会把 Module 中被 @Provides 修饰的方法的返回值类型添加到描述依赖关系的有向无环图中,方式与 @Inject 一样,都是生成该类型对应的 Factory 实现类。对于本例来说,NetworkModule 中定义的两个 @Provides 方法分别返回 LoginService、OkHttpClient 类型,所以 Dagger2 会为它们生成两个 Factory 实现类,下面我们只看有依赖项的 NetworkModule_ProvideLoginServiceFactory:

public final class NetworkModule_ProvideLoginServiceFactory implements Factory<LoginService> {
  // 持有所在的 Module 对象
  private final NetworkModule module;
  // 持有依赖项的 Provider
  private final Provider<OkHttpClient> okHttpClientProvider;

  public NetworkModule_ProvideLoginServiceFactory(NetworkModule module,
      Provider<OkHttpClient> okHttpClientProvider) {
    this.module = module;
    this.okHttpClientProvider = okHttpClientProvider;
  }

  // 调用依赖项 Provider 的 get() 获取依赖项对象,然后 module 中定义的获取
  // LoginService 对象的 provideLoginService() 方法
  @Override
  public LoginService get() {
    return provideLoginService(module, okHttpClientProvider.get());
  }

  public static NetworkModule_ProvideLoginServiceFactory create(NetworkModule module,
      Provider<OkHttpClient> okHttpClientProvider) {
    return new NetworkModule_ProvideLoginServiceFactory(module, okHttpClientProvider);
  }

  public static LoginService provideLoginService(NetworkModule instance,
      OkHttpClient okHttpClient) {
    return Preconditions.checkNotNull(instance.provideLoginService(okHttpClient), "Cannot return null from a non-@Nullable @Provides method");
  }
}

看到这里其实更加印证了,所有处于有向无环图中的类都是通过对应的工厂类维持依赖关系的。

2.2.3 Module 传参

如果在使用 Module 创建对象时,需要外部传入该如何处理呢?最基础的方法是将参数定义成 Module 的成员变量并通过构造方法接收外部传值。比如说创建 OkHttpClient 对象时,由外部传入读取数据的超时时间:

@Module
public class NetworkModule {

    private int readTime;
    private TimeUnit timeUnit;

    public NetworkModule(int readTime, TimeUnit timeUnit) {
        this.readTime = readTime;
        this.timeUnit = timeUnit;
    }

    @Singleton
    @Provides
    public OkHttpClient provideOkHttpClient() {
        return new OkHttpClient.Builder()
                .readTimeout(readTime, timeUnit)
                .build();
    }

	...
}

然后在用 Component 的接口实现类执行注入时,手动配置 NetworkModule 对象,传入所需参数:

	DaggerApplicationComponent.builder()
            .networkModule(new NetworkModule(10, TimeUnit.SECONDS))
            .build()
            .injectActivity(this);

这种方法虽然很容易理解,但是很 low。本来我们使用 Dagger2 就是为了解耦,不再 new 对象,你这现在又来个 new NetworkModule() 不是在开倒车么?可以使用 @BindInstance 注解来解决这个问题。

首先配置 Module,将需要外部传入的参数直接放在方法上:

@Module
public class NetworkModule {

    @Singleton
    @Provides
    public OkHttpClient provideOkHttpClient(long readTime, TimeUnit timeUnit) {
        return new OkHttpClient.Builder()
                .readTimeout(readTime, timeUnit)
                .build();
    }
    
    ...
}

然后修改 Component,自定义 Component.Builder:

@Singleton
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    void injectActivity(MainActivity mainActivity);

    void injectActivity(SecondActivity secondActivity);

	// 自己定义 Component 内 Builder 的实现,而不用默认实现
    @Component.Builder
    interface Builder {
    	@BindsInstance
        Builder initReadTime(long readTime, TimeUnit timeUnit);
        
		@BindsInstance
        Builder initReadTimeUnit(TimeUnit timeUnit);

        ApplicationComponent build();
    }
}

使用方式:

	DaggerApplicationComponent.builder()
            .initReadTime(10)
            .initReadTimeUnit(TimeUnit.SECONDS)
            .build()
            .injectActivity(this); // Activity

但是这种方法不能解决全部问题,因为 Dagger2 规定 @BindsInstance 修饰的方法最多只能有一个参数,且多个方法之间的参数类型不能重复。

2.3 @Singleton / @Scope

2.3.1 使用方法

当你想让 Dagger2 提供一个单例类时,可以使用 @Singleton 注解。具体的规则要分情况来看:

  • 当你使用 @Inject 提供对象时,@Singleton 需要修饰对象所在的类,还需要修饰 @Component 标记的接口
  • 如果是像 Retrofit 或 OkHttp 这种第三方框架的类,不能用 @Inject 注入而使用 @Module + @Provides 提供对象时,需要用 @Singleton 修饰 @Provides 标记的方法以及使用了该方法所在模块的 @Component 组件接口

先看第一种情况,在未使用单例时,测试代码如下:

public class MainActivity extends AppCompatActivity {

    @Inject
    UserRepository userRepository1;

    @Inject
    UserRepository userRepository2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        DaggerApplicationComponent.create().injectActivity(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("Test", "result:" + (userRepository1 == userRepository2));
    }
}

此时输出结果为 false 说明 userRepository1 和 userRepository2 不是同一个对象。然后我们按照前面给出的规则给 UserRepository 类和 ApplicationComponent 接口加上 @Singleton:

@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(modules = NetworkModule.class)
public interface ApplicationComponent {
    void injectActivity(MainActivity mainActivity);
}

再次运行 MainActivity 比较结果就为 true 了,也就是单例配置生效了。

下面再来看第二种情况,通过模块构造单例时,需要给提供对象的 @Provides 方法、使用了该方法所在的模块类的组件上都添加 @Singleton:

@Module
public class NetworkModule {

    @Singleton
    @Provides
    public LoginService provideLoginService(OkHttpClient okHttpClient) {
        // 当 Dagger 需要提供 LoginService 类型的实例时,就会运行到 @Provides 方法内的代码
        return new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl("https://example.com")
                .build()
                .create(LoginService.class);
    }

    @Singleton
    @Provides
    public OkHttpClient provideOkHttpClient() {
        return new OkHttpClient.Builder()
                .build();
    }
}
------------------------------------------------------------
@Singleton
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    void injectActivity(MainActivity mainActivity);
}

@Singleton 是 javax.inject 包中提供的唯一一个作用域注解,它本质上是一个 @Scope,用来描述对象的作用域。关于 @Scope 的具体介绍在 3.3 节中。

2.3.2 实现细节

上一小节给 UserRepository、LoginService 和 OkHttpClient 这三个类配置成单例之后,与未配置 @Singleton 时相比,各个类的 Factory 内部没有变化,变化发生在 @Component 接口的实现类上:

public final class DaggerApplicationComponent implements ApplicationComponent {

  // 原本只持有 ApplicationComponent 上配置的模块 NetworkModule 的引用,
  // 现在变成三个单例类的 Provider 对象了
  private Provider<UserRepository> userRepositoryProvider;
  private Provider<OkHttpClient> provideOkHttpClientProvider;
  private Provider<LoginService> provideLoginServiceProvider;
  
  // 在非单例模式下,构造方法是空的
  private DaggerApplicationComponent(NetworkModule networkModuleParam) {
    initialize(networkModuleParam);
  }
  
  // 在非单例模式下,没有 initialize()
  @SuppressWarnings("unchecked")
  private void initialize(final NetworkModule networkModuleParam) {
  	// DoubleCheck.provider() 包裹的其实就是各个类的工厂对象
    this.userRepositoryProvider = DoubleCheck.provider(UserRepository_Factory.create(UserLocalDataSource_Factory.create(), UserRemoteDataSource_Factory.create()));
    this.provideOkHttpClientProvider = DoubleCheck.provider(NetworkModule_ProvideOkHttpClientFactory.create(networkModuleParam));
    this.provideLoginServiceProvider = DoubleCheck.provider(NetworkModule_ProvideLoginServiceFactory.create(networkModuleParam, provideOkHttpClientProvider));
  }
  
  private MainActivity injectMainActivity(MainActivity instance) {
  	// userRepositoryProvider.get() 取出的是 UserRepository 对应的工厂类对象
    MainActivity_MembersInjector.injectRepository(instance, userRepositoryProvider.get());
    MainActivity_MembersInjector.injectLoginService1(instance, provideLoginServiceProvider.get());
    MainActivity_MembersInjector.injectLoginService2(instance, provideLoginServiceProvider.get());
    return instance;
  }
}

假如要提供 T 类型的单例对象,那么在 @Component 接口实现类中会做如下几件事:

  1. 内部会持有 Provider<T> 对象,在 initialize() 中被初始化为 DoubleCheck.provider(Factory<T>),DoubleCheck 类会保证 T 为单例
  2. 在向 Activity 做字段注入时,将 T 类型对应的工厂类对象取出交给待注入的 Activity

下面来看看 DoubleCheck 是如何保证单例的:

public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
  /**
   * Returns a {@link Provider} that caches the value from the given delegate provider.
   */
  public static <P extends Provider<T>, T> Provider<T> provider(P delegate) {
    checkNotNull(delegate);
    // 如果 delegate 已经是一个 DoubleCheck 对象了,那就不再用 DoubleCheck 再次包装它了,直接返回
    if (delegate instanceof DoubleCheck) {
      /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped
       * binding, we shouldn't cache the value again. */
      return delegate;
    }
    // delegate 传的是生产对象的工厂,如 UserRepository_Factory,用 DoubleCheck 封装一层返回
    return new DoubleCheck<T>(delegate);
  }
}

将原本提供对象的工厂包装成一个 DoubleCheck 对象,这样调用 get() 去获取对象时,就会调用到 DoubleCheck 的 get():

  private static final Object UNINITIALIZED = new Object();

  private volatile Provider<T> provider;
  private volatile Object instance = UNINITIALIZED;

  private DoubleCheck(Provider<T> provider) {
    assert provider != null;
    this.provider = provider;
  }
  
  @Override
  public T get() {
    Object result = instance;
    // 第一次调用当前 DoubleCheck 对象的 get() 会命中 if
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          instance = reentrantCheck(instance, result);
          /* Null out the reference to the provider. We are never going to need it again, so we
           * can make it eligible for GC. */
          provider = null;
        }
      }
    }
    return (T) result;
  }

对 result 进行双重判断是否为 UNINITIALIZED,如果是,则调用 provider.get() 给 result 赋值,再用 result 给 instance 赋值,provider 就是提供对象的工厂类,其实就是将所需对象给了 result。这样在后续再次调用 get() 时就不会命中 if 条件了,直接用 instance 给 result 赋值并返回,从而保证被 DoubleCheck.provider(Factory<T>) 包裹的工厂生产出来的对象是单例

2.3.3 全局单例与局部单例

我们以上说的单例实际上都是局部单例,单例的作用域范围只是在 @Component 接口方法指定的需要注入的 Activity 内。现在我们增加一个 Activity 对 UserRepository 执行注入:

@Singleton
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    void injectActivity(MainActivity mainActivity);
    // 增加 SecondActivity,也进行注入
    void injectActivity(SecondActivity secondActivity);
}
--------------------------------------------------------
// SecondActivity 类似
public class MainActivity extends AppCompatActivity {

    @Inject
    UserRepository repository1;

    @Inject
    UserRepository repository2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        DaggerApplicationComponent.create().injectActivity(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("MainActivity", "userRepository1:" + repository1 + ",userRepository2:" + repository2);
    }  
}

输出结果:

能看到 UserRepository 只在 Activity 内部保持了单例,但是在 Activity 之间不是单例。这是因为在两个 Activity 中调用 DaggerApplicationComponent.create() 生成的 DaggerApplicationComponent 不是同一个对象,导致其内部的 DoubleCheck 也不是同一个对象,所以会出现,两个 DoubleCheck 之间无法保证单例,只能保证同一个 DoubleCheck 对象内的单例。

知道了原因,解决方案也就容易想到,想让 UserRepository 是全局单例,即在 Application 范围内保持单例,将 DaggerApplicationComponent 提到 Application 中就行了:

public class MyApplication extends Application {

    private ApplicationComponent applicationComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        applicationComponent = DaggerApplicationComponent.create();
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }
}

然后在 Activity 中从 MyApplication 获取 ApplicationComponent 执行注入:

((MyApplication)getApplication()).getApplicationComponent().injectActivity(this);

这样就实现了全局单例。

3、进阶使用

之前我们只引入了一个 Component,但是在实际项目中,通常是有多个 Component 的,并且由于 Dagger2 在语法上不允许多个 Component 向同一个类进行注入,所以通常都是将多个 Component 组合到一起使用:

其实用到 Component 组合的场景非常多,我们只能选择其中之一进行介绍。假如有一个全局单例的组件 ApplicationComponent 对 Activity 进行注入:

@Module
public class NetworkModule {

    @Singleton
    @Provides
    public NetworkObject provideNetworkObject() {
        return new NetworkObject();
    }
}
----------------------------------------------------
@Singleton
@Component(modules = {NetworkModule.class})
public interface ApplicationComponent {
    void injectActivity(TestActivity testActivity);

    // 其他注入方法省略....
}
----------------------------------------------------
public class MyApplication extends Application {

    private ApplicationComponent applicationComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        applicationComponent = DaggerApplicationComponent.create();
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }
}

现在,假设我们使用 MVP 模式展示页面,那么 Presenter 也需要一个单独的 Module 和 Component:

@Module
public class PresenterModule {

    @Provides
    public Presenter providePresenter() {
        return new Presenter();
    }
}
----------------------------------------------------
@Component(modules = {PresenterModule.class})
public interface PresenterComponent {
	// 这里让 PresenterComponent 也注入 TestActivity,编译会报错的
    void injectActivity(TestActivity testActivity);
}

由于 ApplicationComponent 和 PresenterComponent 都要注入 TestActivity,这时编译就会报错:

PresenterComponent.java:10: 错误: [Dagger/MissingBinding] com.demo.dagger.object.NetworkObject cannot be provided without an @Inject constructor or an @Provides-annotated method.

实际上这个错误是因为有两个 Component 注入 TestActivity 导致的,那么只能将 Component 组合到一起进行注入,下面介绍两种方法。

3.1 dependencies

先确定使用哪个 Component 作为主 Component,确定后,主 Component 仍然执行注入操作,而其他 Component 作为依赖项,不再执行注入,转而提供 Module 提供的对象。一般我们会选择 ApplicationComponent 作为主 Component。

具体操作是,在 ApplicationComponent 的 @Component 注解值中增加 dependencies,指定依赖的 Component:

@Singleton
@Component(modules = {NetworkModule.class}, /*增加依赖项Component*/dependencies = {PresenterComponent.class})
public interface ApplicationComponent {
    void injectActivity(TestActivity loginActivity);

    // 其他注入方法省略...
}

然后将被依赖的 PresenterComponent 中的注入方法改为提供 Presenter 对象:

@Component(modules = {PresenterModule.class})
public interface PresenterComponent {

    //    void injectActivity(TestActivity loginActivity);
    Presenter providePresenter();
}

最后在创建 ApplicationComponent 接口实例时需要指定 PresenterComponent:

public class MyApplication extends Application {

    private ApplicationComponent applicationComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        applicationComponent = DaggerApplicationComponent.builder()
        		// 指定 PresenterComponent 的实现类
                .presenterComponent(DaggerPresenterComponent.create())
                .build();
    }
}

3.2 @SubComponent

此外还可以通过 @SubComponent 注解标记子组件解决上述问题。与 dependencies 类似,我们还是想让 ApplicationComponent 作为主 Component,所以将 @SubComponent 标记给 PresenterComponent 使其作为子组件:

@Subcomponent(modules = {PresenterModule.class})
public interface PresenterComponent {
    void injectActivity(TestActivity loginActivity);
}

注意由子组件执行注入,而主组件则定义一个返回子组件对象的方法:

@Singleton
@Component(modules = {NetworkModule.class})
public interface ApplicationComponent {
    //    void injectActivity(TestActivity loginActivity);
    PresenterComponent getPresenterComponent();
}

创建 ApplicationComponent 接口实例后,通过该实例获取 PresenterComponent 实例再执行注入:

public class MyApplication extends Application {

    private ApplicationComponent applicationComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        applicationComponent = DaggerApplicationComponent.create();
    }
}
----------------------------------------------------
public class TestActivity extends AppCompatActivity {

    @Inject
    NetworkObject networkObject;

    @Inject
    Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ((MyApplication) getApplication()).getApplicationComponent().getPresenterComponent().injectActivity(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

可以看到与 dependencies 方式相比,主组件与子组件在接口方法定义方式上是相反的。并且生成的代码中,子组件的实现类是主组件的 private 内部类,导致子组件的方法无法被我们调用,没有使用 dependencies 的方式灵活。

3.3 @Scope

假如现在有个需求,想让 Presenter 也变成个单例对象,按照我们之前的做法,给 PresenterModule 中的 Provides 方法和 PresenterComponent 加上 @Singleton 之后,会发现编译报错了:

com.demo.dagger.component.ApplicationComponent also has @Singleton

它说 ApplicationComponent 中已经有 @Singleton 了。显然,Dagger2 要求 @Singleton 不能用在多个组件上。实际上,是要求同一个作用域注解不能用在多个组件上。@Scope 注解表示作用域,被其修饰的类或提供对象的方法会被做成单例,所以我们可以用 @Scope 自定义一个作用域注解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

实际上它和 @Singleton 就只差一个注解名字:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

我们给 PresenterComponent 使用 @ActivityScope,这样编译错误就解决了:

@Module
public class PresenterModule {

    @ActivityScope
    @Provides
    public Presenter providePresenter() {
        return new Presenter();
    }
}
----------------------------------------------------
@ActivityScope
@Subcomponent(modules = {PresenterModule.class})
public interface PresenterComponent {
    void injectActivity(TestActivity loginActivity);
}

使用 @Scope 的原则:

  1. 多个 Component 上面的 Scope 不能相同
  2. 没有 Scope 的 Component 不能依赖有 Scope 的组件
  3. 使用作用域注解的模块只能在带有相同作用域注解的组件中使用
  4. 使用构造方法注入(通过 @Inject)时,应在类中添加作用域注解;使用 Dagger 模块时,应在 @Provides 方法中添加作用域注解

4、其他注解的使用

4.1 Named 注解

此外还有一种情况,比如有个 User 类:

public class User {

    private String name;
    private int pwd;

    public User(String name, int pwd) {
        this.name = name;
        this.pwd = pwd;
    }
}

在 Module 中有两个提供该类对象的方法:

	@Provides
    public User provideUser1() {
        return new User("user1", 123);
    }

    @Provides
    public User provideUser2() {
        return new User("user2", 456);
    }

那么你在注入对象时如果还是用基础方法:

	@Inject
    User user;

框架是无法确定使用 provideUser1() 还是 provideUser2() 注入 user 的,此时就要用到 @Named 注解,给方法和被注入对象都用 @Named 的 key 进行对应:

	@Named(value = "key1")
    @Provides
    public User provideUser1() {
        return new User("user1", 123);
    }

    @Named(value = "key2")
    @Provides
    public User provideUser2() {
        return new User("user2", 456);
    }
    ========================================
    // 被注入的对象也用 @Named 并指定一个值,然后框架就会使用这个值对应的方法进行注入
    @Named(value = "key1")
    @Inject
    User user1;

    @Named(value = "key2")
    @Inject
    User user2;

如果 User 构造方法的参数不是写死的,而是需要以参数形式传入,可以把参数定义成 Module 中的成员变量。

@Named 注解内部使用了 @Qualifier 注解可以关注一下。

4.2 Lazy 与 Provider

如果需要实现懒加载式的注入,可以使用 Lazy 或 Provider:

	@Inject
	Lazy<User> lazy;
	
	@Inject
	Provider<User> provider;

	User user1 = lazy.get();
	User user2 = provider.get();	

这样直到调用 get() 时才去注入 User 对象。Lazy 与 Provider 的区别在于,Lazy 是一个单例,而 Provider 不是:

	@Override
    public void injectMembers(TestActivity instance) {
        if (instance == null) {
        	throw new NullPointerException("Cannot inject members into a null reference");
        }
        ...
        instance.lazy = DoubleCheck.lazy(aAndProviderAndLazyProvider);
        instance.provider = aAndProviderAndLazyProvider;
    }

DoubleCheck 包裹的对象都会处理成单例,因此 instance.lazy 是单例,而 instance.provider 只是一个普通成员,不是单例。

5、总结

使用 Dagger2 的最佳做法:

  1. 如果有可能,通过 @Inject 进行构造函数注入,以向 Dagger 图中添加类型。如果没有可能,则执行以下操作:

    • 使用 @Binds 告知 Dagger 接口应采用哪种实现
    • 使用 @Provides 告知 Dagger 如何提供你的项目所不具备的类
  2. 只能在组件中声明一次模块

  3. 根据注释的使用生命周期,为作用域注释命名,例如 @ApplicationScope@LoggedUserScope@ActivityScope

参考资料:

依赖注入

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值