Android 架构系列:
Android 架构一:Android 架构浅析
Android 架构二:纵向横向结合构建项目
Android 架构三:组件化思想
Android 架构四:组件化架构实战
Android 架构五:MVVM+Data Binding构建项目
……
一、前言
前面一直在提到组件化,它是横向结构的架构,另外三种架构,MVC、MVP和MVVM,都可以说是纵向结构。
之前也提到,要纵横结合才能搭建出优秀的项目架构,那么,我们这一节,就探讨一下组件化。
二、组件化思想
组件化不是个新概念,其在各行各业都一直备受重视.至于组件化什么时候在软件工程领域提出已经无从考究了,不过呢可以确认的是组件化最早应用于服务端开发,后来在该思想的指导下,前端开发和移动端开发也产生各自的开发方式.
先来看一下组件化的概念
所谓的组件化,通俗理解就是将一个工程分成各个模块,各个模块之间相互解耦,可以独立开发并编译成一个独立的 APP 进行调试,然后又可以将各个模块组合起来整体构成一个完整的 APP。
它的好处是当工程比较大的时候,便于各个开发者之间分工协作、同步开发;被分割出来的模块又可以在项目之间共享,从而达到复用的目的。组件化有诸多好处,尤其适用于比较大型的项目。
1、为何要组件化
你的项目大概率地存在以下问题:
代码量大,耦合严重
编译慢,效率低
业务或功能开发分工不明确,开发人员要关心全局代码
业务或功能互相渗透,牵一发动全城
再来看看组件化的优点
- 1、架构更清晰,解耦
- 2、加快编译速度
- 3、业务分工明确,开发人员仅专注于自己的业务
- 4、提高开发效率
- 5、组件、业务独立更新版本,可回滚,持续集成
2、如何组件化
你可能之前听说过组件化架构,或者被其高大上的称谓吓到了,但它其实来并不复杂,这里我们先梳理一下,如何去设计一个组件化架构。
它的主题思想无非就以下几点:
- 1、合理分块(设计)
- 2、块整构建(构建)
- 3、独立内聚(实现)
- 4、通信解耦(优化)
下面,我们一点一点详细探讨。
三、合理分块(设计)
说到组件化,我们的第一反应就肯定是分块了,即将一个臃肿错综复杂交叉编码的项目结构,拆分成脉络清晰的组件化结构。
相信对着几百M,甚至上G的代码,大家肯定会头大无从下手,一脸懵逼顿感无力。 这时候,我们不要盲目动手,先先做好分析,个人觉最好是要跳出码农的视角,由表到内得进行分析,才能设计出合理的结构:
- 1、以产品的视角,横向分业务;
- 2、以架构的视角,纵向分层;
1、以产品的视角,横向分业务
一个产品的业务,其实是在规划产品功能,或者做产品原型时,已经定好了,通常我们的服务端也会做业务划分,每个业务部署到独立的容器、虚拟机、服务器。所以,我们做业务划分时,可以咨询后端,也可以咨询产品经理。
各个业务从需求到产品化都是相互独立的,只是在我们写代码的时候,将他们混在一块了,相对来说,业务还是比较好分块的。借助业务先天的独立性,可以帮助我们快速划分业务组件
例如,美团,首页已经展示了好多个业务:美食、电影/演出、酒店住宿、休闲娱乐、外卖、机票/火车票.....这种多业务APP,他们的业务都划分得非常清楚。
业务组件.jpg
但是也应该注意,划分出来的组件不要过多,否则可能会降低编译的速度并且增加维护的难度。
2、以架构的视角,纵向分层
上面,我们很容易就把业务组件给划分出来了,接下来,从纵向角度去分析划分。
我们这里的纵向分层是指,整个架构的纵向分层,即系组件间的纵向分层,并不是之前提到组件内部的纵向分层架构。
2.1 功能组件
经常使用美团的人都会留意到,每个大业务都会存在若干相同的功能。例如,美食业务,可以搜索、使用优惠券、点评等;同时,电影/演出业务、酒店住宿业务、外卖业务、机票/火车票业务……也有搜索、优惠券、点评等。
所以,搜索、优惠券、点评这种功能就可以可以独立出来,被多个业务使用。
功能组件.jpg2.2 基础组件
上面提过,业务组件包含多个功能组件,也就是说该业务由多个组件组合而成。
那么功能组件是不是也可以由多个基础组件组成呢,答案是肯定的。
还是拿美团做例子,点评功能,发布点评需要加工图片、需要定位、网络提交等等,那么需要调用图片组件、地图组件、网络组件……。
基础组件.jpg
当然,基础组件肯定不会不仅仅上面提到,比如说,一些常用的工具类库,数据处理库、线程库……
最后,我们就会得出一个,自上而下的合理分块结构。
业务-功能-基础组件化.jpg
业务组件依赖功能组件,功能组件依赖基础组件,业务之间、功能之间不相互依赖
OK,结构设计完毕。
四、块整构建(构建)
经过艰难的分块分析后,大概的结构就会出来了,在动手拆分前,我们应该先考虑组件的分合构建,即如何让组件快速顺利地从个体独立到整体一部分,如何又能快速从整体到独立个体转变了。
其实,这个应该是事先就要调研清楚,如果没搞清楚就开始进行庞大的分块作业,万一行不通,那不是吃力不讨好哦。
不同的开发环境的构建的方式会略有不同,我们这讲的是基于Android Studio的构建方式:Module + Gradle
Module 负责分块,Gradle负责构建
1、个体与整体切换
组件独立时,能够单独调试,即可以作为独立的工程即Application,而,作为整体的一部分时,它只是个Library,那么要做到如何构建才能达到这一的目的呢?
其实,有点开发经验的童鞋应该都知道,利用Gradle非常容易地就能实现。
我们打开app的build.gradle,看一下第一行
apply plugin: 'com.android.application'
另外,当我们添加一个Module A,选择Library的方式时,打开Module A的build.gradle,看一下第一行
apply plugin: 'com.android.library'
这样,我们就可以自定义一个调试模式的开关,调试模式时是个体,非调试模式时时整体一部分,随时修改开关,达到自由切换个体与整体。
if(rootProject.ext.isDebug.toBoolean()){
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
2、方便性构建
创建调试包com/……/debug/,讲调试模式下独有的类归入到调试包,如我们需要在调试环境下初始化一些特有的类库,则,要在调试包内添加对应的继承于application的类。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initARouter();
}
/**
* 初始化ARouter
*/
private void initARouter(){
if (AppConstants.IS_DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(this); // 尽可能早,推荐在Application中初始化
}
}
上面只是介绍了,应用插件的两种方式,
-
com.android.application,完整工程,需要有自己的applicationId
com.android.library,库工程,作为整体的库,没有组件的applicationId
配置applicationId开关
android {
……
defaultConfig {
if(rootProject.ext.isDebug.toBoolean()){
applicationId "your applicationId"
}
}
根据情况,选择合适的AndroidManifest,
sourceSets {
main {
if (rootProject.ext.isDebug.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
}
}
}
个体完整的AndroidManifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mymultipleprocesses">
<application
android:name=".debug.MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity
android:name=".ProcessMainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
……
</application>
<!-- uses-permission --/>
……
</manifest>
库版的AndroidManifest
<application android:theme="@style/AppTheme" >
<activity
android:name=".ProcessMainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
</activity>
……
</application>
</manifest>
这样,简单几步,我们就版组件化的架构构建起来了。
五、独立内聚(实现)
既然,设计以及构建都做好了,接下来就可以,进入组件进行内部的实现了。
在实现组件时,需要严格准守设计原则,灵活运用设计模式:
- 单一职责:开发人员专注于自己的业务,不要去调整其它非管辖的组件(配合权限管理)
- 依赖倒置:即系上文提到的:业务组件依赖功能组件,功能组件依赖基础组件,业务之间、功能之间不相互依赖。
-
接口隔离:业务之间调用数据,通过统一的协议与服务中心交互,不调用业务实际代码
………
详细见本人另一系列文章设计模式
这样,代码质量与规范程度明显提高,高内聚、低耦合。业务职责分明,单元测试也更好写。
六、通信解耦(优化)
上面艰巨的任务处理好后,我们还是会发现有问题存在,就是组件间原来的通信方式-原生的通信方式,会产生直接的类依赖,耦合严重,违背了高内聚、低耦合的原则,这就迫使我们进一步对组件间的通信进行解耦,以达目的。
1、跳转解耦-路由总线
我们先看看原生的路由方案缺点:
- 显式:直接的类依赖,耦合严重
- 隐式:规则集中式管理,协作困难
- Manifest扩展性较差
- 跳转过程无法控制
- 失败无法降级
ARouter是阿里巴巴开源的Android平台中对页面、服务提供路由功能的中间件,提倡的是简单且够用。
它的优势:
支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
支持多模块工程使用
支持添加多个拦截器,自定义拦截顺序
支持依赖注入,可单独作为依赖注入框架使用
支持InstantRun
支持MultiDex(Google方案)
映射关系按组分类、多级管理,按需初始化
支持用户指定全局降级与局部降级策略
页面、拦截器、服务等组件均自动注册到框架
支持多种方式配置转场动画
支持获取Fragment
完全支持Kotlin以及混编
经过对比,ARouter完美胜出, 那么,我们有什么理由不用ARouter 作为我们架构的路由总线呢。
ARouter基本框架图简单了解一下ARouter,这里的主题不是ARouter,后面会给ARouter写个主题文章。
2、数据解耦-消息总线
定好路由总线了,那么,我们也可以做定一个消息总线,让组件间的通信彻底解耦,目前市面上用的比较多有RxBus和EventBus。
2.1 EventBus
先看看EventBus,它是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化Android事件传递。EventBus可以代替Android传统的Intent、Handler、Broadcast或接口回调,在Fragment、Activity、Service线程之间传递数据、执行方法。
EventBus最大的特点就是简洁、解耦。在没有EventBus之前我们通常用广播来实现监听,或者自定义接口函数回调,有的场景我们也可以直接用Intent携带简单数据,或者在线程之间通过Handler处理消息传递。但无论是广播还是Handler机制远远不能满足我们高效的开发。EventBus简化了应用程序内各组件间、组件与后台线程间的通信。EventBus一经推出,便受到广大开发者的推崇。
eventBus.png2.2 RxBus
RxBus不是一个库,而是一个文件,实现只有短短30行代码。RxBus本身不需要过多分析,它的强大完全来自于它基于的RxJava技术。响应式编程(Reactive Programming)技术这几年特别火,RxJava是它在Java上的实作。RxJava天生就是发布/订阅模式,而且很容易处理线程切换。所以,RxBus凭借区区30行代码,就敢挑战EventBus“江湖老大”的地位。
RxBus原理
在RxJava中有个Subject类,它继承Observable类,同时实现了Observer接口,因此Subject可以同时担当订阅者和被订阅者的角色,我们使用Subject的子类PublishSubject来创建一个Subject对象(PublishSubject只有被订阅后才会把接收到的事件立刻发送给订阅者),在需要接收事件的地方,订阅该Subject对象,之后如果Subject对象接收到事件,则会发射给该订阅者,此时Subject对象充当被订阅者的角色。
完成了订阅,在需要发送事件的地方将事件发送给之前被订阅的Subject对象,则此时Subject对象作为订阅者接收事件,然后会立刻将事件转发给订阅该Subject对象的订阅者,以便订阅者处理相应事件,到这里就完成了事件的发送与处理。
最后就是取消订阅的操作了,RxJava中,订阅操作会返回一个Subscription对象,以便在合适的时机取消订阅,防止内存泄漏,如果一个类产生多个Subscription对象,我们可以用一个CompositeSubscription存储起来,以进行批量的取消订阅。
2.3 LiveDataBus
前面提到EventBus和RxBus都是非常优秀的消息总线框架,但是,他们都有共同的弱点:
- 非官方API
- 无感知Activity、Fragment或Service等组件的生命周期
这样,就引出了LiveDataBus。
LiveData是Android Architecture Components提出的框架。
LiveData是一个可以被观察的数据持有类,它可以感知并遵循Activity、Fragment或Service等组件的生命周期。正是由于LiveData对组件生命周期可感知特点,因此可以做到仅在组件处于生命周期的激活状态时才更新UI数据。
LiveData具有的可观察性和生命周期感知的能力,使其非常适合作为Android通信总线的基础构件,这就是我选择LiveDataBus作为消息总线的缘由之一。
另外一个原因,上面也说了,尽管EventBus和RxBus都非常优秀,但是,他们不是官方推出的,既然我们要抱大腿,为什么不抱最壮的那一条。
下面总结一下LiveData的优点:
-
UI和实时数据保持一致,因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI。
-
避免内存泄漏,观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destroy)时,观察者会立刻自动清理自身的数据。
-
不会再产生由于Activity处于stop状态而引起的崩溃,例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
-
不需要再解决生命周期带来的问题,LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
-
实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据。
-
解决Configuration Change问题,在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
我们可以模仿RxBus,基于LiveData实现一个LiveDataBus来作为我们的EventBus。
LiveData的使用不是本文主题,后面会单独讲一下LiveDataBus,敬请期待。
七、结语
组件化优点:
- 1、架构更清晰,解耦
- 2、加快编译速度
- 3、业务分工明确,开发人员仅专注与自己的业务
- 4、提高开发效率
- 5、组件、业务独立更新版本,可回滚,持续集成
如何组件化:
- 1、合理分块(设计)
- 2、块整构建(构建)
- 3、独立内聚(实现)
- 4、通信解耦(优化)
既然组件化能给我们的开发维护带来质的飞越,我们何乐而不为呢,赶紧抓紧时间,撸起来。看到这里还是不太明白也没有关系,下一节,我将开始来个组件化的实战。
以上就是我总结的组件化的一些思想,小弟能力有限,如有不当之处,请各位指出。
网友评论