




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、依赖注入利器 - Dagger 概述在开发过程中,为了实现解耦,我们经常使用依赖注入,常见的依赖注入方式有:构造方法注入:在构造方法中把依赖作为参数传递进去setter方法注入:添加setter方法,把依赖传递进去接口注入:把注入方法抽到一个接口中,然后实现该接口,把依赖传递进去下面用一个小栗子来说明三种方式的用法:public class PersonService implements DependencyInjecter private PersonDao personDao; / 构造方法注入 public PersonService(PersonDao personDao) this
2、.personDao = personDao; / setter方法注入 public void setPersonDao(PersonDao personDao) this.personDao = personDao; / 接口注入:实现DependencyInjecter接口 Override public void injectPersonDao(PersonDao personDao) this.personDao = personDao; . . 我们来看下使用一般的依赖注入方法时,代码会是怎么样的:public class MainActivity extends AppCompa
3、tActivity private PersonService mService; Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); / 创建PersonService的依赖:personDao PersonDao personDao = new PersonDaoImpl(); / 通过构造方法注入依赖 mService = new PersonService(person
4、Dao); 看起来还好是吧?但现实情况下,依赖情况往往是比较复杂的,比如很可能我们的依赖关系如下图:PersonDaoImpl依赖类A,类A依赖B,B依赖C和D在这种情况下,我们就要写出下面这样的代码了:public class MainActivity extends AppCompatActivity private PersonService mService; Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.l
5、ayout.activity_main); / 创建依赖D D d = new D(); / 创建依赖C C c = new C(); / 创建依赖B B b = new B(c, d); / 创建依赖A A a = new A(b); / 创建PersonService的依赖:personDao PersonDao personDao = new PersonDaoImpl(a); / 通过构造方法注入依赖 mService = new PersonService(personDao); MainActivity只是想使用PersonService而已,却不得不关注PersonService
6、的依赖是什么、PersonDaoImpl依赖的依赖是什么,需要把整个依赖关系搞清楚才能使用PersonService。而且还有一个不好的地方,一旦依赖关系变更了,比如A不再依赖B了,那么就得修改所有创建A的地方。那么,有没有更好的方式呢?Dagger就是为此而生的,让我们看看使用Dagger后,MainActivity会变成什么模样:public class MainActivity extends AppCompatActivity Inject PersonService mService; Override protected void onCreate(Bundle savedInst
7、anceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); / Dagger注入,读者现在可先不关注里面做了什么操作 DaggerPersonServiceComponent.create().inject(MainActivity.this); / 注意,mService已经是非空了,可以正常使用 mService.update(1, HansChen); . 之前创建A、B、C、D、PersonDaoImpl等依赖的代码全不见了,只需要调用一个注入语句就全搞定了。调用了注入语句
8、之后,mService就可以正常使用了,是不是挺方便呢?至于这句注入语句具体干了什么,读者现在可以先不管,后面会有详细说明,这里只是做一个使用演示而已。我们大概猜想一下,在MainActivity使用PersonService需要做哪些?分析生成依赖关系图,如PersonServicePersonDaoImplABC&D根据依赖关系图获取相关依赖,比如依次创建D、C、B、A、PersonDaoImpl、PersonService的实例把生成的PersonService实例传递给MainActivity的mService成员变量其实Dagger做的也就是上面这些事情了,接下来就让我们真正开始学习
9、Dagger吧声明需要注入的对象首先我们应该用javax.inject.Inject去注解需要被自动注入的对象,Inject是Java标准的依赖注入(JSR-330)注解。比如下面栗子中,需要注入的对象就是MainActivity的mService。这里有个要注意的地方,被Inject注解的变量不能用private修饰public class MainActivity extends AppCompatActivity / 注意,不能被private修饰 Inject PersonService mService; .如何实例化出依赖?在执行依赖注入的时候,Dagger会查找Inject注解的
10、成员变量,并尝试获取该类的实例,Dagger最直接的方式就是直接new出相应的对象了。实例化对象的时候,会调用对象的构造方法,但假如有多个构造方法,具体用哪个构造方法来实例化对象?Dagger肯定是不会帮我们“擅自做主”的,用哪个构造方法来实例化对象应该是由我们做主的,所以我们需要给相应的构造方法添加Inject注解。当Dagger需要实例化该对象的时候,会调用Inject注解的构造方法来实例化对象:public class PersonService implements DependencyInjecter private PersonDao personDao; / 用Inject注解,
11、相当于告诉Dagger需要实例化PersonService的时候,请调用这个构造方法 Inject public PersonService(PersonDao personDao) this.personDao = personDao; .聪明的你应该发现了,调用PersonService的构造方法需要传入PersonDao实例,所以要实例化PersonService,必须先要实例化PersonDao,Dagger会帮我们自动分析出这个依赖关系,并把它添加到依赖关系图里面!Dagger会尝试先去实例化一个PersonDao,如果PersonDao又依赖于另外一个对象A,那么就先尝试去实例化A
12、以此类推,是不是很像递归?当所有依赖都被实例化出来之后,我们的PersonService当然也被构造出来了。问题又来了,如果PersonDao是一个接口呢?Dagger怎么知道这个接口应该怎么实现?答案是不知道的,那么Dagger怎么实例化出一个接口出来?这个就是Module存在的意义之一了。关于Module的讲解我们会在后面详细说明,我们现在只要知道,Module里面会定义一些方法,这些方法会返回我们的依赖,就像:Modulepublic class PersonServiceModule /* * 提供PersonDao接口实例 */ Provides PersonDao provideP
13、ersonDao(A a) return new PersonDaoImpl(a); Dagger根据需求获取一个实例的时候,并不总是通过new出来的,它会优先查找Module 中是否有返回相应实例的方法,如果有,就调用Module的方法来获取实例。比如你用Inject注解了一个成员变量,Dagger会查找Module中是否有用Provides注解的,返回该类实例的方法,有的话就会调用provide方法来获得实例,然后注入,如果没有的话Dagger就会尝试new出一个实例。就像我们现在这个栗子,PersonService依赖于PersonDao接口,Dagger不能直接为我们new出一个接口,
14、但我们可以提供一个Module,在Module中定义一个返回PersonDao接口实例的方法,这样,Dagger就可以解决实例化PersonDao的问题了。我们再梳理一下流程,如果我们用Inject注解了一个成员变量,并调用注入代码之后,Dagger会这样处理:查找Module中是否有用Provides注解的,返回该类实例的方法如果有,就调用那个provide方法来获得实例,然后注入如果没有,就尝试调用相应的类中被Inject注解的构造方法new出一个实例,然后注入如果没有一个构造方法被Inject注解,Dagger会因不能满足依赖而出错所以假如一个变量被Inject注解,要么在Module中
15、提供provide方法获取实例,要么该类提供一个被Inject注解的构造方法,否则Dagger会出错Module的使用一般而言,Dagger会获取所有依赖的实例,比如当需要一个TestBean的时候,会通过new TestBean()创建实例并注入到类中。但是,以下情况会就不好处理了:需要生成的是一个接口,而Dagger不能直接实例化接口不能在第三方库的类中添加注解可配置的对象必须是配置的为了解决以上问题,我们需要定义一个被Module注解的类,在里面定义用Provides注解的方法。用该方法返回所需的实例。Modulepublic class PersonServiceModule Prov
16、ides D provideD() return new D(); Provides C provideC() return new C(); Provides B provideB(C c, D d) return new B(c, d); Provides A provideA(B b) return new A(b); /* * 提供PersonDao实例 */ Provides PersonDao providePersonDao(A a) return new PersonDaoImpl(a); 就像providePersonDao返回了PersonDao接口实例,Dagger虽然不
17、能直接实例化出PersonDao接口,但却可以调用Module的providePersonDao方法来获得一个实例。providePersonDao方法需要传入A的实例,那么这里也构成了一个依赖关系图。Dagger会先获取A的实例,然后把实例传递给providePersonDao方法。Component的使用到目前为止,我们虽然知道了:Dagger怎么获取实例: 从Module的provide方法中获取通过Inject注解的构造方法new出新的实例Dagger会推导provide方法和构造方法的参数,形成依赖图,并“满足”我们依赖图的需求,获取依赖的实例看样子需要注入的依赖可以获取了,但是不是
18、总觉得还有点“零碎”,整个流程还没连贯起来?比如,Module既然是一个类,生成依赖图的时候,怎么知道跟哪个Module挂钩?即使最后生成了需要的实例,注入的“目的地”是哪里?怎么才能把它注入到“目的地”?残缺的这部分功能,正是Component提供的,Component起到了一个桥梁的作用,贯通Module和注入目标。我们来看看最开始那个例子,我们是怎么进行依赖注入的:public class MainActivity extends AppCompatActivity Inject PersonService mService; Override protected void onCrea
19、te(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PersonServiceComponent component = DaggerPersonServiceComponent.builder() .personServiceModule(new PersonServiceModule() .build(); / 注入,所有Inject注解的成员变量都会同时注入 component.inject(MainActivity.this);
20、 / 通过component获取实例,注意,这里只是演示用法,其实mService在component.inject的时候已经完成了注入 mService = component.getPersonService(); 这个DaggerPersonServiceComponent是什么鬼?DaggerPersonServiceComponent其实是Dagger为我们自动生成的类,它实现了一个Component接口(这个接口是需要我们自己写的),我们来看下它实现的接口长什么样子:/* * 指定PersonServiceModule,当需要获取某实例的时候,会查找PersonServiceMod
21、ule中是否有返回相应类型的方法,有的话就通过该方法获得实例 * * author HansChen */Component(modules = PersonServiceModule.class)public interface PersonServiceComponent /* * 查找activity中被Inject注解的成员变量,并尝试获取相应的实例,把实例赋给activity的成员变量 * 注意函数格式:返回值为空、带有一个参数 */ void inject(MainActivity activity); /* * Dagger会尝试从Module中获取PersonService实例
22、,如果Module中不能获取对应实例,则通过PersonService的构造方法new出一个实例 * 注意函数格式:参数为空,返回值非空 */ PersonService getPersonService();这个接口被Component注解修饰,它里面可以定义3种类型的方法:返回值为空,有一个参数:查找参数中被Inject注解的成员变量,并尝试获取相应的实例(通过Module的provide方法或Inject注解的构造方法new出新的实例),把实例赋给参数的成员变量返回值非空,参数为空:获取相应实例并返回返回值是Component,参数是Moduld,通过该方法可以创建SubComponen
23、t实例既然获取实例的时候,有可能用到Module,那么就必须为这个Component指定使用的Module是什么。具体做法就是在Component注解中指定modules。 定义好Component之后,Dagger会自动帮我们生成实现类,这就是Dagger强大的地方!生成的类名格式是:Dagger+Component名。 Component提供了2种方法,一个是注入式方法,一个是获取实例方法。具体用什么方法,就看个人需求了。一个Component其实也对应了一个依赖图,因为Component使用哪个Module是确定不变的,依赖关系无非也就是跟Module和类的定义有关。一旦这些都确定下来了
24、,在这个Component范围内,依赖关系也就被确定下来了。额外再说一点,在Dagger1中,Component的功能是由ObjectGraph实现的,Component是用来代替它的。Component定义好之后,build一下工程,Dagger就会自动为我们生成实现类了,就可以使用自动生成的实现类来进行依赖注入了。到现在为止,我们已经通过Dagger完成了依赖注入。可能看起来比正常方法麻烦得多,但是Dagger框架可以让依赖的注入和配置独立于组件之外,它帮助你专注在那些重要的功能类上。通过声明依赖关系和指定规则构建整个应用程序。熟悉完Dagger基本的使用之后,接下来我们来讲解一些稍微高级
25、一点的用法:Dagger的进阶使用Components之间的关系在Dagger中,Component之间可以有两种关系:Subcomponents和Component dependencies。他们有什么作用呢?比如在我们应用中,经常会有一些依赖我们在各个界面都使用得到,比如操作数据库、比如网络请求。假设我们有个ServerApi的接口,在页面A、B、C都使用到了,那么我们要在页面A、B、C的Component里面都能获取到ServerApi的实例,但显然,获取ServerApi实例的方法都是一样的,我们不想写重复的代码。于是我们可定义一个ApplicationComponent,在里面返回S
26、erverApi实例,通过Component之间的关系便可以共享ApplicationComponent提供的依赖图。下面通过Android中的一个小栗子来说明Subcomponents和Component dependencies如何使用dependencies先说明下各个模块之间的关系 首先,我们定义一个ApplicationComponent,它定义了一个方法,通过它来获得ServerApi实例。ApplicationComponent还关联了ApplicationModule,这个Module是ServerApi实例的提供者,注意,这个Moduld还可以返回Context实例Compo
27、nent(modules = ApplicationModule.class)public interface ApplicationComponent ServerApi getServerApi();Modulepublic class ApplicationModule private final Context mAppContext; ApplicationModule(Context context) mAppContext = context.getApplicationContext(); Provides Context provideAppContext() return
28、mAppContext; Provides ServerApi provideServerApi(Context context) return new ServerApiImpl(context); public class DemoApplication extends Application private ApplicationComponent mAppComponent; Override public void onCreate() super.onCreate(); mAppComponent = DaggerApplicationComponent.builder().app
29、licationModule(new ApplicationModule(this).build(); public ApplicationComponent getAppComponent() return mAppComponent; / 注意,这里有个dependencies声明Component(pendencies = ApplicationComponent.class, modules = MainPresenterModule.class)public interface MainPresenterComponent MainPresenter getMainPresenter
30、();Modulepublic class MainPresenterModule private MainView mMainView; public MainPresenterModule(MainView mainView) this.mMainView = mainView; Provides MainView provideMainView() return mMainView; public class MainPresenter private MainView mMainView; private ServerApi mServerApi; Inject public Main
31、Presenter(MainView mainView, ServerApi serverApi) this.mMainView = mainView; this.mServerApi = serverApi; Component中getMainPresenter的目的很简单,就是返回MainPresenter,而MainPresenter又依赖MainView和ServerApi,MainView还好说,在MainPresenterModule中有provide方法,但是ServerApi呢?就像上面说的那样,如果我们在这个Moduld中也添加相应的provide方法,那真是太麻烦了(当然,
32、这样做完全是可以实现的),所以我们依赖了ApplicationComponent,通过dependencies,在被依赖的Component暴露的对象,在子Component中是可见的。这个是什么意思呢?意思有两个:被依赖Component接口暴露的对象,可以添加到依赖者的依赖图中Component接口没有暴露的对象,依赖者是不可见的对于第一点应该比较好理解,就像这个栗子,MainPresenterComponent生成MainPresenter需要ServerApi,而ApplicationComponent中有接口暴露了ServerApi,所以MainPresenterComponent可
33、以获得ServerApi 对于第二点,假设MainPresenter还需要传入一个Context对象,我们注意到,ApplicationModule是可以提供Context的,那MainPresenterComponent能不能通过ApplicationComponent获取Context实例?答案是不行的,因为ApplicationComponent没有暴露这个对象。想要获取Context,除非ApplicationComponent中再添加一个getContext的方法。他们之间的关系可以用下图描述:Subcomponents让我们对上面的栗子改造改造: 去除MainPresenterCo
34、mponent的Component注解,改为Subcomponent:Subcomponent(modules = MainPresenterModule.class)public interface MainPresenterComponent void inject(MainActivity activity); MainPresenter getMainPresenter();在ApplicationComponent中新增plus方法(名字可随意取),返回值为MainPresenterComponent,参数为MainPresenterModule:Component(modules
35、= ApplicationModule.class)public interface ApplicationComponent MainPresenterComponent plus(MainPresenterModule module);这样,就构建了一个ApplicationComponent的子图:MainPresenterComponent。子图和dependencies的区别就是,子图可以范围父图所有的依赖,也就是说,子图需要的依赖,不再需要在父Component中暴露任何对象,可以直接通过父图的Moduld提供!他们的关系变为了:ScopesScopes可是非常的有用,Dagger
36、2可以通过自定义注解限定注解作用域。Singleton是被Dagger预先定义的作用域注解。没有指定作用域的Provides方法将会在每次注入的时候都创建新的对象一个没有scope的component不可以依赖一个有scope的组件component子组件和父组件的scope不能相同Module中provide方法的scope需要与Component的scope一致我们通常的ApplicationComponent都会使用Singleton注解,也就会是说我们如果自定义component必须有自己的scope。读者到这里,可能还不能理解Scopes的作用,我们先来看下默认提供的Singlton
37、到底有什么作用,然后再讨论Scopes的意义:SingltonSingletons是java提供的一个scope,我们来看看Singletons能做什么事情。 为Provides注释的方法或可注入的类添加添加注解Singlton,构建的这个对象图表将使用唯一的对象实例,比如我们有个ServerApi 方法一:用Singleton注解类:Singletonpublic class ServerApi Inject public ServerApi() public boolean login(String username, String password) return HansChen.eq
38、uals(username) & 123456.equals(password); 方法二:用Singleton注解Module的provide方法:Modulepublic class ApplicationModule Singleton Provides ServerApi provideServerApi() return new ServerApi(); 然后我们有个Component:SingletonComponent(modules = ApplicationModule.class)public interface ApplicationComponent ServerApi
39、 getServerApi();然后执行依赖注入:public class MainActivity extends AppCompatActivity Inject ServerApi mService; Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent component = DaggerApplicationComponent.
40、create(); Log.d(Hans, component.getServerApi().toString(); Log.d(Hans, component.getServerApi().toString(); Log.d(Hans, component.getServerApi().toString(); 使用了以上两种方法的任意一种,我们都会发现,通过component.getServerApi()获得的实例都是同一个实例。不过要注意一点的是,如果类用Singleton注解了,但Module中又存在一个provide方法是提供该类实例的,但provide方法没有用Singleton注解
41、,那么Component中获取该实例就不是单例的,因为会优先查找Module的方法。 这个单例是相对于同一个Component而言的,不同的Component获取到的实例将会是不一样的。自定义Scope既然一个没有scope的component不可以依赖一个有scope的组件component,那么我们必然需要自定义scope来去注解自己的Component了,定义方法如下:DocumentedScopeRetention(RetentionPolicy.RUNTIME)public interface FragmentScoped 定义出来的FragmentScoped在使用上和Single
42、ton是一样的,那它和Singleton除了是不一样的注解之外,还有什么不一样呢?答案是没有!我们自定义的scope和Singleton并没有任何不一样,不会因为Singleton是java自带的注解就会有什么区别。那么,这个scope的设定是为了什么呢?scope的作用scope除了修饰provide方法可以让我们获得在同一个Component实例范围内的单例之外,主要的作用就是对Component和Moduld的分层管理以及依赖逻辑的可读性。 这里借用一个网络上的图片说明:ApplicationComponent一般会用singleton注解,相对的,它的Module中provide方法也
43、只能用singleton注解。UserComponent是用UserSCope能直接使用ApplicationModule吗?不能!因为他俩的scope不一致,这就是这个设定带来的好处,防止不同层级的组件混乱。另外,因为有了scope的存在,各种组件的作用和生命周期也变得可读起来了Lazy注入有时可能会需要延迟获取一个实例。对任何绑定的 T,可以构建一个 Lazy 来延迟实例化直至第一次调用 Lazy 的 get() 方法。注入之后,第一次get的时会实例化出 T,之后的调用都会获取相同的实例。public class MainActivity extends AppCompatActivit
44、y implements MainView / 懒加载 Inject Lazy mPresenter; Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MainPresenterComponent component = DaggerMainPresenterComponent.builder() .mainPresenterModule(new MainPresenter
45、Module(this) .applicationComponent(DemoApplication) getApplication().getAppComponent() .build(); component.inject(this); Log.d(Hans, mPresenter.get().toString(); / 实例化MainPresenter Log.d(Hans, mPresenter.get().toString(); / 跟上次获取的实例是同一个实例 Provider注入跟Lazy注入不一样的是,有时候我们希望每次调用get的时候,获取到的实例都是不一样的,这时候可以用P
46、rovider注入public class MainActivity extends AppCompatActivity implements MainView / Provider Inject Provider mPresenter; Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MainPresenterComponent component = DaggerMai
47、nPresenterComponent.builder() .mainPresenterModule(new MainPresenterModule(this) .applicationComponent(DemoApplication) getApplication().getAppComponent() .build(); component.inject(this); Log.d(Hans, mPresenter.get().toString(); / 实例化MainPresenter Log.d(Hans, mPresenter.get().toString(); / 获取新的MainPresenter实例 Qualifiers注入到目前为止,我们的demo里,Moduld的provide返回的对象都是不一样的,但是下面这种情况就不好处理了:Modulepublic class ApplicationModule . / 返回ServerApi实例 Provides ServerApi provideServerApiA(Context context) return ew ServerApi
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 新闻记者职业资格2024年笔试题库答案分析
- 2024年山西省古县事业单位公开招聘医疗卫生岗笔试题带答案
- 《机器学习技术应用》课件-pro1-2-1校园消费数据的读取与查看
- 《移动通信技术》课件-主题5 移动通信的编码技术
- 先天性鼻咽部狭窄及闭锁的临床护理
- OPT美肤的临床护理
- 手外伤的临床护理
- 2025【合同范本】简化版租约协议示例
- 企业招聘2025内蒙古方鼎金荣集团派驻联通数字科技有限公司内蒙古分公司招聘53人笔试参考题库附带答案详解
- 《2025合同违约金预先扣除的法律后果》
- 江苏省苏州市2024-2025学年统编版(2024)第二学期七年级语文期中模拟卷(二)(含答案)
- 探讨“职继融通”研究的内涵、框架及其未来发展趋势
- 《建筑与市政工程施工现场临时用电安全技术标准JGJT46-2024》知识培训
- 医保飞行检查培训
- 2025年中考英语押题预测卷(苏州专用)(原卷版)
- CT引导下经皮胃造瘘术专家共识(2025版)解读课件
- 2024年美容师考试相关法律法规解读试题及答案
- 2025年新版劳动合同范本
- 高压预试施工方案
- 事故隐患内部报告奖励制度
- 2025年河南焦作市孟州市开创人才服务有限公司招聘笔试参考题库附带答案详解
评论
0/150
提交评论