Android组件化实践

随着开发人员不断增多,如果没有使用合理的开发架构,规范化一定的代码的写法,随着时间推移会使得代码越来越臃肿,维护成本越来越高,离职入职的人员都难以交接。组件化现已是一个成熟方案,是一个团队多人开发的首选方案,我们在组件化重构的过程,是一个规范化各种代码结构的过程,也是对一个现有业务逻辑梳理的过程,去除年久失修的代码,优化以前做的不合理的地方。

组件的划分

  • 功能组件

lib_xxxx命名,如网络请求组件(lib_http)、图片选择器组件(lib_pictureselector)...这些所有模块都有可能用到并且实现单一功能的组件称为功能组件

  • 业务组件

module_xxxx开头,根据业务自行拆分,类似登录组件(module_login)、用户个人信息组件(module_user)、首页组件(module_main)等

框架结构图

框架结构图.png

Demo示例图

image.png
  • app:

app空壳工程,依赖所有的业务组件,不存放任何逻辑代码。

  • lib_common

基础公共模块,最底层的库,依赖了各种功能组件(lib_xxxx)、第三方SDK(友盟,bugly等),二次封装各种工具类和开源库,存放各种自定义view(如圆角imageview)等跟业务无任何关系的东西。

  • lib_base

业务公共模块,存放各个业务组件的公共部分,如BaseActivity,BaseApplication,基础域名,各种全局常量等,所有业务模块只需要依赖此模块即可,无需依赖其他模块。

  • lib_http

拷贝 https://github.com/zhou-you/RxEasyHttp 开源框架源码,具有支持接口数据缓存,支持多种缓存策略,支持多域名等功能,能有效帮我们统一整个APP的请求方式,每个模块只需要关注自己的请求接口是什么,防止单纯使用Retrofit带来的写法过乱的问题,不一定要用这个框架,只是想表达APP内需要一个高定制化的网络框架,如果项目使用MVVM架构那么可以用Retrofit和Kotlin协程参考优秀的框架自己封一个。

  • lib_pictureselector

第三方开源图片选择器

  • lib_xxxx.....

模块单独编译

每个功能模块单独编译成一个独立APP是组件化开发中最突出的一个优点,能帮我们减少大量的编译时间,我们只需要关注自己模块所正在开发的功能,一切配置结束后,只需要在config.gradle中打开开关即可


image.png

这里有一个注意的点,单独编译的时候像用户数据,是否登录等数据就获取不到了,因为我们之前是在其他模块获取到的这些数据,现在相当于一个没有那些东西的一个全新APP,需要我们在需要单独编译的那个模块下的application直接写死一些数据,如用户数据等(前提是有用到的话),像请求接口需要一些基础参数(auth,token...),那么这一部分基础参数也要先写死先。

组件初始化

