public MyActivity(LoginViewModel viewModel){
…
}
}
字段注入:
class MainActivity : AppCompatActivity() {
@Inject
lateinit var viewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
DaggerApplicationComponent.create().inject001(this)
super.onCreate(savedInstanceState)
…
}
}
public class LoginViewModel {
private final UserRepository userRepository;
@Inject
public LoginViewModel(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
在 Activity 或 Fragment 中使用时,需要注意组件的生命周期:
-
在 super.onCreate() 中的恢复阶段,Activity 会附加绑定的 Fragment,这些 Fragment 可能需要访问 Activity。为保证数据一致性,应在调用 super.onCreate() 之前在 Activity 的 onCreate() 方法中注入 Dagger。
-
在使用 Fragment 时,应在 Fragment 的 onAttach() 方法中注入 Dagger,此操作可以在调用 super.onAttach() 之前或之后完成。
3.3 @Singleton / @Scope
- @Singleton / @Scope:声明作用域,可以约束依赖项的作用域周期
@Singleton
public class UserRepository {
…
}
@Component
@Singleton
public interface ApplicationComponent {
…
}
在 ApplicationComponent 和 UserRepository 上使用相同的作用域注解,表明两者处于同一个作用域周期。这意味着,同一个 Component 多次提供该依赖项都是同一个实例。你可以直接使用内置的 @Singleton,也可以使用自定义注解:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}
提示: 使用 @Singleton 或 @MyCustomScope,效果是完全一样的。
以上代码在构建后会自动生成代码:
public final class DaggerApplicationComponent implements ApplicationComponent {
private Provider userRepositoryProvider;
private DaggerApplicationComponent() {
initialize();
}
private void initialize() {
this.userRepositoryProvider = DoubleCheck.provider(UserRepository_Factory.create(UserLocalDataSource_Factory.create(), UserRemoteDataSource_Factory.create()));
}
@Override
public UserRepository userRepository() {
return userRepositoryProvider.get();
}
…
}
作用域注解约束
有几个关于作用域注解的约束,你需要注意下:
- 如果某个组件有作用域注解,那么该组件只能给提供带有该注解的类或者不带任何作用域注解的类;
- 子组件不能使用和某个父组件的相同的作用域注解。
提示: 关于子组件的概念,你可以看 第 3.5 节。
作用域注解规范
只要你满足上面提到的约束规则,Dagger2 框架并不严格限制你定义的作用域语义。你可以按照业务划分作用域,也可以按照生命周期划分作用域。例如:
按照业务划分:
@Singleton
@LoginScope
@RegisterScope
按声明周期划分:
@Singleton
@ActivityScope
@ModuleScope
@FeatureScope
不过,按照生命周期划分作用域是更加理想的做法,作用域不应该明确指明其实现目的。
3.4 @Module + @Providers
- @Module + @Providers:指示 Dagger 如何实例化一个对象,但不是以构造器的方式
public class UserRemoteDataSource {
private final LoginRetrofitService loginRetrofitService;
@Inject
public UserRemoteDataSource(LoginRetrofitService loginRetrofitService) {
this.loginRetrofitService = loginRetrofitService;
}
}
@Module
public class NetworkModule {
@Provides
public LoginRetrofitService provide001(OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl(“https://example.com”)
.build()
.create(LoginService.class);
}
}
@Singleton
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
UserRepository userRepository();
void inject001(MainActivity activity);
}
@Module 模块提供了一种与 @Inject 不同的提供对象实例的方式。在 @Module 里,@Provides 方法的返回值是依赖项实例,而参数是进一步依赖的对象。另外,你还需要在 @Component 参数中应用该模块。
目前为止,我们构造的依赖关系图如下所示:
3.5 @Subcomponent
- @Subcomponent:声明子组件,使用子组件的概念可以定义更加细致的作用域
子组件是继承并扩展父组件的对象图的组件,子组件中的对象就可以依赖于父组件中提供的对象,但是父组件不能依赖于子组件依赖的对象(简单的包含关系,对吧?)。
我们继续通过一个简单的例子来展开:假设我们有一个登录模块 LoginActivity,它依赖于 LoginModel。我们的需求是定义一个子组件,它的声明周期只在一次登录流程中存在。在 第 3.2 节 提过,Activity 无法使用构造器注入,所以 LoginActivity 我们采用的是 @Inject 字段注入的语法:
@Subcomponent
public interface LoginComponent {
void inject(LoginActivity activity);
}
但是这样定义的 LoginComponent 还不能真正称为某个组件的子组件,需要增加额外声明:
@Module(subcomponents = LoginComponent.class)
public class SubComponentsModule {
}
@Component(modules = {NetworkModule.class,SubComponentsModule.class})
@Singleton
public interface ApplicationComponent {
UserRepository userRepository();
LoginComponent.Factory loginComponent();
}
@Subcomponent
public interface LoginComponent {
@Subcomponent.Factory
interface Factory{
LoginComponent create();
}
void inject001(LoginActivity activity);
}
在这里,我们需要定义一个新模块 SubcomponentModule,同时需要在 LoginComponent 中定义子组件 Factory,以便 ApplicationComponent 知道如何创建 LoginComponent 的示例。
现在,LoginComponent 就算声明完成了。为了让 LoginComponent 保持和 LoginActivity 相同的生命周期,你应该在 LoginActivity 内部创建 LoginComponent 实例,并持有引用:
public class LoginActivity extends Activity {
1、持有子组件引用,保证相同生命周期
LoginComponent loginComponent;
2、@Inject 字段注入
@Inject
LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
3、创建子组件实例
loginComponent = ((MyApplication) getApplicationContext())
.appComponent.loginComponent().create();
4、注入
loginComponent.inject(this);
…
}
}
执行到步骤 4 ,loginViewModel 字段就初始化完成了。这里有一个需要特别注意的点,你思考这个问题:如果你在 LoginActivity 中的一个 Fragment 重复注入 LoginViewModel,它是一个对象吗?
@Subcomponent
public interface LoginComponent {
@Subcomponent.Factory
interface Factory {
LoginComponent create();
}
void inject001(LoginActivity loginActivity);
void inject002(LoginUsernameFragment fragment);
}
肯定是不同对象的,因为我们还没有使用 第 3.3 节 提到的 @Singleton / @Scope 作用域注解。现在我们增加作用域注解:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}
@ActivityScope
@Subcomponent
public interface LoginComponent { … }
@ActivityScope
public class LoginViewModel {
private final UserRepository userRepository;
@Inject
public LoginViewModel(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
目前为止,我们构造的依赖关系图如下所示:
4. 在 Dagger2 的基础上进行单元测试
当一个项目应用了 Dagger2 或者其它依赖注入框架,那么在一定程度上它的各个组件之间是处于一种松耦合的状态,此时进行单元测试显得游刃有余。
在 Dagger2 项目上你可以选择在不同级别上注入模拟依赖项:
4.1 对象级别:
你可以定义一个 FakeLoginViewModel,然后替换到 LoginActivity:
public class LoginActivity extends Activity {
1、持有子组件引用,保证相同生命周期
LoginComponent loginComponent;
2、@Inject 字段注入
@Inject
FakeLoginViewModel loginViewModel;
}
4.2 组件级别
你可为为正式版和测试版定义两个组件:ApplicationComponent 和 TestApplicationComponent:
@Singleton
@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
}
5. 总结
总结一下我们提到的注解:
注解 | 描述 |
---|---|
@Component | 创建一个 Dagger 容器,作为获取依赖项的入口 |
@Inject | 指示 Dagger 如何实例化一个对象 |
@Singleton / @Scope | 作用域,可以约束依赖项的作用域周期 |
@Module + @Providers | 指示 Dagger 如何实例化一个对象,但不是以构造器的方式 |
@Subcomponent | 声明子组件,使用子组件的概念可以定义更加细致的作用域 |
参考资料
- 【Dagger · 官网】【Hilt · 官网】【Dagger2 · Github】
- 《Android 中的依赖项注入》系列文档 —— Android Developers(必看)
- 《Tasting Dagger 2 on Android.》 —— Fernando Cejas 著
- 《从 Dagger 到 Hilt,谷歌为何执着于让我们用依赖注入?》 —— 扔物线 著
- 《Jetpack 新成员,一篇文章带你玩转 Hilt 和依赖注入》 —— 郭霖 著
可以定义更加细致的作用域 |
参考资料
- 【Dagger · 官网】【Hilt · 官网】【Dagger2 · Github】
- 《Android 中的依赖项注入》系列文档 —— Android Developers(必看)
- 《Tasting Dagger 2 on Android.》 —— Fernando Cejas 著
- 《从 Dagger 到 Hilt,谷歌为何执着于让我们用依赖注入?》 —— 扔物线 著
- 《Jetpack 新成员,一篇文章带你玩转 Hilt 和依赖注入》 —— 郭霖 著