带你解析Dagger2

2024-06-24 05:38
文章标签 解析 dagger2

本文主要是介绍带你解析Dagger2,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. Dagger2简介:

Dagger2是Dagger1的分支,由谷歌公司接手开发,目前的版本是2.0。Dagger2是受到AutoValue项目的启发。 刚开始,Dagger2解决问题的基本思想是:利用生成和写的代码混合达到看似所有的产生和提供依赖的代码都是手写的样子。

Dagger2具有以下好处:
1) 依赖的注入和配置独立于组件之外,注入的对象在一个独立、不耦合的地方初始化,这样在改变注入对象时,我们只需要修改对象的实现方法,而不用大改代码库。
2) 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。
3) app中的组件不需要知道有关实例创建和生命周期的任何事情,这些由我们的依赖注入框架管理的。
dagger2这样的依赖注入框架对MVP架构来说,是最好的解耦工具,可以进一步降低modle-view-presenter之间的耦合度。

2. 什么是依赖注入

如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。例如下面类 Human 中用到一个 Father 对象,我们就说类 Human 对类 Father 有一个依赖。

public class Human {...Father father;...public Human() {father = new Father();}
}

那什么又是依赖注入呢,依赖注入就是非自己主动初始化依赖,而通过外部来传入依赖的方式,简单来说就是不使用 new 来创建依赖对象。使用 Dagger2 创建依赖对象,我们就不用手动初始化了。个人认为 Dagger2 和 MVP 架构是比较不错的搭配,Activity 依赖的 Presenter 可以使用该DI框架直接生成,实现解耦,简单的使用方式如下:

public class MainActivity extends BaseActivity {@InjectMainActivityPresenter presenter;...  }

上面这些主要是对DI框架有一个初步全局的了解,下面来看看Dagger2的基本内容。Dagger2 通过注解来生成代码,定义不同的角色,主要的注解有:@Inject、@Module 、@Component 、@Provides 、@Scope 、@SubComponent 等。

3. Dagger2注解解释:
接下来介绍Dagger2中的每一个概念:

@Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。
@Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)。

@Provide: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。

@Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。 Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的@Modules组成该组件,如 果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的modules知道依赖的范围。

@Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没 必要让每个对象都去了解如何管理他们的实例。在scope的例子中,我们用自定义的@PerActivity注解一个类,所以这个对象存活时间就和 activity的一样。简单来说就是我们可以定义所有范围的粒度(@PerFragment, @PerUser, 等等)。

Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。

4. 如何使用Dagger2

首先还是要在我们的build.gradle文件中如下配置:

apply plugin: 'com.neenbedankt.android-apt'buildscript {repositories {jcenter()}dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'}
}android {...
}dependencies {apt 'com.google.dagger:dagger-compiler:2.0'compile 'com.google.dagger:dagger:2.0'...
}

我们添加了编译和运行库,还有必不可少的apt插件,没有这插件,dagger可能不会正常工作,特别是在Android studio中。

Application Component: 生命周期跟Application一样的组件。可注入到AndroidApplication和BaseActivity中类中。

@Singleton // Constraints this component to one-per-application or unscoped bindings.
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {void inject(BaseActivity baseActivity);//Exposed to sub-graphs.Context context();ThreadExecutor threadExecutor();PostExecutionThread postExecutionThread();UserRepository userRepository();
}

使用了@Singleton注解,使其保证唯一性。也许你会问为什么我要将context和其他成员暴露出去。这正是Dagger中 components工作的重要性质:如果你不想把modules的类型暴露出来,那么你就只能显示地使用它们。在这个例子中,我把这些元素暴露给子图, 如果你把他们删掉,编译的时候就会报错。

Application Module: 这里提供了Application Component里的需要注入的对象。这也是为什么@Provide注解的方法要用@Singleton限定。

@Module
public class ApplicationModule {private final AndroidApplication application;public ApplicationModule(AndroidApplication application) {this.application = application;}@Provides @Singleton Context provideApplicationContext() {return this.application;}@Provides @Singleton Navigator provideNavigator() {return new Navigator();}@Provides @Singleton ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) {return jobExecutor;}@Provides @Singleton PostExecutionThread providePostExecutionThread(UIThread uiThread) {return uiThread;}@Provides @Singleton UserCache provideUserCache(UserCacheImpl userCache) {return userCache;}@Provides @Singleton UserRepository provideUserRepository(UserDataRepository userDataRepository) {return userDataRepository;}
}

