前言
前一段时间看到JessYan的一篇文章,分享他的MVP开源框架,我相信大多数人应该都还有印象,不得不说这个框架确实很棒,感谢JessYan的开源和分享。框架虽然不是项目的核心,但却是项目的基础。一个好的项目框架,能帮助你快速地开始企业级项目开发。本人经过对MVPArms的研究考量并结合自己的想法与实际开发过程得到Atoms-MVP。
项目已开源Github,完全组件化的项目框架Atoms-mvp
什么是组件化
组件化就是将一个完整的App按照关注点分离成多个组件,使得每个组件成为单独的个体,做到组件内部的高聚合和组件之间低耦合。
为什么我们要组件化?
宏观上讲,组件化使开发人员更加专注于各模块的开发,使团队成员更加方便迭代升级和维护,管理者也更容易统筹帷幄,规范项目层次和结构统一。微观上讲,组件化必将对基础库下沉从而提高复用,这也是项目框架演变进化过程中的的核心思想,对于子模块一个萝卜一个坑,互相不直接依赖,所以不用担心变更带来的风险。从根本上避免一个小小的改动牵连多处需要修改,导致回归整个App测试。对于测试同学而言,能有效的减少测试的时间,原有的业务不需要再次进行功能测试,只需专注于发生变化的业务,以及最终的集成测试即可
Atoms-mvp组件化过程
1.面向开发过程,维护成本,学习成本。化繁为简,less is more,高扩展性为宗旨,使新手也能快速搭建自己的组件化项目
2.基础库下沉,只下沉最核心最必须,绝不拖泥带水,纳污藏秽。
2.弱化了Dagger2的使用频率,因为这对于一些新手来说Dagger2可能不是很好理解,排错不是很好,但是对于一些频繁使用到的Dagger代码我做了优化,省去了大量重复代码,帮助团队加快开发速度。
3.网络请求方面使用Rxjava,okhttp,retrofit组合式框架但是并没有使用RxCache处理缓存,因为我觉得这样会加重开发成本,增加编码量和维护成本,其次Okhttp本身是自带缓存策略的,我们可以到后期真正需要的时候再使用缓存框架
4.在设计架构时,将最原始,最需要,最重要的库打入baseLib封装成基础库,比如网络,support,依赖注入型框架dagger,以及和view相关的butterknife,或者其他的一些基础库,开发者可以自行扩展。目的是将这些充斥于整个界面相关的业务逻辑集成到最底层。往上一层封装整个APP需要的公共服务。其中包括CommonSDK用来承上启下,并提供ARouter路由框架,或者使用360Replugin插件化框架,为组件之间解耦做铺垫,开发者可视情况而定二选其一。以及业务服务库和公共UI库,统一整个APP的UI风格方便后期统一调整。再往上就是App的核心组件,各组件依赖公共层,组件之间绝对分离。最后整合在一起完美融合,形成一个APP应用系统
本项目特点
1.支持路由框架和插件划框架比如Arouter,RePlugin等
2.动态代理Application生命周期,各组件可做具体实现,使得子Module间接地拥有Application,能够在子module中初始化相对当前nodule的的第三方库。
3.各模块可配置单独的ServiceApi,拥有属于自己独立的Api接口,同时还支持不同host场景
4.各组件数据传输面向接口不直接依赖于对象,使组件化解耦更彻底,做到完全组件化。在下面我会一一讲到。
组件化的关键点
代码隔离
代码隔离同样也是代码解耦,每个组件之间代码互不侵入,互不依赖,团队之间应具备规范的意识。除了人为上的约束,IDE工具gradle也为我们提供了很好的代码隔离语法,从AS3.0开始类库依赖出现了四种新语法如下:
implementation:对应以前的compile。与compile的区别在于使用implementation在编译期间只对当前宿主可见,对其他宿主隔离。同时它还有一个好处是能够加快编译速度。打个比方如果宿主使用implementation引用库的话,当宿主发生变化重新编译时,库不需要再编译,只需要编译这个宿主。而如果你使用的是compile引用库的话,那么两者都需要重新编译。两者相比较推荐使用implementation。
compileOnly:这个语法就很有意思了,它的作用是依赖的类库只参与编译,而不会打包进Apk。应用场景:子Module中引入于父亲Module相同的库可使用compileOnly ,比如下面这个例子
子Module
compileOnly "com.alibaba:arouter-compiler:1.1.4"
compileOnly "com.jakewharton:butterknife-compiler:8.8.1"
compileOnly 'com.google.dagger:dagger-compiler:2.15'
父Module
api "com.alibaba:arouter-api:1.1.4"
api "com.jakewharton:butterknife:8.8.1"
api "com.google.dagger:dagger:2.15"
注意,如果你引入进来的是一个Module那么请注意父Module也应该有相同的引入,但是你不能调用这个Module的资源文件,否则编译运运行的时候会报错。如果您在其他模块以compile的方式依赖了相同lib,最终在打包过程中可能会出现重复代码,建议您采用compileOnly解决重复依赖的问题
runtimeOnly: 从上图可以看出,在代码隔离效果上,runtimeOnly的效果是最好的!在引用子模块最好选择runtimeOnly,防止主Module调用子Module的代码和资源。
其次我再补充说明一下资源文件隔离。虽然上面解决了代码隔离但是对于资源文件并没有控制,其他宿主还是能够访问下级的资源文件,为了避免编译时冲突,可为资源名增加一个前缀,约束资源文件的命名规范。
android {
resourcePrefix "gank_" //给 Module 内的资源名增加前缀, 避免资源名冲突
}
独立的ServiceAPI
有些项目可能有多baseUrl的需求,这种情况Retrofit官方给了两种解决方案,如下。
第一种:在 @Get , @Post注解中不仅可以传相对路径,还可以传全路径
第二种:将全路径以参数@Url的形式传递给接口
独立的Application
对于有些子模块来说,它有属于自己依赖的第三方库,有些第三方库需要在Application中初始化,而一个应用程序的Application对象只有一个,每个组件又全部分离出去了,那么如何才能各组件共享?在Atoms-MVP中提供了解决方案。通过代理方式,委派一个AppDelegate对象去代理Application的生命周期。各各子模块只需在Manifest文件中定义 meta标签,指定真实被调用的Application生命周期类名,由Appdelegate创建时通过反射的手段初始化。贴一张图方便理解
数据传递
在组件化项目中,每个组件都是相对独立,那么他们之间如何进行数据传递呢?首先想到的是Intent、sp、file、广播、内容提供者等这些常见的数据传递方式,但是刚刚说了组件之间是相对独立的,不能直接把实体类传递过去,因为在完全解耦的组件化项目中,实体类不会下沉放到公共库,也不会在多个组件之间存在依赖关系,只出现在于属于它们自己的组件当中。那么如何将实体类也完全解耦呢?Atoms-mvp提供了一套解决方案,每个组件如果需要对外提供数据传递,那么统一在CommonService组件中声明向外提供服务接口,具体的实现由需要传递数据的组件负责,数据接收者只需要声明这个接口,再调用接口中定义的方法获取数据就能完成跨模块之间的数据传递。这样做使得组件之间不直接指引,避免各组件之间的类出现互相指引的关系,把原有的一对多的依赖变成了一对一的依赖,CommonService相当于一个中介者,来处理各组件之间需要交流的角色。另一方面这是一种很好的思想,想像一下如果没有中间件,那各组件之间数据传递相互指引很有可能就会出现相互缠绕导致耦合严重,一处修改,多出受灾。中间件的好处能够降低变更带来的风险,定义好通信协议,由组件实现或调用。大多数情况下变更的总是实现,接口相对很少变更,即使变更也有办法兼容。所以它能够对抗业务逻辑发生变化带来的风险。
总结
Android应用的架构,根据不同的应用场景和复杂程度使得技术架构也有不同,架构没有好坏之分,只要适用于自己的业务就是好架构,每一次的重构每一次的更新都是为了节省人力和成本,提高开发效率。如果您正打算使用组件化框架完全可以把我的项目搬过去,改一改包名即可。同时Atoms-mvp也可以作为大家在进行组件化时的参考资料,如果您觉得我的文章写的不错,对您有帮助请为我点赞。
Atoms-mvp项目地址:https://github.com/xwc520/Atoms-mvp
参考文档:
https://www.jianshu.com/p/1c5afe686d75
https://www.jianshu.com/p/f671dd76868f
关于我Vea
:https://www.jianshu.com/u/9184fed0c6f2