浅谈项目架构重构之路——组件化与MVP

忙了一个多月,一直没时间写文章。终于把项目重构完了,借此机会浅谈一下对Android架构的见解。笔者将会把重构分为三个部分讲解。
上一篇文章中,我们介绍了项目全局架构重构的方案,即模块化。接下来这篇文章将介绍局部架构重构方案。
前两篇为 概述篇模块化篇

[如有解释错误的地方,欢迎评论区指正探讨]


为什么要进行局部架构重构

解决完全局架构的优化,整个项目已经实现模块化,然而这样就足够了吗?
来看一看模块内部是什么样的,先看一下相对于简单的模块:


简单的局部.png

看起来还挺干净利落的对吧?采用mvc模式,一个activity承载一个业务。
那么当业务变得复杂且需要与其他activity共用又会变成什么样?以我们项目中相册业务为一个例子。

复杂的局部.png

可以看到,这个层次结构比上面的复杂了一些,在我们的项目中,有几个业务具有相册业务,为了实现可复用性,我们抽取了共用的相册业务Activity,提供一些相册相关的基础业务和操作。由于校园业务的相册又分视频相册和图片相册,而这两个相册又只有点击和界面的些许不同,大部分逻辑相同,于是又抽取了校园相册的基础Activity。这是这个复杂业务的大致情况,实际为了复用,这里并不止抽取了这几个Activity,大概是一个不同业务就抽了一个共用的Activity,导致相册这一块的代码逻辑和结构层次极其复杂,混乱。

抽取共用的Activity确实提高了复用度,但是当业务复杂起来,就带来了严重的冗余问题,同时业务的不合理拆分容易造成开发时的混乱。

再看看mvc这块,在图中可能看不出什么端倪,但实际上,在复杂的业务模型下,view与model之间往往纠缠不清,view持有多个model,多个model又持有view,同时大量的controller代码和view代码糅合在Activity中,不仅使得Activity的代码量剧增,难以维护,往往还带来了不少内存泄漏的问题。

为此,笔者提出了采用组件化来改善现有架构,并引入MVP模式优化业务逻辑的处理。
先来看看引入组件化+MVP的架构,能解决什么问题:

  • 实现代码上的解耦,减少冗余的Activity
  • 模块职责划分明显,层次清晰
  • 实现各通用功能的高复用性和灵活性
  • 通过组件组装的方式,使得整个模块层次结构分明
  • 组件可以作为独立工程开发,因此对功能的开发,测试不用再进行全项目编译
  • 解决在mvc模式容易因接口回调导致内存泄漏的问题

下面来看一下,组件化+MVP是什么样的架构。

什么是组件化与MVP

组件化

在上一篇文章中有提到组件和模块的概念,所谓组件指的是构成业务模块或业务功能的基本单位
正如上一篇文章中提到,朋友圈模块由Uploader图片上传组件等多个组件以特定的逻辑组合而成。

组件化与上一篇文章提到的模块化并不是同一个层次的概念,组件化的粒度更小,笔者更偏向于用Lib来形容一个组件。就比如我们常用的Picasso,这是一个图片加载组件,又如OkHttp是一个网络请求组件。这样子的单一功能或单一功能集的Lib,便是组件。

实现组件化即将项目中的业务功能进行细分,划分出粒度更小的通用功能组件,这些组件可以在多个项目中实现复用,而不再单单只归属于某一特定项目。
举个例子来说,前面有提到笔者参与项目有个相册模块,看似抽离了很多通用的Activity,好像可以在其他项目中复用,可实际上呢?仅仅只有最上层的一个Activity可以实现复用,其他Activity都与View强耦合。而最上层的Activity功能又是最少的,根本无法实现复用,只能说减少了一点代码量。

对此,笔者将整个相册模块抽离成了一个图片展示组件:


图片展示组件.png

这个组件依赖于图片加载组件,也就是Picasso之类的框架,实现了通用的图片列表,图片九宫图,图片添加删除等展示功能。原来所有冗余的Activity全部弃用,只需实现一个承载特定业务逻辑的界面,并使用该组件进行展示即可。

MVP

对于MVP模式相信大家都不陌生,MVP模式是传统安卓MVC模式的改良版,将原本承担View和Controller功能的Activity改良为只承担View这一功能,引入Presenter作为View与Model层的中介,架设特定的业务逻辑。
引用一张经典MVP的图片来解释:(原文)

mvp.png

对于MVP模式,网上包括Google官方都有提出几种不同的实现方法,常见的有todo-mvpmvp-clean等,其实对于MVP的实现方式,不同人有不同的见解,不同方式也适用于不同的项目,因此只要理解MVP的思想,以最适用自己项目的方式实现即可。在笔者项目中主要以todo-mvp为原型进行改良,实现了一套自己的MVP模式

如何实现组件化和MVP

如何实现组件化

对于组件化开发,从功能的角度,笔者将组件分为两种:功能型组件View型组件。简单的来说,View型组件一般比功能型组件多了View层的实现。

  • 功能型组件
    也就是日志组件、网络组件、图片加载组件等基础组件。这些组件一般不具备View,只负责提供逻辑功能,需要开发者自己实现特定的View来搭载这些功能。
    对于功能型组件的实现大家应该都比较熟悉,就算没有实现过也应该用过各种第三方的开源组件。

  • View型组件
    而对于View型组件也就是笔者项目中的图片展示组件以及知乎的Matisse组件。这样的组件不仅仅具有逻辑功能,还附带View的实现。
    对于知乎实现的Matisse组件,其采取的是Activity的方式来作为载体,暴露出该Activity的启动方法作为入口。这是大多数View组件会采取的实现方式。以Activity做为载体,那么实现过程与我们平时进行开发的过程差别不会很多,然而却不可避免的减少的了灵活性和复用性。并且只能通过继承来扩展其他功能,组件的通信也十分局限。这也正是笔者项目一开始的实现方式。

