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 namedDaggerMyComponent
.
简单翻译:
注解一个接口或抽象类,该接口或抽象类将从
@Modules
集合生成完整的依赖注入实现。用@Component
注解接口所生成的类,将有一个使用 Dagger 前缀的类型名称。例如,@Component MyComponent{…}
将生成一个名为DaggerMyComponent
的实现。
2.1 解决思路
上一章,依赖注入失败,我们需要排查问题,找到解决办法。
由 @Inject
引申出来的 Factory
和 MembersInjector
接口,需要重点关注。
2.1.1 工厂接口
Factory
接口是 Dagger2 框架的成员,它继承 JSR330 框架的 Provider
接口,用来提供实例。
API 描述:
An unscoped
Provider
. While aProvider
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 toget
.
Note that while subsequent calls toget
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 eitherProvider
orMembersInjector
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 依赖注入的基本步骤:
- 创建实例类
Account
账户,将@Inject
注解在构造函数上 - 创建目标类
MainActivity
活动,将@Inject
注解在依赖字段上 - 创建组件
ActivityComponent
接口,将@Component
注解在接口上,创建成员注入方法 - 构建
ActivityComponent
接口的实例,在合适的地方调用成员注入方法
现在,我们已成功入门 Dagger2 框架,可以在实际开发中大展身手。
下一章,讨论 @Provides
,它可以实现第三方库的依赖注入。