APT [点赞更新] Java 注解(包括Kotlin)
1. 为什么要进行依赖注入
依赖注入(DI)实际上并不是那么神秘的概念。依赖注入应用“控制反转”(IoC)的原理。这仅仅意味着使用构造函数或setter 注入在类外部构建依赖项。
提示:人们经常错误地使用依赖注入的思想。
使用依赖注入有什么好处?
组件重用:因为您在类之外构建依赖项,所以组件隔离:如果您需要更改组件的实现,您可以注入依赖项的模拟实现,而无需对项目进行大量更改。纳入依赖方可以更轻松地测试依赖方。生命周期透明:依赖方不知道依赖项的创建/销毁生命周期,并且可以由依赖项注入框架进行管理。
2. Android 依赖注入框架
当只有一个依赖项时,手动依赖项注入很简单,但随着项目规模的增加,手动依赖项注入会变得越来越复杂。依赖注入框架使依赖注入过程变得更加容易。此外,依赖项注入框架通常提供管理依赖项生命周期的功能。从实现的角度来看,依赖注入框架可以分为两类:
1. 基于反射的动态解决方案:Guice、Dagger;2. 基于编译时注释的静态解决方案(高性能):Dagger2、Hilt、ButterKnife。
提示:依赖注入框架本身并不提供依赖注入功能,而是使用注释和其他方法来促进依赖注入。
今天我们就来聊聊Dagger2和Hilt。
Dagger2:Dagger以有向无环图(DAG)命名,最初由Square组织开发。 Dagger2 和Hilt 框架随后由Square 和Google 联合开发和维护。
Hilt:Hilt是Dagger2的二次封装。它本质上是Dagger 的基于场景的版本。为Android平台建立了一套规则,大大简化了Dagger2的使用。与Dagger2 需要您手动检索依赖图并执行注入操作不同,Hilt 会自动查找Android 系统组件中的最佳注入位置,因此注入会自动发生。
接下来,我们将分别讨论两个框架:Dagger2 和Hilt。最初,我并没有打算过多介绍Dagger2(因为Android 直接使用Hilt)。考虑到两者之间的关系,我们觉得为了真正理解Hilt给我们带来了什么,我们需要清楚地解释Dagger2。
3. Dagger2 使用教程
提示:我在学习Dagger2的时候,也看了很多文章和官方文档。有些作者列出了所有注释的用法,而另一些作者只提供用法并忽略自动生成的代码描述。我也在寻找一种易于理解和接受的教学方法,但我认为如果从“基本注释”开始,然后解释如何自动生成“复杂注释”,会更容易理解。我将引导您完成代码并向您展示如何使用它。我们期待听到您的意见~
在讨论过程中,我们将扩展这个简单的示例。假设您有一个依赖于两个依赖项的用户数据模块。
公共类用户存储库{
私有最终UserLocalDataSource userLocalDataSource;
私有最终UserRemoteDataSource userRemoteDataSource;
公共UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource=userLocalDataSource;
this.userRemoteDataSource=userRemoteDataSource;
}
}
首先,您可以选择不使用依赖注入。在这种情况下,您可能需要在项目中的多个位置重复构建它。它的缺点已在第一节中讨论过。
新的UserRepository(新的UserLocalDataSource(),新的UserRemoveDataSource());
然后我开始使用依赖注入并创建全局工具方法。
公共静态UserRepository get() {
返回新的UserRepository(新的UserLocalDataSource(),新的UserRemoveDataSource());
}
虽然这满足了我们的要求,但在实际项目中,模块之间的依赖关系往往比这个例子复杂得多。目前,频繁手工编写依赖注入模板代码不仅费时费力,而且容易出错。接下来,使用Dagger2 帮助程序创建模板代码。
3.1 @Component + @Inject
@Component和@Inject是Dagger2中两个最基本的注解。只需使用这两个注释就可以实现最简单的依赖注入。
@Component:创建Dagger 容器作为检索依赖项的入口点。
@成分
公共接口应用程序组件{
UserRepository userRepository();
}
@Inject:告诉Dagger如何实例化对象。
公共类用户存储库{
私有最终UserLocalDataSource userLocalDataSource;
私有最终UserRemoteDataSource userRemoteDataSource;
@注入
公共UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource=userLocalDataSource;
this.userRemoteDataSource=userRemoteDataSource;
}
}
公共类用户本地数据源{
@注入
公共用户本地数据源(){
}
}
公共类用户删除数据源{
@注入
公共用户删除数据源(){
}
}
您必须使用@Inject 注释来装饰依赖项的构造函数。同时,还必须在其依赖项UserLocalDataSource和UserRemoteDataSource中添加@Inject注解。
上面的代码构建后会自动生成代码。
DaggerApplicationComponent.java
1. 实现ApplicationComponent接口
公共最终类DaggerApplicationComponent 实现ApplicationComponent {
私人DaggerApplicationComponent() {
}
2. 创建依赖实例
@覆盖
公共UserRepository userRepository() {
返回新的UserRepository(新的UserLocalDataSource(),新的UserRemoteDataSource());
}
3.建造者模式
公共静态构建器构建器(){
返回新的构建器();
}
公共静态ApplicationComponent创建(){
返回一个新的Builder().build()。
}
公共静态最终类生成器{
私有构建器(){
}
公共应用程序组件构建(){
返回一个新的DaggerApplicationComponent()。
}
}
}
正如你所看到的,最简单的依赖注入模板代码已经自动为你生成了。使用此功能时,您所需要做的就是通过ApplicationComponent 条目获取UserReopsitory 实例。
ApplicationComponent 组件=DaggerApplicationComponent.create();
UserRepository userRepository=component.userRepository();
3.2 @Inject 字段注入
有些类不是使用构造函数初始化的。例如,Android框架类Activity和Fragment是由系统实例化的。此时3.1节中使用的构造函数注入已经不能使用了,可以改为字段注入。调用请求注入的方法。
构造函数注入:(X)
公共类我的活动{
@注入
公共MyActivity(LoginViewModel viewModel){
……
}
}
现场注入:
类MainActivity : AppCompatActivity() {
@注入
Lateinit var viewModel: LoginViewModel
覆盖fun onCreate(savedInstanceState: Bundle?) {
DaggerApplicationComponent.create().inject001(this)
super.onCreate(savedInstanceState)
……
}
}
公共类LoginViewModel {
私有最终UserRepository userRepository;
@注入
公共LoginViewModel(UserRepository userRepository) {
this.userRepository=用户存储库;
}
}
在活动或片段中使用组件时,您必须了解组件的生命周期。
在super.onCreate() 的恢复阶段,活动会附加可能需要访问该活动的绑定片段。为了确保数据一致性,您必须在调用super.onCreate() 之前将Dagger 注入到Activity 的onCreate() 方法中。
使用fragment时,必须将Dagger注入到fragment的onAttach()方法中。这可以在调用super.onAttach() 之前或之后完成。
3.3 @Singleton / @Scope
@Singleton/@Scope:声明一个作用域,可以限制依赖的作用域循环。
@单例
公共类用户存储库{
……
}
@成分
@单例
公共接口应用程序组件{
……
}
在ApplicationComponent 和UserRepository 上使用相同的作用域注释来指示它们处于同一作用域循环中。这意味着同一组件多次提供同一依赖项实例。您可以直接使用内置的@Singleton或使用自定义注释。
@范围
@记录
@hold(运行时)
公共@interface单例{}
@范围
@Retention(RetentionPolicy.RUNTIME)
公共@interfaceMyCustomScope{}
提示:使用@Singleton 或@MyCustomScope 具有完全相同的效果。
上面的代码构建后会自动生成代码。
公共最终类DaggerApplicationComponent 实现ApplicationComponent {
私有提供者userRepositoryProvider;
私人DaggerApplicationComponent() {
初始化();
}
私有无效初始化(){
this.userRepositoryProvider=DoubleCheck.provider(UserRepository_Factory.create(UserLocalDataSource_Factory.create(), UserRemoteDataSource_Factory.create()));
}
@覆盖
公共UserRepository userRepository() {
返回userRepositoryProvider.get();
}
……
}
作用域注解约束
您应该注意范围注释的一些限制。
如果组件具有范围注释,则该组件只能提供具有该注释的类,也可以提供不具有范围注释的类。子组件不能使用与父组件相同的范围注释。
提示:有关子组件的概念,请参阅第3.5 节。
作用域注解规范
Dagger2框架并不严格限制你定义的作用域语义,只要你满足上面描述的约束规则即可。您可以按业务划分范围,也可以按生命周期划分范围。例如:
按照划分,
@单例
@登录范围
@RegisterScope
按生命周期细分如下。
@单例
@ActivityScope
@ModuleScope
@FeatureScope
不过,按照生命周期来划分范围是比较理想的,并且范围不应该明确表明实现的目的。
3.4 @Module + @Providers
@Module + @Providers:告诉Dagger如何实例化对象而不是构造函数。
公共类UserRemoteDataSource {
私有最终LoginRetrofitService LoginRetrofitService;
@注入
公共UserRemoteDataSource(LoginRetrofitServiceloginRetrofitService){
this.loginRetrofitService=loginRetrofitService;
}
}
@模块
公共类网络模块{
@假如
公共LoginRetrofitService Provide001(OkHttpClient客户端){
返回一个新的Retrofit.Builder()
.baseUrl(\”https://example.com\”)
。建造()
.create(LoginService.class);
}
}
@单例
@Component(模块=NetworkModule.class)
公共接口应用程序组件{
UserRepository userRepository();
无效注入001(MainActivity活动);
}
@Module 模块以与@Inject 不同的方式提供对象实例。在@Module中,@Provides方法的返回值是依赖实例,参数是进一步依赖的对象。此外,您需要使用@Component 参数来应用该模块。
到目前为止我们构建的依赖关系图如下所示:
3.5 @Subcomponent
@Subcomponent:子组件的概念允许您定义更细粒度的范围。
子组件是继承并扩展其父组件的对象图的组件。子组件中的对象可以依赖于父组件提供的对象,但父组件不能依赖于子组件所依赖的对象。 (这是一个简单的包含关系,对吧?)
让我们继续看一个简单的例子。假设您有一个依赖于LoginModel 的登录模块LoginActivity。我们的要求是定义一个子组件,其生命周期仅存在于单次登录过程中。 3.2节提到,Activity不能使用构造函数注入,因此LoginActivity使用@Inject字段注入语法。
@子组件
公共接口登录组件{
无效注入(LoginActivity 活动);
}
然而,以这种方式定义的LoginComponent实际上不能称为特定组件的子组件,因此必须添加额外的声明。
@Module(子组件=LoginComponent.class)
公共类子组件模块{
}
@Component(模块={NetworkModule.class,SubComponentsModule.class})
@单例
公共接口应用程序组件{
UserRepository userRepository();
LoginComponent.Factory LoginComponent();
}
@子组件
公共接口登录组件{
@子组件.工厂
接口工厂{
创建登录组件();
}
无效注入001(LoginActivity活动);
}
这里我们需要定义一个新的模块SubcomponentModule。您还需要在LoginComponent中定义一个子组件Factory,以便ApplicationComponent知道如何创建LoginComponent的实例。
LoginComponent 现在已声明。为了使LoginComponent 保持与LoginActivity 相同的生命周期,您必须在LoginActivity 中创建一个LoginComponent 实例并维护一个引用。
公共类LoginActivity 扩展Activity {
1.维护子组件的引用,保证生命周期相同
登录组件登录组件;
2.@Inject字段注入
@注入
登录视图模型登录视图模型;
@覆盖
protected void onCreate(bundle 保存实例状态) {
super.onCreate(savedInstanceState);
3.创建子组件实例
登录组件=((MyApplication) getApplicationContext())
.appComponent.loginComponent().create();
4、注射
登录组件.注入(this);
……
}
}
步骤4 初始化loginViewModel 字段。这里有一些地方需要特别注意。如果我重复地将LoginViewModel插入LoginActivity的Fragment中,它是一个对象吗?
@子组件
公共接口登录组件{
@子组件.工厂
接口工厂{
创建登录组件();
}
无效注入001(登录活动登录活动);
void Inject002(LoginUsernameFragment 片段);
}
这必须是一个单独的对象,因为我们没有使用3.3 节中描述的@Singleton/@Scope 作用域注释。接下来,添加范围注释。
@范围
@Retention(RetentionPolicy.RUNTIME)
公共@interface活动范围{}
@ActivityScope
@子组件
公私
接口LoginComponent { … }
最后
多年来,编辑了解到,大多数入门级和中级Android工程师想要提高自己,往往必须自己探索和成长,这是不系统的、短暂的,我了解到这并没有帮助。
因此,我们收集整理了《2024年Android移动开发全套学习资料》册。最初的目的也很简单。换句话说,我们想帮助那些想学习、提高自己,但不知道从哪里开始的朋友。
由于文件较多,这里仅展示部分目录截图。这套完整的内容包括对领先制造商的采访、学习笔记、源代码讲义、实践项目、概述路线、电子书和教学视频。它将继续更新。
一个人可以走得快,但一群人可以走得更远。无论是IT行业的资深人士还是对IT行业感兴趣的新人。
欢迎任何人加入我们的圈子(技术交流、学习资源、职场投诉、大公司内部推荐、面试指导),让我们一起学习、成长!
如果您需要此信息,请从第 栏中获取。
因此,我们收集整理了《2024年Android移动开发全套学习资料》册。最初的目的也很简单。换句话说,我们想帮助那些想学习、提高自己,但不知道从哪里开始的朋友。
[外部链接图像正在传输.(img-NSusXASJ-1719166894494)]
[正在传输外部链接图像.(img-RmcTmKgM-1719166894495)]
[外部链接图片正在传输中.(img-Axpbf2r6-1719166894495)]
[外部链接图像正在传输.(img-GFt474aJ-1719166894496)]
由于文件较多,这里仅展示部分目录截图。这套完整的内容包括对领先制造商的采访、学习笔记、源代码讲义、实践项目、概述路线、电子书和教学视频。它将继续更新。
一个人可以走得快,但一群人可以走得更远。无论是IT行业的资深人士还是对IT行业感兴趣的新人。
欢迎任何人加入我们的圈子(技术交流、学习资源、职场投诉、大公司内部推荐、面试指导),让我们一起学习、成长!
如果您需要此信息,请从第 栏中获取。
#上面关于Android_玩转Dagger2到Hilt的依赖注入(一)的相关内容摘自网络,供大家参考。相关信息请参见官方公告。
原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/91870.html