Dagger2 | 二、入门 - @Component

Dagger2 的核心是 @Component,用来管理依赖注入的细节,充当目标类和实例类之间的中介。当它发现目标类需要依赖,就会自动生成对应的实例,并注入到指定位置。

API 描述:

Annotates an interface or abstract class for which a fully-formed, dependency-injected implementation is to be generated from a set of modules. The generated class will have the name of the type annotated with @Component prepended with Dagger. For example, @Component interface MyComponent {...} will produce an implementation named DaggerMyComponent.

简单翻译:

注解一个接口或抽象类,该接口或抽象类将从 @Modules 集合生成完整的依赖注入实现。用 @Component 注解接口所生成的类,将有一个使用 Dagger 前缀的类型名称。例如,@Component MyComponent{…} 将生成一个名为 DaggerMyComponent 的实现。

2.1 解决思路

上一章,依赖注入失败,我们需要排查问题,找到解决办法。

@Inject 引申出来的 FactoryMembersInjector 接口,需要重点关注。

2.1.1 工厂接口

Factory 接口是 Dagger2 框架的成员,它继承 JSR330 框架的 Provider 接口,用来提供实例。

API 描述:

An unscoped Provider. While a Provider may apply scoping semantics while providing an instance, a factory implementation is guaranteed to exercise the binding logic (@Inject constructors, @Provides methods) upon each call to get.
Note that while subsequent calls to get will create new instances for bindings such as those created by @Inject constructors, a new instance is not guaranteed by all bindings. For example, @Provides methods may be implemented in ways that return the same instance for each call.

简单翻译:

一种非范围提供者。虽然 JSR330 框架的提供者接口可以在提供实例时应用范围语义(译注:通常是指单例),但工厂实现可以保证在每次调用 get 方法时执行绑定逻辑(即 @Inject 注解构造函数,@Provides 注解方法)。
注意:虽然 get 方法的后续调用将为绑定创建新的实例,比如由 @Inject 注解构造函数所创建的那些实例,但并非所有绑定都保证创建新的实例。例如,@Provides 注解方法的实现方式可能为每次调用返回相同的实例。

提示:JSR330 框架的 Provider 接口不在本章的讨论范围内,留给大家自行探索。Dagger2 框架的 @Provides 注解将在下一章开始讨论。

Factory 接口的 API 描述和上一章的总结一致,由于它是正常工作,我们可以排除嫌疑。

2.1.2 成员注入器接口

MembersInjetor 接口是 Dagger2 框架的成员,它有一个 injectMembers 方法,用来注入成员。

查看 injectMembers 方法的 API 描述:

Injects dependencies into the fields and methods of instance. Ignores the presence or absence of an injectable constructor.
Whenever a @Component creates an instance, it performs this injection automatically (after first performing constructor injection), so if you're able to let the component create all your objects for you, you'll never need to use this method.

简单翻译:

注入依赖到实例的字段和方法上。忽略可注入构造函数是否存在。
当创建一个 @Component 组件实例时,它会自动执行注入(在最先执行的构造函数注入之后),所以如果你可以让组件为你创建所有的对象,你永远不需要使用这个方法(译注:指主动调用此方法)。

原来是我们没有用到 @Component,所以依赖注入才没有成功。

2.2 入门实战

根据 @Component 的 API 描述,一步步实现依赖注入。

2.2.1 组件接口

创建 ActivityComponent 接口:

@Component
interface ActivityComponent {
}

编译生成的 DaggerActivityComponent 类:

final class DaggerActivityComponent implements ActivityComponent {

  private DaggerActivityComponent() {
  }

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

  public static ActivityComponent create() {
    return new Builder().build();
  }

  static final class Builder {
    private Builder() {
    }

    public ActivityComponent build() {
      return new DaggerActivityComponent();
    }
  }
}

实现为建造者模式,可以通过 new Builder().build()create() 方法获取 ActivityComponent 实例,但并没有生成依赖注入的相关代码,我们还需要更多的资料。

2.2.2 组件方法

继续往下查看 @Component 的 API 描述:

Component methods
Every type annotated with @Component must contain at least one abstract component method. Component methods may have any name, but must have signatures that conform to either Provider or MembersInjector contracts.

简单翻译:

组件方法
每个用 @Component 注解的类型都必须包含至少一个抽象的组件方法。组件方法可以有任意名称,但必须有符合 Provider 接口或 MembersInjector 接口约定的签名。

Provision methods

