安居客Android项目架构演进-程序员宅基地

技术标签: 组件化  架构  android  模块化  Android  

这里写图片描述

本文已授权微信公众号 AndroidDeveloper 独家发布。

入职安居客三年从工程师到 Team Leader,见证了 Android 团队一路走来的发展历程。因此有心将这些记录下来与大家分享,也算是对自己三年来一部分工作的总结。希望对大家有所帮助,更希望能得到大家宝贵的建议。

三网合并

三年前入职时安居客在业务上刚完成了三网合并(新房、二手房、好租和商业地产多个平台多个网站合成现在的 anjuke.com,这在公司的历史上称之为三网合并),因此移动端也将原先的新房、二手房、好租和商业地产多个 App 合并成为了现在的安居客 App。所谓的合并也差不多就是将多个项目的代码拷贝到了一起组成了新的 Anjuke Project。下面这张图能更加直观的呈现当时的状况:

图片名称

这一时期代码结构混乱、层次不清,各业务技术方案不统一,冗余代码充斥项目的各个角落;甚至连基本的包结构也是胡乱不堪,项目架构更是无从谈起。大家只不过是不停地往上堆砌代码添加新功能罢了。于是我进入公司的第一件事就是向 Leader 申请梳理了整个项目的结构。

而后随着项目的迭代,我们不断引入了 Retrofit、UniversalImageLoader、OKHttp、ButterKnife 等一系列成熟的开源库,同时我们也开发了自己的 UI 组件库 UIComponent、基础工具库 CommonUtils、基于第三方地图封装的 MapSDK、即时聊天模块 ChatLibrary 等等。这之后安居客项目架构大致演变成了由基础组件层、业务组件层和业务层组成的三层架构。如下图:

图片名称

其中业务层是一种非标准的 MVC 架构,Activity 和 Fragment 承担了 View 和 Controller 的职责:

图片名称

前面这种分层的架构本身是没太大问题的,即使到了现在我们的业务项目也已然是基于这种分层的架构来构建的,只不过在不断的迭代中我们做了些许调整(分层架构后面在介绍组件化和模块化的时候会详细介绍)。但是随着业务的不断迭代,我们慢慢发现业务层这种非标准的MVC架构带来了种种影响团队开发效率的问题:

  • Activity 和 Fragment 越来越多的同时承担了 Controller 和 View 的职责,导致他们变得及其臃肿且难以维护;
  • 由于 Controller 和 View 的揉合,导致单元测试起来很困难;
  • 回调嵌套太多,面对负责业务时的代码逻辑不清晰,难以理解且不利于后期维护;
  • 各层次模块之间职责不清晰等等

鉴于三网合并时期我还未加入安居客,所以对这一块的理解难免有偏差,如果有安居客的老同事发现文章中的描述有不对的地方还望批评指正。

由 RxJava 驱动的 MVP 架构

一种技术架构无法满足所有的业务项目,更不可能有一种架构方案能够一劳永逸。正如上一节中提到的随着业务的不断迭代,现有架构的缺陷逐渐浮出水面,项目架构必需不断升级迭代才能更好地服务于业务。

MVP 的设计与实现

在研究了 Google 推出的基于 MVP 架构的 Demo 后,我们发现 MVP 架构能解决现在所面临过的很多问题,于是我们学习并引入到了我们的项目中来,并针对性的做了部分调整。下图呈现的是安居客 MVP 方案:
这里写图片描述

以前面提到的三层架构的方案来看是这样的:

图片名称

基于此架构我在 GitHub 上开源了一个项目MinimalistWeather,有兴趣的小伙伴可以去 Clone 下来看看,如果觉得对你有帮助就给个 Star 吧。 :)

  • View Layer: 只负责 UI 的绘制呈现,包含 Fragment 和一些自定义的 UI 组件,View 层需要实现 ViewInterface 接口。Activity 在项目中不再负责 View 的职责,仅仅是一个全局的控制者,负责创建 View 和 Presenter 的实例;
  • Model Layer: 负责检索、存储、操作数据,包括来自网络、数据库、磁盘文件和SharedPreferences的数据;
  • Presenter Layer: 作为 View Layer 和 Module Layer 的之间的纽带,它从 Model 层中获取数据,然后调用 View 的接口去控制 View;
  • Contract: 我们参照 Google 的 Demo 加入契约类 Contract 来统一管理 View 和 Presenter 的接口,使得某一功能模块的接口能更加直观的呈现出来,这样做是有利于后期维护的。