Activity Component: 生命周期跟Activity一样的组件。

@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {//Exposed to sub-graphs.Activity activity();
}

@PerActivity是一个自定义的范围注解,作用是允许对象被记录在正确的组件中,当然这些对象的生命周期应该遵循activity的生命周期。

Activity Module: 在对象图中,这个module把activity暴露给相关联的类。比如在fragment中使用activity的context。

@Module
public class ActivityModule {private final Activity activity;public ActivityModule(Activity activity) {this.activity = activity;}@Provides @PerActivity Activity activity() {return this.activity;}
}

User Component: 继承于ActivityComponent的组件,并用@PerActivity注解。我通常会在注入用户相关的fragment中使用。因为 ActivityModule把activity暴露给图了,所以在任何需要一个activity的context的时候,Dagger都可以提供注入, 没必要再在子modules中定义了。

@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = {ActivityModule.class, UserModule.class})
public interface UserComponent extends ActivityComponent {void inject(UserListFragment userListFragment);void inject(UserDetailsFragment userDetailsFragment);
}

User Module: 提供跟用户相关的实例。基于我们的例子,它可以提供用户用例。

@Module
public class UserModule {@Provides @PerActivity GetUserListUseCase provideGetUserListUseCase(GetUserListUseCaseImpl getUserListUseCase) {return getUserListUseCase;}@Provides @PerActivity GetUserDetailsUseCase provideGetUserDetailsUseCase(GetUserDetailsUseCaseImpl getUserDetailsUseCase) {return getUserDetailsUseCase;}
}

整合:
Dagger给了我们一堆选择用来注入依赖:

构造方法注入:在类的构造方法前面注释@Inject
成员变量注入:在类的成员变量(非私有)前面注释@Inject
函数方法注入:在函数前面注释@Inject

这个顺序是Dagger建议使用的,因为在运行的过程中,总会有一些奇怪的问题甚至是空指针,这也意味着你的依赖在对象创建的时候可能还没有初始化 完成。这在Android的activity或者fragment中使用成员变量注入会经常遇到,因为我们没有在它们的构造方法中使用。

看一下我们是如何在BaseActivity中注入一个成员变量。在这个例子中,我们注入了一个叫Navigator的类,它是我们应用中负责管理导航的类。

public abstract class BaseActivity extends Activity {@Inject Navigator navigator;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.getApplicationComponent().inject(this);}protected ApplicationComponent getApplicationComponent() {return ((AndroidApplication)getApplication()).getApplicationComponent();}protected ActivityModule getActivityModule() {return new ActivityModule(this);}
}

Navigator类是成员变量注入的,由ApplicationModule里面@Provide注解显示提供的。最终我们初始化 component然后调用inject()方法注入成员变量。我们通过在Activity的onCreate()方法中调用 getApplicationComponent(),完成这些操作。getApplicationComponent()方法放在这儿是为了复用性,它 的主要作用是为了获取实例化的ApplicationComponent对象。

Fragment的presenter中我们也做了同样的事情,这儿的获取方法有一点不一样,因为问我们使用的是per-activity范围限 定的component。所以我们注入到UserDetailsFragment中的UserComponent其实是驻留在 UserDetailsActivity中的。

private UserComponent userComponent;

我们必须在activity的onCreate()方法中用下面的方式初始化。

private void initializeInjector() {this.userComponent = DaggerUserComponent.builder().applicationComponent(getApplicationComponent()).activityModule(getActivityModule()).build();
}

Dagger会处理我们的注解,为components生成实现并重命名加上“Dagger”前缀。因为这个是一个组合的component,所以在构建 的时候,我们必须把所有的依赖的传进去(components和modules)。现在我们的component已经准备好了,接着为了可以满足 fragment的依赖需求,我们写一个获取方法:

@Override public UserComponent getComponent() {return userComponent;
}

我们现在可以利用get方法获取创建的component,然后调用inject()方法将Fragment作为参数传进去,这样就完成了绑定UserDetailsFragment依赖。

