dagger2从入门到放弃-多模块项目下dagger的使用
在只有一个Module的Android Project中,dagger2的使用相对来说是比较顺畅的,但是事实上现在基本上不会存在只有一个Module的项目了。
一个最底层的Module,上层多个功能模块,再上层多个业务模块,再上层一个app模块去组织业务和功能,这是大部分android项目的结构,而在这种结构中dagger2的使用就会有一些不太顺手了
场景构建
先确定一下dagger需要在多模块中实现什么样的场景
- Base模块提供Gson对象
- Api模块依赖基础模块,提供OkHttp和Retrofit对象
- 应用模块依赖网络模块
- Gson、OkHttp和Retrofit都是拥有和Application相同生命周期的局部单例
这基本是比较简单的场景了
正常思路下的使用
在介绍Component的继承体系时提到过只有使用Component的dependencies属性指定父Component的方式才能在子Component指定父Component,这是唯一的从上到下的确定依赖关系的方式
很自然的会想到在每个模块拥有一个Component,上层的Component依赖下层Component
BaseLib
- 写一个BaseAppComponent,指定提供Gson的Module
- BaseAppComponent暴露获取Gson的接口,因为上层Component指定依赖的话需要底层Componnet显式暴露可以提供的依赖的方法
- 在BaseApplication中创建并持有BaseAppComponent实例
这三步实现起来比较自然
ApiLib
- 写一个ApiComponent,指定依赖BaseAppComponent,指定提供OkHttp和Retrofit的Module
- 暴露提供OkHttp和Retrofit的方法
第三步问题来了,在什么时机去创建一个ApiComponent实例呢
?
作为一个处于中间层级的库,ApiLib并不适合拥有自己的Application类,BaseLib在下层访问不到ApiLib的代码,所以只能将ApiComponent的创建逻辑放到上层的App模块中
这样会造成本来ApiLib不需要上层去进行初始化,而现在必须依赖上层初始化才能正常使用。而且现在设定的场景是一种比较简单的情况,实际项目中的模块依赖会更加复杂,所有的模块的Component的初始化逻辑都放App模块中需要App模块的开发者了解所有模块Component的依赖关系,这不现实也不合理
不过有问题暂时先搁置,想个办法先跑起来
- 创建一个ApiManager单例,init方法传入BaseAppComponent实例来创建ApiComponent,在单例中持有ApiComponent,使用时从ApiManager中获取
App模块
- 写一个AppComponent,指定依赖ApiComponent
- 继承BaseApplication
- 初始化ApiManager,完成ApiComponent的创建
- 使用ApiComponent完成AppComponent的创建
还是没跑起来
按照上面的步骤应该可以正常运行才对,但是天不遂人愿,还是有地方姿势不对
之前的文章中提到过使用dependencies指定依赖的情况下,父Component需要显式暴露提供依赖的方法,而且不具有传递性,只能为下一级提供依赖而不能为下下级提供依赖
所以ApiComponent只暴露获取OkHttp和Retrofit的方法还不够,还需要暴露获取Gson的方法
添加了获取Gson的方法之后代码终于可以正常运行了
方案评价
通过上面的介绍,大家应该发现了这种方案在多模块的项目中dagger使用不是那么顺手
- 首先底层模块需要依赖最上层模块触发Component的创建,而且需要在当前模块中创建一个单例保存创建的Component来实现生命周期的管理,这是之前不需做的额外工作
- 上层模块需要了解下层所有的模块的Component的创建和依赖关系才能完成所有Component的创建,这样的要求对上层应用的开发者明显是不合理的,依赖一多明显是不可能实现的
- 如果上层Component需要用到底层的依赖,需要中间所有的Component显式声明获取该依赖的方法,这种情况下底层添加一个依赖需要多n个方法,反而增加了开发者的负担
- 这个方案中好像dagger.android/Multibindings也不是很好使
- 库的Component需要暴露提供依赖的方法上层才能用,而两个平级的库要同时向上暴露同一个基类元素的Map比如DispatchingAndroidInjector<Activity>好像行不通,编译会出现错误
优化
前两个痛点的问题在于Application的创建时机只有最上层的模块才知道,仔细分析代码发现事实上这个时机并不重要,主要目的是要给Component提供一个Application实例同时保持和Application相同的生命周期
- 提供Application实例
只需要在底层提供一个获取Application的静态方法,则库中根Component的创建就可以不需要依赖上层的Application了 - 保持和Application的生命周期
这个比较简单,用单例或者直接在类中用一个static变量保存Component实例都可以保持和Application相同的生命周期
这样修改之后,库的根Component的管理都变成了在库中完成,App模块就不需要关注下层库中Component的组织了;当然如果App模块也要用依赖注入的方式使用下层库中dagger提供的依赖,那还是需要处理自己得Component和要用到的库的Component的依赖关系,不过这样是用到哪个处理哪个,而不是之前的不管用不用都要处理所有依赖关系
第三个痛点仔细考虑了一下发现可以在底层的模块中的提供一个接口,暴露获取依赖的方法,上层Component只用继承这样的接口,而添加依赖只用在接口中增加方法,不用在每个Component中一个个添加
最后一个痛点暂时还没发现好的解决方式
上层Application中处理所有依赖
上面的方案经过优化其实基本可用了,但是dagger使用起来是能用注解的地方都用注解才好,不能跨库使用dagger.android还是有些麻烦的
所以对自己整体把控的项目,对dagger使用变成了在App模块的Component中进行所有依赖的组织
先看看怎么实现
对Module中providerXXX方法的Scope进行统一
之前是用多个Component去组织,所以需要多个Scope进行标记,现在是一个Component,所以只能使用一个Scope进行标记
在每个模块中定义一个Module,将Application级别的依赖include进去
这一步是为了简化App模块的工作,一个库的Application级别的Module自己去组织,App只需要包含一个Module,库中添加新的Module也不需要上层的改动
@Module(includes = {ApiServiceModule.class, OrmModule.class})
public class ApiCollectionModule {
}
将所有的库提供的Module包含到AppComponent中
@AppScope
@Component(modules = {AppModule.class,
BaseAppCollectionModule.class,
ApiCollectionModule.class,
MvvmCollectionModule.class})
public interface AppComponent {
...
}
如何将依赖注入到库中的对象
上面的做法完成了将依赖注入到App模块的准备工作,但是库中的类怎么实现依赖注入呢?
这里提供两种方式
-
使用Multibindings/dagger.android向库中的Activity/Fragment注入依赖
-
在库中创建AppComponent的SubComponent,用SubComponent代替AppComponent在库中使用
dagger.android的方式和之前基本没啥区别,所以主要说下SubComponent的方式
-
为什么需要一个SubComponent
dagger.android只能注入Activity,Fragment等固定类型的子类,如果有一个其他的类具有生命周期(例如登录的Manager)也需要依赖注入,那么dagger.android就做不到了,所以需要一个具有Application相同的生命周期的Component来作为库中的初始Component。
当然,对于其他的对象也可以用Multibindings的思路去实现,不过没有dagger.android的支持,需要写更多的模板代码,麻烦一些 -
使用什么方式关联库中的SubComponent和AppComponent
因为现在AppComponent在最上层而SubComponent反而是在下层,所以只能在AppComponent声明SubComponent或者在Module中指定subcomponents -
SubComponent何时创建/怎么保存/库怎么获取
- 还是需要在Application onCreate时创建,库中没有合适的时机
- 保存在Application中
- 库中定义一个获取SubComponent的接口,Application实现它
与上个方案的比较
上个方案实际每个库的依赖集合都是独立的,每个库的依赖集合包括当前库提供的依赖和下层库暴露的依赖
而当前方案的实际是只有一个总的依赖集合,当然因为dagger是编译期的依赖注入框架,所以即使注入时所有的依赖都有,但是也无法用反射这样的方式为组件提供上层的依赖
不过因为是一个整体,所以组织起来比第一种方案方便一些
优点
- 可以使用dagger.android/Multibindings在平行的库中进行依赖注入
- 库中需要做的事情变少了
- 库的SubComponent保存在Application中,不需要创建一个单例来保存Componet
缺点
上层的App模块也必须依赖dagger
总结
dagger在多模块的项目中的使用或者说在Library中的使用确实会有很多的不顺手的地方,不同的方案有利有弊,要看具体的场景进行选择
只在App模块使用dagger
- 所有的下层依赖都使用Module中Provider方式来提供
- 自己组织App模块的Component
这种引入dagger的方式是代价最小的,可以渐进的去迭代,但是有些Module是应该放在库中公用的,有些Component的依赖逻辑也是可以公用的,现在只存在于App模块中导致复用度不高
这种情况适合只负责上层业务开发的情况
只在库模块中使用dagger
如果要开发一个独立的库,想用dagger的话,只能用第一种方案了,因为库是独立的;如果需要Application实例的话还需要指定Application onCreate时传入Application实例等初始化操作
个人不建议在独立的库中使用dagger
当然如果你的库并不需要和Application作为依赖同时没有使用Multibindings来解耦的场景也是可以用dagger的
在全局使用dagger
这种情况才是用起来最爽的,dagger有一种用的地方越多用起来越顺手的特性,而这种情况适合文章中介绍的第二种方案
当然真实的项目千差万别,所以还是需要自己衡量项目中到底要不要使用要怎样使用dagger
相关文章
dagger2从入门到放弃-概念
dagger2从入门到放弃-最基础的用法介绍
dagger2从入门到放弃-Component的继承体系、局部单例
dagger2从入门到放弃-ActivityMultibindings
dagger2从入门到放弃-dagger.android
dagger2从入门到放弃-其他用法
dagger2从入门到放弃-多模块项目下dagger的使用
dagger2从入门到放弃-为何放弃
示例代码
DaggerInAction
欢迎star
master分支上最新的代码可能会比当前文章的示例代码稍微复杂点,提交记录里包含了每一步的迭代过程,可以顺藤摸瓜