另外这套MVP架构还为我们带来了一个额外的好处:我们有了足够明确的开发规范和标准。细致到了每一个类应该放到哪个包下,哪个类具体应该负责什么职责等等。这对于我们的 Code Review、接手他人的功能模块等都提供了极大的便利。前面提到的 MinimalistWeather 就是为了定规范定标准而开发的。

这一时期我们还在项目中引入了 RxJava,很好的解决了前面提到的嵌套回调的问题,同时能够帮助我们简化复杂业务场景下的代码逻辑(当然 RxJava 的好处远远不止这么一点,对 RxJava 不了解的同学可以去翻翻我之前一系列关于 RxJava 的文章)。我们也将网络库升级到了 Retrofit2 + OKHttp3,它们和 RxJava 之间能更好的配合。

MVP 带来的新问题及解决方案

是不是升级到了 MVP 架构就高枕无忧了呢?很明显不是这样!MVP 架构也会带来以下新的问题:

  • 由于大量的业务逻辑处理转移到了 Presenter 层,在一些复杂的业务场景中 Presenter 同样会变得臃肿难懂。细心的同学可能注意到了前面的架构图中的 Model 层有个 Data Repository 模块,Data Repository 在这里有两个作用:一是可以将原本由 Presenter 处理的部分逻辑转移到这里来处理,包括数据的校验、部分单纯只与数据相关的逻辑等等,向 Presenter 屏蔽数据处理细节,比如作为 Presenter 就不必关心 Model 层传递过来的数据到底是来至网络还是来至数据库还是来至本地文件等等;二是我们引入了 RxJava,但是只有网络层中的 Retrofit 能返回 Observable 对象,其他模块都是返回的还是一些非 Observable 的 Java 对象,为了能在整个 Presenter 层中都体验 RxJava 带来的美妙之处,因此可以通过 Data Repository 做一层转换;
  • 现在的 MVP 架构中最重的部分就是 Model Layer 了,这一点从前面的架构图中就能体现。因此这就要求我们在 Model 层的设计过程中职责划分要足够清晰,分包更明确,耦合度更低。至于分包大家可以参考 MinimalistWeather 的方案:db 包为数据库模块、http 包为网络模块、preference 包是对 SharedPreferences 的一些封装、repository 包就是前面提到的 Data Repository 模块;
  • 同时还有一点需要注意,很多人在使用 RxJava 的过程中往往忘记了对生命周期的管理,这很容易造成内存泄露。MinimalistWeather 中采用了 CompositeSubscription 来管理,你也可以使用 RxLifecycle 这类开源库来管理生命周期。

组件化与模块化

去年下半年我们 Android 团队内部成立了技术小组,基础组件的开发是技术小组很重要的一部分工作,所以组件化是我们正在做的事;模块化更多的是现有的方案受到来自业务上的挑战以及受到了 Oasis Feng 在 MDCC 上的分享和整个大环境的启发,现在正处于设计规划和 Demo 开发的阶段。

组件化

组件化不是个新概念,通俗的讲组件化就是基于可重用的目的,将一个大的软件系统拆分成一个个独立组件。

组件化的带来的好处不言而喻:

  • 避免重复造轮子,节省开发维护成本;
  • 降低项目复杂性,提升开发效率;
  • 多个团队公用同一个组件,在一定层度上确保了技术方案的统一性。

现在的安居客有是三个业务团队:安居客用户 App、经纪人 App、集客家 App。为了避免各个业务团队重复造轮子,团队中也需要有一定的技术沉淀,因此组件化是必须的。从本篇的第一节大家就能看到组件化的影子,只不过在这之前我们做的并不好。现在我们需要提供更多的、职能单一、性能更优的组件供业务团队使用。根据业务相关性,我们将这些组件分为:基础组件和业务组件。后面在介绍模块化的时候会有进一步的描述。

模块化

自从 Oasis Feng 在去年的 MDCC2016 上分享了模块化的经验后,模块化在 Android 社区越来越多的被提起。我们自然也不落俗的去做了一些研究和探索。安居客现在面临很多问题:例如全量编译时间太长(我这台13款的 MacBook Pro 上打一次包得花十多分钟);例如新房、二手房、租房等等模块间耦合严重,不利于多团队并行开发测试;另外在17年初公司重新将租房 App 捡起推广,单独让人来开发维护一个三年前的项目并不划算,所以我们希望能直接从现在的安居客用户端中拆分出租房模块作为一个单独的 App 发布上线。这样看来模块化似乎是一个不错的选择。

