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<SomeType> getSomeTypes();
 int getPortNumber();

简单翻译:

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

 SomeType getSomeType();
 Set<SomeType> 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,它可以实现第三方库的依赖注入。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353