点赞关注,再也不迷路,你的支持对我意义重大!java
🔥 Hi,我是丑丑。本文 GitHub · Android-NoteBook 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一块儿成长。(联系方式在 GitHub)android
这篇文章的内容会涉及如下前置 / 相关知识,贴心的我都帮你准备好了,请享用~git
依赖注入(Dependency Injection,简称 DI)其实并非一个很神秘的概念,每每在不经意地间咱们就使用了依赖注入。依赖注入应用了 “控制反转(IoC)” 的原理,简单来讲就是在类的外部构造依赖项,使用构造器或者 setter 注入。github
提示: 你每每在不经意间使用了依赖注入的思想。markdown
使用依赖注入能够为咱们带来什么好处呢?app
当只有一个依赖项时,手动进行依赖注入很简单,但随着项目规模变大,手动注入会变得愈来愈复杂。而使用依赖注入框架,可让依赖注入的过程更加简便,另外,依赖注入框架每每还提供了管理依赖项的生命周期的功能。从实现上,依赖注入框架能够归为两类:框架
提示:依赖注入框架本质上不是提供了依赖注入的能力,而是采用了注解等方式让依赖注入变得更加简易。ide
在这里面,Dagger2 和 Hilt 是咱们今天讨论的主题。工具
Dagger2: Dagger 的名字取自有向无环图(DAG,Directed acyclic graph),最初由 Square 组织开发,然后来的 Dagger2 和 Hilt 框架则由 Square 和 Google 共同开发维护。oop
Hilt: Hilt 是 Dagger2 的二次封装,Hilt 本质上是对 Dagger 进行场景化。它为 Android 平台制定了一系列规则,大大简化了 Dagger2 的使用。在 Dagger2 里,你须要手动获取依赖图和执行注入操做,而在 Hilt 里,注入会自动完成,由于 Hilt 会自动找到 Android 系统组件中那些最佳的注入位置。
下面,咱们分别来讨论 Dagger2 和 Hilt 两个框架。本来我不打算介绍太多 Dagger2 的内容(由于在 Android 里咱们是直接使用 Hilt),考虑到二者的关系仍是以为仍是有必要把 Dagger2 讲清楚,才能真正理解 Hilt 帮咱们作了什么。
提示: 我在学习 Dagger2 时,也阅读了不少文章和官方文档。有些做者会列举出全部注解的用法,有些做者只介绍用法而忽略解释自动生成的代码。我也在寻求一种易于理解 / 接受的讲法,最后我以为先「基础注解」再「复杂注解」,边介绍用法边解释自动生成代码的方式,或许是更容易理解的方式。期待获得你的反馈~
在讨论的过程当中,咱们经过一个简单的例子来展开:假设咱们有一个用户数据模块,它依赖于两个依赖项:
public class UserRepository {
private final UserLocalDataSource userLocalDataSource;
private final UserRemoteDataSource userRemoteDataSource;
public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource = userLocalDataSource;
this.userRemoteDataSource = userRemoteDataSource;
}
}
复制代码
首先,你能够选择不使用依赖注入,那么你可能就会在项目多处重复构建,缺点咱们在第一节都讨论过了。
new UserRepository(new UserLocalDataSource(), new UserRemoveDataSource());
复制代码
后来,有追求的你已经开始使用依赖注入,你写了一个全局的工具方法:
public static UserRepository get() {
return new UserRepository(new UserLocalDataSource(), new UserRemoveDataSource());
}
复制代码
这确实能知足需求,然而在真实项目中,模块之间的依赖关系每每比这个例子要复杂得多。此时,若是常常手动编写依赖注入的模板代码,不只耗时耗力,也容易出错。下面,咱们开始使用 Dagger2 这个帮手来替咱们编写模板代码。
@Component 和 @Inject 是 Dagger2 最基础的两个注解,仅使用这两个注解就能够实现最简单的依赖注入。
@Component
public interface ApplicationComponent {
UserRepository userRepository();
}
复制代码
public class UserRepository {
private final UserLocalDataSource userLocalDataSource;
private final UserRemoteDataSource userRemoteDataSource;
@Inject
public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource = userLocalDataSource;
this.userRemoteDataSource = userRemoteDataSource;
}
}
--------------------------------------------
public class UserLocalDataSource {
@Inject
public UserLocalDataSource() {
}
}
--------------------------------------------
public class UserRemoveDataSource {
@Inject
public UserRemoveDataSource() {
}
}
复制代码
你须要用 @Inject 注解修饰依赖项的构造方法,同时,它的依赖项 UserLocalDataSource 和 UserRemoteDataSource 也须要增长 @Inject 注解。
以上代码在构建后会自动生成代码:
DaggerApplicationComponent.java
一、实现 ApplicationComponent 接口
public final class DaggerApplicationComponent implements ApplicationComponent {
private DaggerApplicationComponent() {
}
二、建立依赖项实例
@Override
public UserRepository userRepository() {
return new UserRepository(new UserLocalDataSource(), new UserRemoteDataSource());
}
三、构建者模式
public static Builder builder() {
return new Builder();
}
public static ApplicationComponent create() {
return new Builder().build();
}
public static final class Builder {
private Builder() {
}
public ApplicationComponent build() {
return new DaggerApplicationComponent();
}
}
}
复制代码
能够看到,最简单的依赖注入模板代码已经自动生成了。使用时,你只须要经过 ApplicationComponent 这个入口就能够得到 UserReopsitory 实例:
ApplicationComponent component = DaggerApplicationComponent.create();
UserRepository userRepository = component.userRepository();
复制代码
有些类不是使用构造器初始化的,例如 Android 框架类 Activity 和 Fragment 由系统实例化,此时就不能再使用 3.1 节 中使用的构造器注入,能够改成字段注入,并手动调用方法请求注入。
构造器注入:(X)
public class MyActivity {
@Inject
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() 以前或以后完成。
@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<UserRepository> 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
复制代码
不过,按照生命周期划分做用域是更加理想的作法,做用域不该该明确指明其实现目的。
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 参数中应用该模块。
目前为止,咱们构造的依赖关系图以下所示:
子组件是继承并扩展父组件的对象图的组件,子组件中的对象就能够依赖于父组件中提供的对象,可是父组件不能依赖于子组件依赖的对象(简单的包含关系,对吧?)。
咱们继续经过一个简单的例子来展开:假设咱们有一个登陆模块 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 {
一、持有子组件引用,保证相同生命周期
LoginComponent loginComponent;
二、@Inject 字段注入
@Inject
LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
三、建立子组件实例
loginComponent = ((MyApplication) getApplicationContext())
.appComponent.loginComponent().create();
四、注入
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;
}
}
复制代码
目前为止,咱们构造的依赖关系图以下所示:
当一个项目应用了 Dagger2 或者其它依赖注入框架,那么在必定程度上它的各个组件之间是处于一种松耦合的状态,此时进行单元测试显得游刃有余。
在 Dagger2 项目上你能够选择在不一样级别上注入模拟依赖项:
你能够定义一个 FakeLoginViewModel,而后替换到 LoginActivity:
public class LoginActivity extends Activity {
一、持有子组件引用,保证相同生命周期
LoginComponent loginComponent;
二、@Inject 字段注入
@Inject
FakeLoginViewModel loginViewModel;
}
复制代码
你可为为正式版和测试版定义两个组件:ApplicationComponent 和 TestApplicationComponent:
@Singleton
@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
}
复制代码
总结一下咱们提到的注解:
注解 | 描述 |
---|---|
@Component | 建立一个 Dagger 容器,做为获取依赖项的入口 |
@Inject | 指示 Dagger 如何实例化一个对象 |
@Singleton / @Scope | 做用域,能够约束依赖项的做用域周期 |
@Module + @Providers | 指示 Dagger 如何实例化一个对象,但不是以构造器的方式 |
@Subcomponent | 声明子组件,使用子组件的概念能够定义更加细致的做用域 |
创做不易,你的「三连」是丑丑最大的动力,咱们下次见!