Android _ 从 Dagger2 到 Hilt 玩转依赖注入(一),android面试必看书籍

1. 为什么要进行依赖注入

依赖注入(Dependency Injection,简称 DI)其实并不是一个很神秘的概念,往往在不经意地间我们就使用了依赖注入。依赖注入应用了 “控制反转(IoC)” 的原理,简单来说就是在类的外部构造依赖项,使用构造器或者 setter 注入。

提示: 你往往在不经意间使用了依赖注入的思想。

使用依赖注入可以为我们带来什么好处呢?

  • 重用组件: 因为我们在类外部构造依赖项;
  • 组件解耦: 当我们需要修改某个组件的实现时,不需要在项目中进行大量变更;
  • 易测试: 我们可以向依赖方注入依赖项的模拟实现,这使得依赖方的测试更加容易;
  • 生命周期透明: 依赖方不感知依赖项创建 / 销毁的生命周期,这些可以交给依赖注入框架管理。

2. Android 依赖注入框架

当只有一个依赖项时,手动进行依赖注入很简单,但随着项目规模变大,手动注入会变得越来越复杂。而使用依赖注入框架,可以让依赖注入的过程更加简便,另外,依赖注入框架往往还提供了管理依赖项的生命周期的功能。从实现上,依赖注入框架可以归为两类:

  • 1、基于反射的动态方案: Guice、Dagger;
  • 2、基于编译时注解的静态方案(性能更高): Dagger2、Hilt、ButterKnife。

提示:依赖注入框架本质上不是提供了依赖注入的能力,而是采用了注解等方式让依赖注入变得更加简易。

在这里面,Dagger2 和 Hilt 是我们今天讨论的主题。

  • Dagger2: Dagger 的名字取自有向无环图(DAG,Directed acyclic graph),最初由 Square 组织开发,而后来的 Dagger2 和 Hilt 框架则由 Square 和 Google 共同开发维护。

  • Hilt: Hilt 是 Dagger2 的二次封装,Hilt 本质上是对 Dagger 进行场景化。它为 Android 平台制定了一系列规则,大大简化了 Dagger2 的使用。在 Dagger2 里,你需要手动获取依赖图和执行注入操作,而在 Hilt 里,注入会自动完成,因为 Hilt 会自动找到 Android 系统组件中那些最佳的注入位置。

下面,我们分别来讨论 Dagger2 和 Hilt 两个框架。原本我不打算介绍太多 Dagger2 的内容(因为在 Android 里我们是直接使用 Hilt),考虑到两者的关系还是觉得还是有必要把 Dagger2 讲清楚,才能真正理解 Hilt 帮我们做了什么。


3. Dagger2 使用教程

提示: 我在学习 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 这个帮手来替我们编写模板代码。

3.1 @Component + @Inject

@Component 和 @Inject 是 Dagger2 最基础的两个注解,仅使用这两个注解就可以实现最简单的依赖注入。

  • @Component:创建一个 Dagger 容器,作为获取依赖项的入口

@Component
public interface ApplicationComponent {
UserRepository userRepository();
}

  • @Inject:指示 Dagger 如何实例化一个对象

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

1、实现 ApplicationComponent 接口
public final class DaggerApplicationComponent implements ApplicationComponent {
private DaggerApplicationComponent() {
}

2、创建依赖项实例
@Override
public UserRepository userRepository() {
return new UserRepository(new UserLocalDataSource(), new UserRemoteDataSource());
}

3、构建者模式
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();

3.2 @Inject 字段注入

有些类不是使用构造器初始化的,例如 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() 之前或之后完成。

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 还不能真正称为某个组件的子组件,需要增加额外声明:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

以下是今天给大家分享的一些独家干货:

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

id工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助**。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

以下是今天给大家分享的一些独家干货:

[外链图片转存中…(img-QWfIrcrq-1712346632148)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值