具笔者了解,还有另外两种实现View型组件的方式,一种基于Fragment,另一个种基于Custom View。两种实现方式各有各有的优缺点。

基于Fragment的View组件

基于Fragment的实现类似于Matisse基于Activity的方式。一样的生命周期处理,同样可以套用MVP模式。实现过程与我们平时开发也不会差很多。最后只需要暴露Fragment的创建方式即可。

不同于Activity,采用Fragment使得整个组件灵活了许多。可以采用接口通信,并同时保留了与Activity相应的生命周期处理。很好的划分了各个组件之间的界限,解决了复用和耦合的问题。

不过,采用Fragment作为主体,那么不可避免的就带了一系列碎片化的问题,包括旋转屏幕、状态改变、Fragment的重建等问题。

基于Custom View组件

首先要说的是这类Custom View不同于常见的自定义View,常见的自定义View往往不具有太多的非View逻辑操作。这类Custom View以自定义View为载体,具备自己的Model层和Presenter层。可以说是另类的MVP模式。

以图片选择组件为例子,我们需要的东西有:图片加载组件(Picasso)、获取本地图片的工具类(Model)、承载这两者的Custom View、以及协调CustomView和Model的Presenter。这个Custom View可以继承自RecyclerView或者ViewGroup,不同父类保留给外界使用者的特性不同,根据不同情况可以选择不同父类(如果希望保持足够多的列表特性给外界,那么可以继承自RecyclerView,如果不希望外界对内部干预过多,那么继承自ViewGroup)。而统筹这三者逻辑类似于Matisse,只不过Matisse以Activity为承载。

最后我们只需要将这个Custom View暴露给外界即可,其余逻辑由内部实现。这样的实现方式避免了采用Fragment而带来的碎片化问题,也很好的解决了复用和耦合的问题。不过也因此Custom View的生命周期难以管理,无法与Activity对应上,需另行处理。

小结View型组件

对于View型组件的两种实现方式,笔者选择了Fragment的方式,一来团队对于Fragment与MVP的结合比较熟悉,而来Custom View生命周期的管理确实是一个不小的问题,即使解决了管理问题,也带了不小的研发与学习负担。而对于Fragment的带来的碎片化问题,目前来说只需要要求组件继承BaseFragment基类,由基类统一解决即可。

管理组件

对于组件化,其实实现难点并不多,更多是理解组件化的思想和如何管理各个组件。
对于管理组件而言,尤其是View型组件,网上有很多方案,有不少人建议用Router统一管理,在笔者看来,组件化使用Router,有些大材小用。
在笔者项目中,对于组件化的管理,采用上传到私有Nexus仓库,直接通过Gradle依赖管理。

如何实现MVP模式

对于MVP模式,网上已经有很多文章进行解释,GitHub也有很多案例,这里简单介绍一下思想,更多详细建议学习官方DEMO

在MVP模式中,将架构分为三层:

  • View
    View层主要负责界面元素的展示,包括Button、TextView等。由XML和Activity / Fragment组成。前者主要负责静态界面布局,负责负责动态界面。
  • Model
    Model层通常是MVP模式中最复杂的一层,主要负责数据的请求、读写。这一层往往可以分为本地数据和网络数据。
  • Presenter
    Presenter是MVP模式中极其关键的一层,作为View和Model的管理者,也即是中介者。承载着相应的业务逻辑,接收来自View的请求,转换为对Model的请求,进而接收Model的回复,从而通知View进行刷新。

三个层次之间的关系如下:


mvp层次关系.png
  1. View接收用户的触摸事件,传递action给Presenter
  2. Presenter接收到action,执行对应的业务逻辑。如果涉及到数据请求,那么发送相应的request给Model
  3. Model接收到request,进行本地或网络数据请求。待请求结束,返回对应的response给Presenter
  4. Presenter接收到Response,执行对应的业务逻辑。如果需要更新界面元素,那么通知View进行处理

整个流程看起来十分清晰,实现也不难,需要注意的一点是要注意各个层次的调用关系。View层不能主动调用Model,Model亦不可调用View层。两者需要通过Presener作为桥梁。

再思考

其实对于局部架构的重构,并没有太多的技巧,更多的是一种思想。
无论是组件化还是MVP,都是力求实现代码层次的解耦,实现更高程度的复用,是我们的代码更加优雅。
纵然使用组件化和MVP都会增加我们的代码量和研发周期(稍微,可接受)。然而,这样的付出是值得的,但我们回观整个架构,会感觉一切都是可控的,不管是控件的管理,还是View、Model的管理,都十分灵活。

最后再看一下结合模块化重构完之后,整个项目的架构:


系统架构设计.png

一目了然,整个App划分为多个业务模块,这些业务模块以Module的形式管理。对于每个Module需要使用的通用功能,也就划分为了组件,这些组件以Lib的形式管理,藉由Gradle进行依赖管理。
而不管是模块亦或是组件,我们基本都采用了MVP的模式(部分简单的模块、组件除外)。这样便使得不管是整体架构,亦或是局部架构,都十分灵活可管理。

这样的重构,何乐而不为?


最后希望笔者分享的一点经验能对大家提高代码有些帮助,如有错误的地方,欢迎指正探讨。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容