Provision methods have no parameters and return an @Inject or @Provides type. Each method may have a @Qualifier annotation as well. The following are all valid provision method declarations:

 SomeType getSomeType();
 Set getSomeTypes();
 int getPortNumber();

简单翻译:

提供方法没有参数,返回 @Inject@Provides 类型。每个方法上可以被 @Qualifier 注解。以下是所有有效的提供方法声明:

 SomeType getSomeType();
 Set getSomeTypes();
 int getPortNumber();

提示:@Qualifier 将在后面的章节中进行介绍。

按照以上描述,在 ActivityComponent 组件中创建 account 方法:

@Component
public interface ActivityComponent {

    Account account();
}

编译后,DaggerActivityComponent 类出现新的内容:

final class DaggerActivityComponent implements ActivityComponent {

    @Override
    public Account account() {
        return new Account();
    }

    // ...
}

还是直接 new 出来 Account 类的实例,提供方法不满足我们的需求。

Members-injection methods

Members-injection methods have a single parameter and inject dependencies into each of the Inject-annotated fields and methods of the passed instance. A members-injection method may be void or return its single parameter as a convenience for chaining. The following are all valid members-injection method declarations:

 void injectSomeType(SomeType someType);
 SomeType injectAndReturnSomeType(SomeType someType);

简单翻译:

成员注入方法只有一个参数,它注入依赖项到此参数实例的每个被 @Inject 注解的字段和方法中。成员注入方法可以返回 void,也可以返回它传入的单个参数,以便进行链式调用。下面是所有有效的成员注入方法声明:

 void injectSomeType(SomeType someType);
 SomeType injectAndReturnSomeType(SomeType someType);

按照以上描述,我们创建 inject 方法,参数为 MainActivity 类:

@Component
interface ActivityComponent {

    Account account();

    void inject(MainActivity activity);
}

编译生成的关键内容:

final class DaggerActivityComponent implements ActivityComponent {
  // ...

  @Override
  public Account account() {
    return new Account();}

  @Override
  public void inject(MainActivity activity) {
    injectMainActivity(activity);}

  private MainActivity injectMainActivity(MainActivity instance) {
    MainActivity_MembersInjector.injectAccount(instance, new Account());
    return instance;
  }

  // ...
}

自动调用成员注入器进行 Account 实例的注入,现在完全满足我们的需求。

注意,在 @Component 接口的成员注入方法中,不能为了省事而只传入通用基类。比方说:如果方法参数是 BaseActivity 类,那么 Dagger2 只会将依赖项注入到 BaseActivity 类中被 @Inject 注解的地方,而不会对它的子类 MainActivity 进行任何依赖注入,即便传入的参数是子类 MainActivity 的实例。

2.2.3 简单测试

既然都是 new 出来的 Account 实例,那么在构造函数上注解 @Inject 有点多余。

让我们注释掉 @Inject,看看会出现什么情况:

错误: [Dagger/MissingBinding] com.github.mrzhqiang.dagger2_example.account.Account cannot be provided without an @Inject constructor or an @Provides-annotated method.

原来 Account 类不能提供实例,是因为丢失 @Inject 注解的构造函数或 @Provides 注解的方法。

由此可知,要提供实例,必须在 @Inject@Provides 中二选一,工厂接口的 API 描述也提到过这一点。关于 @Provides,我们下一章再讨论,现在 恢复构造函数上的 @Inject,继续下一步。

2.2.4 组件实例

构建 ActivityComponent 接口的实例,调用 inject 方法:

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {

    @Inject
    Account account;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerActivityComponent.builder().build().inject(this);

        TextView contentText = findViewById(R.id.content_text);
        contentText.setText(account.toString());
    }
}

也可以这样创建实例:

DaggerActivityComponent.create().inject(this);

最终,我们打通了依赖注入的全部流程,可以试着跑一下程序。

2.3 运行

运行效果:

依赖注入成功

没有空指针异常,没有闪退,成功显示 Account 实例的内容。

2.4 总结

实现 Dagger2 依赖注入的基本步骤:

  1. 创建实例类 Account 账户,将 @Inject 注解在构造函数上
  2. 创建目标类 MainActivity 活动,将 @Inject 注解在依赖字段上
  3. 创建组件 ActivityComponent 接口,将 @Component 注解在接口上,创建成员注入方法
  4. 构建 ActivityComponent 接口的实例,在合适的地方调用成员注入方法

现在,我们已成功入门 Dagger2 框架,可以在实际开发中大展身手。

下一章,讨论 @Provides,它可以实现第三方库的依赖注入。

你可能感兴趣的:(Dagger2 | 二、入门 - @Component)