Dagger2 中的 @Provides 是 @Inject 的替代方案。当需要提供实例时,由于 @Inject 注解在构造函数上,因此无法提供第三方库的实例,而 @Provides 则注解在方法上,可以通过方法返回第三方库实例。
API 描述:
Annotates methods of a
@Module
to create a provider method binding. The method's return type is bound to its returned value. The@Component
implementation will pass dependencies to the method as parameters.
简单翻译:
@Module
注解在模块方法上,用来创建提供者方法绑定。模块方法的返回类型绑定到它的返回值。@Component
的实现是将依赖项作为参数传递到模块方法。
注意,默认情况下,Dagger2 禁止注入 null
值,调用 @Provides
注解的方法如果返回 null
值,将立即抛出 NullPointerException
异常。通过 @Nullable
可以允许 null
值,但注入字段也必须用 @Nullable
注解,否则将编译失败。
3.1 进阶实战
根据 API 描述,我们先尝试提供 Account
类的实例,之后再用第三方库提供实例。
3.1.1 账户模块
查看 @Module
的 API 描述:
Annotates a class that contributes to the object graph.
简单翻译:
注解在类上,以便贡献对象图谱。
创建 AccoutModule
类,添加 provideAccount
方法:
@Module
final class AccountModule {
@Provides
Account provideAccount() {
return new Account();
}
}
注意,你可以有任意命名,但约定俗成的是,@Providers
注解的方法以 provide
作为前缀。
查看编译生成的内容:
public final class AccountModule_ProvideAccountFactory implements Factory {
// ...
@Override
public Account get() {
return provideAccount(module);
}
// ...
public static Account provideAccount(AccountModule instance) {
return Preconditions.checkNotNull(instance.provideAccount(), "Cannot return null from a non-@Nullable @Provides method");
}
}
恰好印证 Factory
接口的 API 描述,即可以通过 @Provides
注解的方法执行绑定逻辑。
3.1.2 组件和模块
回忆一下 @Component
的 API 描述:它可以从一组 @Module
集合中生成完整的依赖注入实现。
所以我们将模块交给组件管理:
@Component(modules = AccountModule.class)
public interface ActivityComponent {
// ...
}
编译后的内容:
public final class DaggerActivityComponent implements ActivityComponent {
// ...
@Override
public Account account() {
return AccountModule_ProvideAccountFactory.provideAccount(accountModule);}
@Override
public void inject(MainActivity activity) {
injectMainActivity(activity);}
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectAccount(instance, AccountModule_ProvideAccountFactory.provideAccount(accountModule));
return instance;
}
// ...
}
之前直接 new
实例的代码消失,转而调用 provideAccount
方法执行注入。
我们注意到 ActivityComponent.Builder
类的 build
方法:
public ActivityComponent build() {
if (accountModule == null) {
this.accountModule = new AccountModule();
}
return new DaggerActivityComponent(accountModule);
}
默认情况下,模块实例由建造者直接 new
出来。
思考:如果模块需要用 Context
进行初始化,该如何处理?
3.1.3 简单测试
我们依然对构造函数上的 @Inject
抱有敌意,移除它之后,再编译一下:
Account_Factory
类消失不见,现在 Account
是 POJO 类。
3.1.4 第三方库
我们用真正的第三方库来提供依赖。
声明依赖
在项目的 build.gradle
文件中,引入 jitpack.io
库:
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
在 app 模块的 build.gradle
文件中,添加内容:
dependencies {
// ...
implementation 'com.github.mrzhqiang:security:v1.0'
}
说明一下,这个框架是从 spring-security-crypto 扒到 Android 上来,用于信息的摘要加密处理。
为了运行它,我们还需要在 JDK1.8 版本上编译,所以修改 app 模块中的 build.gradle
文件:
android {
// ...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
提供依赖
在账户模块下提供依赖:
@Module
final class AccountModule {
@Provides
Account provideAccount() {
return new Account();
}
@Provides
PasswordEncoder providePasswordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
编译后,生成新的类:
注入依赖
注入到 MainActivity
类,对密码进行加密处理:
public class MainActivity extends AppCompatActivity {
@Inject
Account account;
@Inject
PasswordEncoder passwordEncoder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerActivityComponent.create().inject(this);
TextView contentText = findViewById(R.id.content_text);
String content = String.format("username: %s, password: %s, encodePassword: %s",
account.getUsername(),
account.getPassword(),
passwordEncoder.encode(account.getPassword()));
contentText.setText(content);
}
}
3.2 运行
现在我们来运行一下,看看效果:
即使是 123456 这样的密码,经过加密处理后,也是普通暴力破解望而却步的长度和复杂度。
3.3 总结
@Provides
注解在提供者方法上,用来提供第三方库依赖。
不仅如此,当我们提取通用类到公共模块中,又希望公共模块保持纯洁,则可以通过 @Provides
提供其依赖。更多情况下,创建实例之后还需要执行初始化操作,为此使用 @Provides
比 @Inject
更灵活。
下一章,讨论 @Singleton
,它可以帮助我们实现最佳单例模式。