@Override public void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);this.getComponent.inject(this);
}

public interface HasComponent<C> {C getComponent();
}

因此,客户端(例如fragment)可以获取并且使用component(来自activity):

@SuppressWarnings("unchecked")
protected <C> C getComponent(Class<C> componentType) {return componentType.cast(((HasComponent<C>)getActivity()).getComponent());
}

这儿使用了强制转换,不论这个客户端不能获取到能用的component,但是至少很快就会失败。


@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerApplicationComponent implements ApplicationComponent {private Provider<Navigator> provideNavigatorProvider;private MembersInjector<BaseActivity> baseActivityMembersInjector;private DaggerApplicationComponent(Builder builder) {  assert builder != null;initialize(builder);}public static Builder builder() {  return new Builder();}private void initialize(final Builder builder) {  this.provideNavigatorProvider = ScopedProvider.create(ApplicationModule_ProvideNavigatorFactory.create(builder.applicationModule));this.baseActivityMembersInjector = BaseActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideNavigatorProvider);}@Overridepublic void inject(BaseActivity baseActivity) {  baseActivityMembersInjector.injectMembers(baseActivity);}public static final class Builder {private ApplicationModule applicationModule;private Builder() {  }public ApplicationComponent build() {  if (applicationModule == null) {throw new IllegalStateException("applicationModule must be set");}return new DaggerApplicationComponent(this);}public Builder applicationModule(ApplicationModule applicationModule) {  if (applicationModule == null) {throw new NullPointerException("applicationModule");}this.applicationModule = applicationModule;return this;}}
}

有两个重点需要注意。第一个:由于我们要将依赖注入到activity中,所以会得到一个注入这个比成员的注入器(由Dagger生成的BaseActivity_MembersInjector):

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class BaseActivity_MembersInjector implements MembersInjector<BaseActivity> {private final MembersInjector<Activity> supertypeInjector;private final Provider<Navigator> navigatorProvider;public BaseActivity_MembersInjector(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) {  assert supertypeInjector != null;this.supertypeInjector = supertypeInjector;assert navigatorProvider != null;this.navigatorProvider = navigatorProvider;}@Overridepublic void injectMembers(BaseActivity instance) {  if (instance == null) {throw new NullPointerException("Cannot inject members into a null reference");}supertypeInjector.injectMembers(instance);instance.navigator = navigatorProvider.get();}public static MembersInjector<BaseActivity> create(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) {  return new BaseActivity_MembersInjector(supertypeInjector, navigatorProvider);}
}

这个注入器一般都会为所有activity的注入成员提供依赖,只要我们一调用inject()方法,就可以获取需要的字段和依赖。
第二个重点:关于我们的DaggerApplicationComponent类,我们有一个Provider,它不仅仅是一个提供实例的接口,它还是被ScopedProvider构造出来的,可以记录创建实例的范围。
Dagger还会为我们的Navigator类生成一个名叫ApplicationModule_ProvideNavigatorFactory的工厂,这个工厂可以传递上面提到的范围参数然后得到这个范围内的类的实例。

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ApplicationModule_ProvideNavigatorFactory implements Factory<Navigator> {private final ApplicationModule module;public ApplicationModule_ProvideNavigatorFactory(ApplicationModule module) {  assert module != null;this.module = module;}@Overridepublic Navigator get() {  Navigator provided = module.provideNavigator();if (provided == null) {throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");}return provided;}public static Factory<Navigator> create(ApplicationModule module) {  return new ApplicationModule_ProvideNavigatorFactory(module);}
}

这个类非常简单,它代表我们的ApplicationModule(包含@Provide方法)创建了Navigator类。
总之,上面的代码看起来就像是手敲出来的,而且非常好理解,便于调试。其余还有很多可以去探索,你们可以通过调试去看看Dagger如何完成依赖绑定的。

这篇关于带你解析Dagger2的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089314

相关文章

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

python解析HTML并提取span标签中的文本

《python解析HTML并提取span标签中的文本》在网页开发和数据抓取过程中,我们经常需要从HTML页面中提取信息,尤其是span元素中的文本,span标签是一个行内元素,通常用于包装一小段文本或... 目录一、安装相关依赖二、html 页面结构三、使用 BeautifulSoup javascript

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库