所以我们做模块化的目的大致是这样的:

  • 业务模块间解耦
  • 单个业务模块单独编译打包,加快编译速度
  • 多团队间并行开发、测试
  • 解决好租App需要单独维护的问题,降低研发成本

15年 Trinea 还在安居客的时候开发了一套插件化框架,但受限于当时的团队规模并且插件化对整个项目的改造太大,因此在安居客团队中插件化并未实施下来。而模块化其实是个很好的过渡方案,将项目按照模块拆分后各业务模块间解耦的问题不存在了,后续如有必要,再进行插件化改造只不过是水到渠成的事。

来看看安居客用户 App 的模块化设计图:
这里写图片描述

整个项目分为三层,从下往上分别是:

  • Basic Component Layer: 基础组件层,顾名思义就是一些基础组件,包含了各种开源库以及和业务无关的各种自研工具库;
  • Business Component Layer: 业务组件层,这一层的所有组件都是业务相关的,例如上图中的支付组件 AnjukePay、数据模拟组件 DataSimulator 等等;
  • Business Module Layer: 业务 Module 层,在 Android Studio 中每块业务对应一个单独的 Module。例如安居客用户 App 我们就可以拆分成新房 Module、二手房 Module、IM Module 等等,每个单独的 Business Module 都必须准遵守前面提到的 MVP 架构。

同时针对模块化我们也需要定义一些自己的游戏规则:

  • 对于 Business Module Layer,各业务模块之间的通讯跳转采用路由框架 Router 来实现(可能会采用成熟的开源库,也可能会选择重复造轮子);
  • 对于 Business Component Layer,单一业务组件只能对应某一项具体的业务,对于有个性化需求的对外部提供接口让调用方定制;
  • 合理控制各组件和各业务模块的拆分粒度,太小的公有模块不足以构成单独组件或者模块的,我们先放到类似于 CommonBusiness 的组件中,在后期不断的重构迭代中视情况进行进一步的拆分(这一点的灵感来源于 Trinea 的文章);
  • 上层的公有的业务或者功能模块可以逐步下放到下层,合理把握好度就好;
  • 各 Layer 间严禁反向依赖,横向依赖关系由各业务 Leader 和技术小组商讨决定。

对于模块化项目,每个单独的 Business Module 都可以单独编译成 APK。在开发阶段需要单独打包编译,项目发布的时候又需要它作为项目的一个 Module 来整体编译打包。简单的说就是开发时是 Application,发布时是 Library。因此需要你在 Business Module 的 Gradle 配置文件中加入如下代码:

if(isBuildModule.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}

如果我们需要把租房模块打包成一个单独的租房 App,像下面这样就好:
这里写图片描述

我们可以把 Basic Component Layer 和 Business Component Layer 放在一起看做是 Anjuke SDK,新的业务或者项目只需要依赖 Anjuke SDK 就好(这一点同样是受到了 Trinea 文章的启发)。甚至我们可以做得更极致一些,开发一套自己的组件管理平台,业务方可以根据自己的需求选择自己需要的组件,定制业务专属的 Anjuke SDK。业务端和 Anjuke SDK 的关系如下图所示:
这里写图片描述

最后看看安居客模块化的整体设计图:
这里写图片描述

模块化拆分对于安居客这种比较大型的商业项目而言,由于历史比较久远很多代码都运行五六年了;各个业务相互交叉耦合严重,所以实施起来还是有很大难度的。过程中难免会有预料不到的坑,这就需要我们对各个业务有较深的理解同时也要足够的耐心和细致。虽然辛苦,但是一旦完成模块化拆分对整个团队及公司业务上的帮助是很大的。

以上是我的简单总结以及对模块化的一些思考,不足之处还望大家批评指正。后面模块化的 Demo 完善后我会把它放到 GitHub,并再出一篇文章详细介绍模块化的设计实现细节。

参考资料:

如果你喜欢我的文章,就关注下我的知乎专栏或者在 GitHub 上添个 Star 吧!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/baron_leizhang/article/details/58071773

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线

推荐文章

热门文章

相关标签