每一个业务模块只需要依赖lib_common底层库即可,像有的模块需要引入其他的第三方库,那么只在该模块的gradle中去引入,但是这个时候可能会有问题,有一些库的初始化是需要在application的,而base层由于没引入这个库是没办法初始化的,关于这个问题可以参考(https://juejin.cn/post/6866628586414997512#heading-5)中组件初始化的那个部分,不过一般情况我们只需要base层的BaseApplication把一些常用的一起初始化了就好,如下:

image.png

这里对有一些第三方SDK进行了延时初始化,一方面是因为隐私政策的问题,一方面是可以提高启动速度,这里我用了新建一个IntentService的形式:
image.png

资源命名冲突

由于在最后打包成APK的时候会进行资源的合并,如果资源文件相同最后会被合成一份,为避免这种情况,需要在每个模块下的gradle中配置resourcePrefix字段,每个模块的资源文件以其模块名开头,如果不按照这个规则AndroidStudio会有警告。


image.png

image.png

配置文件统一抽取

为方便所有配置信息管理,我们将所有依赖的第三方库、包名、targetSdkVersion、compileSdkVersion等统一抽取到config.gradle文件中

image.png

我们知道 lib_common 依赖此config配置文件下的所有第三方SDK,开源库等,如果一个个写会很麻烦,这里我们直接写一个循环,依次依赖config下的dependencies数组即可,如下:
image.png

每个业务模块下仍然存在很多重复的配置信息,诸如compileSdkVersion、buildToolsVersion、minSdkVersion等,为了好维护,让代码优雅点,抽取出这些共性的东西命名为module-basic.gradle,业务模块引用即可,如下:
image.png

image.png

模块间的通信

  • 模块间的跳转

模块间没有任何联系,所以如果需要跳转这里用的是阿里的ARouter(https://github.com/alibaba/ARouter),可以在base层做下简单的封装,具体使用可参考github,封装后调用如下:

image.png

同时ARouter支持获取fragment实例,像一般APP首页结构是一个Activity加上四五个fragment,可以将fragment写在对应的模块,在main模块使用如下调用方式即可获取fragment实例
image.png

  • 模块间的数据通信

之前我的做法是直接把一些共性的东西放在base层,像LoginUtil判断用户登录状态这种东西,这变相把实现逻辑都写在了base层,不符合组件化思想,正确的思想应该是每个模块实现每个模块的逻辑,像LoginUtil的实现逻辑就应该是login模块去做,然后其他模块只需要知道怎么调用就好,具体的做法是需要新起一个module(如:export_login)去存放login所需要暴露的服务(interface),然后interface的具体实现在module_login去做,哪个模块需要用到就去依赖这个暴露服务组件,这里面同样是借助了ARouter,这里只做简单阐述,具体做法可参考该文章(https://juejin.cn/post/6881116198889586701#heading-21)组件间的通信那部分。

各组件分包应保持一致

各个业务组件module应该保持一致的分包结构,这样有时候我们需要去维护别人开发的模块的时候,上手才方便,同时也有利于项目保持一致结构,下面分包只是示例,不一定要按照这个分,只是想强调需要保持一致。


image.png

为什么要把网络请求拆分成一个独立的功能库(lib_http)

网络请求是一个APP中非常重要的一块,没改版之前我是用单例简单封装的Retrofit,带来的问题是:

  • ApiService过于臃肿,成千上百个接口写在了一起
  • 如果App存在多域名的情况下,需要新建多个RetrofitClient,看起来也不优雅
  • 缓存问题不好做,如果某个Post方式的接口需要缓存,写法很挫,要么手动判断下有没有缓存数据,要么结合Rxjava在请求代码层做些合并操作,更没有什么缓存策略可言
  • 有一些请求是外部链接的话又得去专门写一个不是返回BaseBean形式,纯返回json的
  • ......
    基于这种种情况,写法会有五花八门,如果人员多起来,如果网络请求库功能不够完善,每个人都去写一套,那就会很乱,我预想的结果是:
  • 每个业务组件无论什么情况下调用网络请求的代码应该保持一致
  • 业务组件的 API 由自己模块去管理,其他模块并不需要知道
  • 支持各种缓存策略,支持多域名,多种请求方式等功能
  • ......
    底层需要一个如RxEasyHttp(https://github.com/zhou-you/RxEasyHttp),OkGo(https://github.com/jeasonlzy/okhttp-OkGo)这样优秀的网络请求库封装,如果使用MVVM+Kotlin,我们可以参考使用Kotlin+协程自己封一个。
之前组件化项目的使用示例如下:

每个模块只需要关心自己请求的是哪个接口,返回什么数据,怎么使用即可。


image.png

无论什么情况,外层都是这样的一种调用形式(支持独立接口的各种配置,如缓存策略啥的...),保持统一有利于我们以后不想用Retrofit想用其他库的时候,只需要修改lib_http即可,业务组件逻辑代码不用修改。


image.png

基于Glide的图片二次封装

为什么要封装?一是因为直接使用Glide写法较长,二是可以让各个业务模块的调用方式统一,目的同样同上面一样,如果封装的好,万一以后不想用Glide,想换其他的图片加载库,我们的业务逻辑才不需要改动到,我下面的写法是直接封装在了base层,如果我们想拆分的粒度比较小,可以单独在抽出一个lib_imageloader的功能组件来写:


image.png

调用方式如下:


image.png

如果有多个业务模块共用某个接口怎么办?

我现在的做法是抽取这个接口到base层作为公共Url,如果是超过四五个模块多用到的,就新建一个IntentService去请求,然后用Eventbus吧请求结果暴露出去。


image.png

屏幕适配方案

屏幕适配采用字节跳动修改DisplayMetrics的方案:https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA,很多大神基于此方案开源了一些库,像AutoSize,但是仍然存在有时候因为适配失效or横竖屏切换导致样式全乱掉的问题,特别是一些系统弹窗,横竖屏需要展示不同弹窗的时候,更容易体现出这个问题,这个问题本质都是用的dp适配的原因,这里可以使用blankj大神的用pt单位适配可以减少适配失效问题,具体可参考 https://blankj.com/2018/12/18/android-adapt-screen-killer/,该方案缺点是如果重构的话改动成本较大,因为布局中的dp、sp需要都换成pt。

常用的第三方开源库

好用的第三方库可以大幅度提升我们的开发效率,同时在多人开发中更有利于我们保持一致的代码样式,想象一下一个DeviceUtils获取唯一的设备ID的方法,同事A写一个方法,同时B不知道A写过了,他又写了一个,同事C又新建了一个PhoneUtils写一个,那代码会越来越冗余,越来越乱:

  • utils库:https://github.com/Blankj/AndroidUtilCode

优秀的utils库,如果我们需要某一个XXUtils工具库的时候,我们应该保持一致意识,优先在该库里面寻找方法,没有的话再补充到base层并周知其他同事

  • recycleview adapter(brvah)库:https://github.com/CymChad/BaseRecyclerViewAdapterHelper

recycleview adapter如果用原生的方式,像多item布局,item点击事件啥的同样是写法很多,使用brvah有效帮我们统一各种写法,方便我们阅读其他同事的代码。

  • ......
    这些库都是几万star很优秀的库,但是不一定就一定要用,如果我们自认为封的比别人好,也可以自己写一套,主要是我们要防止一些模板代码的重复,然后每个人重复的样子还不一样.....

MVVM+Kotlin

MVVM+Kotlin+Jetpack现也已是主流,我们应该多使用ViewBinding等来替代ButterKnife等,多使用Lifecycle等具有生命感知能力的组件来避免内存泄露,多使用Livedata、DataBinding等组件来数据驱动UI,合理的根据业务+MVVM思想合理的拆分逻辑到ViewModel,Repository,UI层,防止一个Activity几千行代码的情况,这会使维护成本大大增加,官方的MVVM架构图如下:


image.png

参考文章

关于组件化更详细的介绍参考下面这几篇:
https://juejin.cn/post/6844903649102004231#heading-18
https://juejin.cn/post/6866628586414997512#heading-6
https://juejin.cn/post/6881116198889586701#heading-21

你可能感兴趣的:(Android组件化实践)