Android工程化实践:模块化,太完整了

  1. 中型项目:业务进一步增长,单纯搞Module Library已经不好用了,这个时间插件化框架很火,很强大,但是问题也很多,我们最终采用了Router的方式实现了一套伪模块化方案
  2. 大型项目:时间来到了现在,公司业务有了爆发式的增长,公司的应用也有原来的2个变成了5个,而且还有很多定制App、影子App,模块App等需求提交给我们,在上一套伪模块化方案的基础 上,我们要实现一套真正的模块化方案。

大风车工程架构如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1k1L5gC-1630829115832)(https://user-gold-cdn.xitu.io/2018/3/22/1624d2362f768e8a?imageView2/0/w/1280/h/960/ignore-error/1)]

可以看到整个大风车的主工程可以分为四层:

  • 主工程业务层
  • 模块业务层
  • 公司框架层
  • 第三方框架层

所以你可以看到这个工程与模块之间、模块与模块之间的依赖关系真的是美如画😅,相互引用导致扩展性和可维护性都很差,而且难以测试。我们来看看这种项目架构的问题在哪里:

  • 模块边界被破坏,模块之间相互依赖,模块升级复杂,测试困难。
  • 基础工程中心化,类库积累过重,难以维护。
  • 模块依赖主工程,所有模块无法独立编译、独立发布,编译耗时,APK体积巨大,多团队无法并行开发。

二 提出方案

我们先来看一看重构后的架构,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gGQIGTLF-1630829115834)(https://user-gold-cdn.xitu.io/2018/3/22/1624d239f3c89625?imageView2/0/w/1280/h/960/ignore-error/1)]

重构后的大风车采用多容器架构,我们来看看这套架构是如何实现的。

2.1 模块容器

既然要把业务模块化,那就要有承载模块的容器,目前来说主要用以下三种容器:

  • Native容器:Android/iOS原生的容器,承载使用原生实现的业务,例如Android就有Activity容器、Fragment容器以及更加细粒度的View容器。
  • H5容器:传统WebView承载的页面。
  • ReactNative/Weex/Flutter容器:这是自Facebook从15年推出RN方案开始后,流行起来的方案,这套方案的思想就是将JS组件转义成Native组件,从而实现一套界面,多端运行的效果。

👉 注:手淘提供了细粒度的View容器方案:Virtualview-Android,它可以通过下发XML配置文件,动态的渲染View。

从长远来看,这三套容器都不是用来相互取代对方,而是会长期并存,取长补短,相互助益。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h4IhZite-1630829115835)(https://user-gold-cdn.xitu.io/2018/3/22/1624d24324fc4a7c?imageView2/0/w/1280/h/960/ignore-error/1)]

  • Native容器:Native容器适合用来编写应用的基础骨架页面,例如主页等,这在iOS上也用来避免审核上的问题。
  • H5容器:H5容器适合用来编写经常需要变化的页面,例商家活动页等。
  • ReactNative/Weex/Flutter容器:这一类容器就适合用来编写常规的页面界面,由于这一类容器也天然带有热更新能力,所以它也可以用来解决动态发布,热修复等方面的问题。

那如何实现这三套容器呢?🤔

  • Native容器:插件化方案,插件化方案大体都比较相似,具体可以参见我这一篇文章的讨论VirtualAPK
  • H5容器:WebView封装,Jockey通信协议封装。
  • ReactNative/Weex/Flutter容器:ReactNative/Weex/Flutter容器工程化体系搭建,事实上,用RN或者Weex写页面是十分简单的,它的复杂性在于工程化体系的搭建。

这三套容器的实现,我们后续都有详细的文章来讨论,我们接着来看看模块架构的实现。

2.2 模块架构

一个良好的系统设计纵向分层,横向模块化。我们来看看从纵向和横向的角度如何去设计一个模块。

2.2.1 纵向架构

一般说来,从纵向角度,一个模块一般可以划分为三个部分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j63Cw3P3-1630829115837)(https://user-gold-cdn.xitu.io/2018/3/22/1624d247378230c8?imageView2/0/w/1280/h/960/ignore-error/1)]

  • Api层:接口部分,提供对外的接口和数据结构。
  • Implementation层:实现部分,提供对业务逻辑的实现,它往往和应用的状态、账户信息等息息相关,library为它提供具体的功能,它决定如何去加载、组织、以及展示这些功能。
  • Library层:功能部分,为implementation提供一些具体的功能。

一个模块就这样可以被划分为三层,如果是更加复杂的模块,我们还有做好层与层间的解耦与通信,我们接着来看一下横向架构如何实现。

2.2.2 横向架构

横向架构就是如何去处理视图、数据与业务逻辑的关系,关于这一块内容的实践,从最初的MVC、到MVP、MVVM,各种架构的目的都都是希望模块的耦合性更低、独立性更强,移植性更好。

Google自己也开了一个Repo来讨论这些框架的最佳实践,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GLBqqDHc-1630829115838)(https://user-gold-cdn.xitu.io/2018/3/22/1624d24c1e3412c2?imageView2/0/w/1280/h/960/ignore-error/1)]

  • MVC:PC时代就有的架构方案,在Android上也是最早的方案,Activity/Fragment这些上帝角色既承担了V的角色,也承担了C的角色,小项目开发起来十分顺手,大项目就会遇到 耦合过重,Activity/Fragment类过大等问题。
  • MVP:为了解决MVC耦合过重的问题,MVP的核心思想就是提供一个Presenter将视图逻辑I和业务逻辑相分离,达到解耦的目的。
  • MVVM:使用ViewModel代替Presenter,实现数据与View的双向绑定,这套框架最早使用的data-binding将数据绑定到xml里,这么做在大规模应用的时候是不行的,不过数据绑定是 一个很有用的概念,后续Google又推出了ViewModel组件与LiveData组件。ViewModel组件规范了ViewModel所处的地位、生命周期、生产方式以及一个Activity下多个Fragment共享View Model数据的问题。LiveData组件则提供了在Java层面View订阅ViewModel数据源的实现方案。

Google官方也提供了MVP的实现,这个MVP框架的核心思想如下所示:

  • 使用Contract接口统一管理View接口和Presenter接口的定义,当然这个也不是一定非得这么写,并不是每个View接口和Presenter接口都可以成对出现,可能会出现一个VIew接口对应介个Presenter接口或者 一个Presenter接口对应几个View接口的情况。
  • 采用Fragment实现View接口,我们知道Presenter接口主要定义的是业务逻辑,例如:加载下一页、下拉刷新、编辑、提交、删除等,这些都是在页面的生命周期方法或者setXXXListener里调用的,Fragment的生命 周期正好可以用的上,而且Fragment还可以独立的填充到其他Activity里。

官方的这套框架存在两个问题:

  • 正如上面所说的View接口交由Fragment实现,但是如果一个页面由多个独立的子页面组合而成,那是不是要在这个页面添加几个Fragment,这显示是不合理的,鉴于这种情况,我们可以 退而求其次,采用自定义View的方式来实现View接口。
  • 当页面增大到一定的量级的时候,就出出现大量的Presenter实现类,其实大风车现有的工程就有很多的Presenter实现类,Presenter实现类和View实现类需要相互set,以便View可以调用Presenter加载数据 ,Presenter调用View刷新UI,管理这些Presenter类是个很大的问题,而且如果别人要继承你这个View,你还要告诉它在View的生命周期里如何去处理Presenter的创建和销毁,以及何时去加载数据等等。 如果出现跨部门甚至跨跨城市的合作时,沟通成本就非常的高。

总的说来,就是当业务量急剧膨胀的时候,就会需要写大量的View接口和Presenter类,而且这还牵扯到Presenter类与Activity生命周期同步的问题,在大型项目面前,这些操作都会变得十分复杂。

综上所述,一个理想的方案就是结合ViewModel组件与LiveData组件来实现MVVM框架。

这套框架有两个重要的原则:

  • 任何不处理UI逻辑和用户交互的代码都不应该写到Activity或者Fragment中,因为Activity或者Fragment是十分脆弱的,低内存、配置发生变化、进入后台等等都可能导致它们的销毁,应该 最大限度的减低对Activity或者Fragment的依赖。
  • 应该使用一个持久数据模型来驱动我们的UI,数据可以在该套模型里进行持久化,一旦Activity或者Fragment被销毁,用户数据不会丢失,这套模型专门用来处理数据逻辑,使应用的数据逻辑与视图逻辑 向分离,让应用变得更易维护。

👉 注:这里可能有人有疑问,非得用Lifecycle组件吗,利用View的onAttachToWindow()、onDetachToWindow()这些方法来模拟Activity或者Fragment的生命周期不可以吗,事实上View的生命周期在 一些特殊的场景下是不可靠的,例如:RecyclerView、ViewPager,所以我们还是需要利用Lifecycle组件来监听Activity或者Fragment的生命周期变化。

2.3 模块通信

解决了模块间的解耦问题,另一个就是模块间的通信问题。在一个大型的应用里很多模块都是可以独立运行甚至独立成一个App的,这就牵扯到模块间的数据交互和通信问题,例如:最常见的一种 场景就是子模块需要知道主应用里的登录信息等等,模块间的通信业可以分为两种情况:

  • 进程内通信:模块都运行在同一个进程中。
  • 跨进程通信:模块运行在不同的进程中。
2.3.1 进程内通信

进程内通信的手段有很多种,最常见的就是EventBus,

EventBus 用来完成 Activities, Fragments, Threads, Services 之间的数据交互和通信。

EventBus是早期页面通信和模块通信常见的手段,它的好处是显而易见的,将事件的发布者与订阅者解耦,无需再定义一堆复杂的回调接口,但是随着工程的 膨胀,它的问题也凸显出来,具体说来:

  • Event并非所有通信常见的最佳方式,它主要适合一对多的广播场景,如果业务中的通信需要一组接口时,就需要定义多个Event,代码复杂。
  • 大量的Event的类,难以管理,如果应用越来越庞大,模块划分也越来越多,这个Event就变得难以维护。

但是即便这样,EventBus还是一个优秀的进程内通信的方式。

👉 注:当然除了EventBus以外,在简单的通信场景下,我们还可以选择LocalBroadcastReceiver。LocalBroadcastReceiver是一个应用内的局域广播,它也是利用一个Looper Handler维护一个 全局Map进行应用内部通信,与EventBus不同,它发送的是字符串。LocalBroadcastReceiver在面临业务膨胀的时候,也会遇到消息字符串的管理问题。

2.3.2 进程间通信

跨进程通信可以借助Content Provider来完成,

Content Provider 底层采用的是Binder机制,用来完成进程间的数据交互和通信。

模块通信采用Content Provider的方式来解决,一个比较常见的场景就是多模块共享登录信息,登录信息可以用Content Provider来保存,当登录状态发生变化时,可以通知到 各个模块。

通过上面的分析,我们已经完成了一个设计良好的模块,但是模块的接入仍然面临着诸多问题,例如:如何界定模块的生命周期,用户信息等如何同步,模块如何进行注册以及初始化 等问题。少量的模块,这些都不是问题,但是当模块增长到一定的数量级的时候,这个问题就会变得十分突出。

2.4 模块生命周期

模块生命周期的生命周期可以做如下划分:

  1. 进程启动:执行模块的初始化。
  2. Account初始化:执行模块用户信息同步,告知模块用户已经登录。
  3. Account注销:执行模块用户信息同步,告知模块用户已经注销。
  4. 进程退出:执行模块的退出。

2.5 模块初始化

学习交流

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值