文档当前状态:**beta0.4**
* [x] 选题收集:2017/11/20
* [x] 初稿整理:
* [ ] 补充校对:
* [ ] 入库存档:
---
[TOC]
---
**引言**
在移动开发中,我想大部分开发人员都会面临一个问题,随着项目的开发与不断的迭代,业务也会随之不断壮大,也就意味着业务模块越来越多,这个时候就会出现一个问题,各个模块之间的相互引用,导致模块之间的耦合越来越严重,最终迭代会牵一发而动全身,项目的迭代和维护都特别困难;一些门户型的APP还伴随着子应用单独包装推广,影子应用单独发布等等需求,那么一个合适的应用框架的需求是必不可少的,接下来我们会对Android组件化 实践进行探讨,以图能在需求日益复杂多变的情况下,我们也能如期交付。
那么这篇文章就通过组件化来分析Android模块解耦的实现,实现多人真正的协同开发,每一个模块实现
### 一、模块化、组件化与插件化
项目发展到一定程度,随着人员的增多,代码越来越臃肿,这时候就必须进行模块化的拆分。模块化是一种指导理念,其核心思想就是分而治之、降低耦合。而在Android工程中如何实施,目前有两种途径,也是两大流派,一个是组件化,一个是插件化。
提起组件化和插件化的区别,有一个很形象的图:
![组件化和插件化对比.png](http://upload-images.jianshu.io/upload_images/6650461-c64f921882f379ac.png)
组件化和插件化对比.png
上面的图看上去比较清晰,其实容易导致一些误解,有下面几个小问题,图中可能说的不太清楚:
* 组件化是一个整体吗?去了头和胳膊还能存在吗?左图中,似乎组件化是一个有机的整体,需要所有器官都健在才可以存在。而实际上组件化的目标之一就是降低整体(app)与器官(组件)的依赖关系,缺少任何一个器官app都是可以存在并正常运行的。
* 头和胳膊可以单独存在吗?左图也没有说明白,其实答案应该是肯定的。每个器官(组件)可以在补足一些基本功能之后都是可以独立存活的。这个是组件化的第二个目标:组件可以单独运行。
* 组件化和插件化可以都用右图来表示吗?如果上面两个问题的答案都是YES的话,这个问题的答案自然也是YES。每个组件都可以看成一个单独的整体,可以按需的和其他组件(包括主项目)整合在一起,从而完成的形成一个app
* 右图中的小机器人可以动态的添加和修改吗?如果组件化和插件化都用右图来表示,那么这个问题的答案就不一样了。对于组件化来讲,这个问题的答案是部分可以,也就是在编译期可以动态的添加和修改,但是在运行时就没法这么做了。而对于插件化,这个问题的答案很干脆,那就是完全可以,不论实在编译期还是运行时!
本文主要集中讲的是组件化的实现思路,对于插件化的技术细节不做讨论,我们只是从上面的问答中总结出一个结论:组件化和插件化的最大区别(应该也是唯一区别)就是组件化在运行时不具备动态添加和修改组件的功能,但是插件化是可以的。
暂且抛弃对插件化“道德”上的批判,我认为对于一个Android开发者来讲,插件化的确是一个福音,这将使我们具备极大的灵活性。但是苦于目前还没有一个完全合适、完美兼容的插件化方案(RePlugin的饥饿营销做的很好,但还没看到疗效),特别是对于已经有几十万代码量的一个成熟产品来讲,套用任何一个插件化方案都是很危险的工作。所以我们决定先从组件化做起,本着做一个最彻底的组件化方案的思路去进行代码的重构,下面是最近的思考结果,欢迎大家提出建议和意见。[引用原文](http://www.jianshu.com/p/1b1d77f58e84)
### 一、组件化概述
组件化开发就是将一个APP分成多个模块,每个模块我们都可以看成是一个组件,也就是在IDE中所创建的(Module),在开发的过程中我们可以对这些组件进行单独调试,编译和运行以及进行单独的版本控制等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。对此,组件化也有三个方面的要求:
* **可以单独编译**(不对其他module强依赖)
* **可以单独运行**(开发时以Application运行,发布时Libary)
* **可以单独的版本控制**(可选,主要用于超大型项目,既可以独立app发布,又可以集成到门户app中)
### 二、组件化的优势
#### 1.提升编译速度
在项目达到一定的规模的时候,编译是一件十分痛苦的事情,短则几分钟,长则十几分钟。我们在修改了很小一块代码就要去编译整个项目,这样会导致开发效率降低;通过组件化,在实际开发过程中,我们可以仅仅选择自己负责的那一个Module,排除其他不必要的module,单独编译就会极大的改善这个问题;
#### 2.适应并行开发
通过组件化可以更好的去适应并行开发,因为我们可以为每一个模块进行单独的版本控制,甚至每一个模块的负责人可以选择自己的设计架构而不影响其他模块的开发,与此同时组件化还可以避免模块之间的交叉依赖,每一个模块的开发人员可以对自己的模块进行独立测试,独立编译和运行,甚至可以实现单独的部署。从而极大的提高了并行开发效率。
### 二、实现组件化面对的问题
要实现组件化,不论采用什么样的技术路径,需要考虑的问题主要包括下面几个:
**1.整体结构抽象**
* **代码解耦**。如何将一个庞大的工程拆分成有机的整体?
* **代码隔离**。组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用呢?也就是如何从根本上杜绝耦合的产生呢?只有做到这一点才是彻底的组件化。
**2.Build系统改造**
* **单独编译**。在开发阶段如何做到按需的编译组件?一次调试中可能只有一两个组件参与集成,这样编译的时间就会大大降低,提高开发效率。
* **组件单独运行**。上面也讲到了,每个组件都是一个完整的整体,如何让其单独运行和调试呢?
**3.实际代码编写**
* **数据传递**。因为每个组件都会给其他组件提供的服务,那么主项目(Host)与组件、组件与组件之间如何传递数据?
* **UI跳转**。UI跳转可以认为是一种特殊的数据传递,在实现思路上有啥不同?
* **组件的生命周期**。我们的目标是可以做到对组件可以按需、动态的使用,因此就会涉及到组件加载、卸载和降维的生命周期。
### 暂行的解决方式
**1.调整整体结构,实现解耦**
把庞大的代码进行拆分,Androidstudio能够提供很好的支持,使用IDE中的multiple module这个功能,我们很容易把代码进行初步的拆分。在这里我们对两种module进行区分:**基础库library**和**业务组件Component**。
* **基础库library**。这些代码被其他组件直接引用。比如网络库module可以认为是一个library。
* **业务组件Component**。这种module是一个完整的业务功能模块。比如读书或者分享module就是一个Component。
为了方便,我们统一把library称之为依赖库,而把Component称之为组件,我们所讲的组件化也主要是针对Component这种类型。而负责拼装这些组件以形成一个完成app的module,一般我们称之为主项目、主module或者Host,方便起见我们也统一称为主项目。
经过实际项目的尝试,我们可能就可以把代码拆分成下面的结构:
![](http://upload-images.jianshu.io/upload_images/6650461-ea5003a86e816885.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/505)
这种拆分都是比较容易做到的,从图上看,读书、分享等都已经拆分组件,并共同依赖于公共的依赖库(简单起见只画了一个),然后这些组件都被主项目所引用。读书、分享等组件之间没有直接的联系,我们可以认为已经做到了组件之间的解耦。但是这个图有几个问题需要指出:
* 从上面的图中,我们似乎可以认为组件只有集成到主项目才可以使用,而实际上我们的希望是每个组件是个整体,可以独立运行和调试,那么如何做到单独的调试呢?
-》单独编译,可以通过配置Gradle进行处理,实现运行类型自定义。
* 主项目可以直接引用组件吗?也就是说我们可以直接使用compile project(:reader)这种方式来引用组件吗?如果是这样的话,那么主项目和组件之间的耦合就没有消除啊。我们上面讲,组件是可以动态管理的,如果我们删掉reader(读书)这个组件,那么主项目就不能编译了啊,谈何动态管理呢?所以主项目对组件的直接引用是不可以的,但是我们的读书组件最终是要打到apk里面,不仅代码要和并到claases.dex里面,资源也要经过meage操作合并到apk的资源里面,怎么避免这个矛盾呢?
-》使用ARouter中,主项目一般仅依赖基础库,然后
* 组件与组件之间真的没有相互引用或者交互吗?读书组件也会调用分享模块啊,而这在图中根本没有体现出来啊,那么组件与组件之间怎么交互呢?
-》对于组件间交互这块,实际场景多样,这里主要说 两种情况:
1. 直接跳转页面
2. 不跳转页面,仅调用组件中的部分功能
对于直接跳转页面 并夹带数据的 可以直接使用ARtouer进行跳转,对于第二点仅调用组件的部分功能,这点其实类似sdk的功能,现阶段我们的简单粗暴的做法是 直接把需要的代码下沉到 基础库,现在来看确实能满足业务需求,也足够简单,缺点就是未来业务越来越多后,基础库会原来越大,代码越来越冗杂,有兴趣的可以看看微信团队分享的历程。[微信 Android 模块化架构重构实践(上](https://cloud.tencent.com/community/article/441423)、[微信 Android 模块化架构重构实践 下](https://cloud.tencent.com/community/article/794491)。如果实在不能接受这种技术债务的方式,可以尝试使用下面的抽象服务解决方式。
**抽象服务**
上面我们讲到,主项目和组件、组件与组件之间不能直接使用类的相互引用来进行数据交互。那么如何做到这个隔离呢?在这里我们采用接口+实现的结构。每个组件声明自己提供的服务Service,这些Service都是一些抽象类或者接口,组件负责将这些Service实现并注册到一个统一的路由Router中去。如果要使用某个组件的功能,只需要向Router请求这个Service的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。这与Binder的C/S架构很相像。
因为我们组件之间的数据传递都是基于接口编程的,接口和实现是完全分离的,所以组件之间就可以做到解耦,我们可以对组件进行替换、删除等动态管理。这里面有几个小问题需要明确:
● 组件怎么暴露自己提供的服务呢?在项目中我们简单起见,专门建立了一个componentservice的依赖库,里面定义了每个组件向外提供的service和一些公共model。将所有组件的service整合在一起,是为了在拆分初期操作更为简单,后面需要改为自动化的方式来生成。这个依赖库需要严格遵循开闭原则,以避免出现版本兼容等问题。
● service的具体实现是由所属组件注册到Router中的,那么是在什么时间注册的呢?这个就涉及到组件的加载等生命周期,我们在后面专门介绍。
● 一个很容易犯的小错误就是通过持久化的方式来传递数据,例如file、sharedpreference等方式,这个是需要避免的。
下面就是加上数据传输功能之后的架构图:
![](http://upload-images.jianshu.io/upload_images/6650461-2d59aac42947cbbc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/527)
组件之间的数据传输
其实单独调试比较简单,只需要把apply plugin: 'com.android.library'切换成apply plugin: 'com.android.application'就可以,但是我们还需要修改一下AndroidManifest文件,因为一个单独调试需要有一个入口的actiivity。
为此,我们可以添加了一个变量isApp,标记当前是否需要单独调试,根据isApp的取值,使用不同的gradle插件和AndroidManifest文件,甚至可以添加Application等Java文件,以便可以做一下初始化的操作。
此外,为了避免不同组件之间资源名重复,在每个组件的build.gradle中增加resourcePrefix "xxx_",从而固定每个组件的资源前缀。下面是读书组件的build.gradle的示例:
~~~
if(isApp){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
.....
resourcePrefix "readerbook_"
sourceSets {
main {
if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
java.srcDirs = ['src/main/java','src/main/runalone/java']
res.srcDirs = ['src/main/res','src/main/runalone/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
~~~
通过这些额外的代码,我们给组件搭建了一个测试Host,从而让组件的代码运行在其中,所以我们可以再优化一下我们上面的框架图。
![](http://upload-images.jianshu.io/upload_images/6650461-11e562d609742fbf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/527)
### 参考引文
[阿里ARouter开源组件化框架项目实践](http://www.jianshu.com/p/735c969fdd9d)
[Android彻底组件化方案实践](http://www.jianshu.com/p/1b1d77f58e84)
- 0-发现
- AndroidInterview-Q-A
- Android能让你少走弯路的干货整理
- LearningNotes
- temp
- temp11
- 部分地址
- 0-待办任务
- 待补充列表
- 0-未分类
- AndroidView事件分发与滑动冲突处理
- Spannable
- 事件分发机制详解
- 1-Java
- 1-Java-01基础
- 未归档
- 你应该知道的JDK知识
- 集合框架
- 1-Java-04合集
- Java之旅0
- Java之旅
- JAVA之旅01
- JAVA之旅02
- JAVA之旅03
- JAVA之旅04
- JAVA之旅05
- JAVA之旅06
- JAVA之旅07
- JAVA之旅08
- JAVA之旅09
- java之旅1
- JAVA之旅10
- JAVA之旅11
- JAVA之旅12
- JAVA之旅13
- JAVA之旅14
- JAVA之旅15
- JAVA之旅16
- JAVA之旅17
- JAVA之旅18
- JAVA之旅19
- java之旅2
- JAVA之旅20
- JAVA之旅21
- JAVA之旅22
- JAVA之旅23
- JAVA之旅24
- JAVA之旅25
- JAVA之旅26
- JAVA之旅27
- JAVA之旅28
- JAVA之旅29
- java之旅3
- JAVA之旅30
- JAVA之旅31
- JAVA之旅32
- JAVA之旅33
- JAVA之旅34
- JAVA之旅35
- 1-Java-05辨析
- HashMapArrayMap
- Java8新特性
- Java8接口默认方法
- 图解HashMap(1)
- 图解HashMap(2)
- 2-Android
- 2-Android-1-基础
- View绘制流程
- 事件分发
- AndroidView的事件分发机制和滑动冲突解决
- 自定义View基础
- 1-安卓自定义View基础-坐标系
- 2-安卓自定义View基础-角度弧度
- 3-安卓自定义View基础-颜色
- 自定义View进阶
- 1-安卓自定义View进阶-分类和流程
- 10-安卓自定义View进阶-Matrix详解
- 11-安卓自定义View进阶-MatrixCamera
- 12-安卓自定义View进阶-事件分发机制原理
- 13-安卓自定义View进阶-事件分发机制详解
- 14-安卓自定义View进阶-MotionEvent详解
- 15-安卓自定义View进阶-特殊形状控件事件处理方案
- 16-安卓自定义View进阶-多点触控详解
- 17-安卓自定义View进阶-手势检测GestureDetector
- 2-安卓自定义View进阶-绘制基本图形
- 3-安卓自定义View进阶-画布操作
- 4-安卓自定义View进阶-图片文字
- 5-安卓自定义View进阶-Path基本操作
- 6-安卓自定义View进阶-贝塞尔曲线
- 7-安卓自定义View进阶-Path完结篇伪
- 8-安卓自定义View进阶-Path玩出花样PathMeasure
- 9-安卓自定义View进阶-Matrix原理
- 通用类介绍
- Application
- 2-Android-2-使用
- 2-Android-02控件
- ViewGroup
- ConstraintLayout
- CoordinatorLayout
- 2-Android-03三方使用
- Dagger2
- Dagger2图文完全教程
- Dagger2最清晰的使用教程
- Dagger2让你爱不释手-终结篇
- Dagger2让你爱不释手-重点概念讲解、融合篇
- dagger2让你爱不释手:基础依赖注入框架篇
- 阅读笔记
- Glide
- Google推荐的图片加载库Glide:最新版使用指南(含新特性)
- rxjava
- 这可能是最好的RxJava2.x入门教程完结版
- 这可能是最好的RxJava2.x入门教程(一)
- 这可能是最好的RxJava2.x入门教程(三)
- 这可能是最好的RxJava2.x入门教程(二)
- 这可能是最好的RxJava2.x入门教程(五)
- 这可能是最好的RxJava2.x入门教程(四)
- 2-Android-3-优化
- 优化概况
- 各种优化
- Android端秒开优化
- apk大小优化
- 内存分析
- 混淆
- 2-Android-4-工具
- adb命令
- 一键分析Android的BugReport
- 版本控制
- git
- git章节简述
- 2-Android-5-源码
- HandlerThread 源码分析
- IntentService的使用和源码分析
- 2-Android-9-辨析
- LRU算法
- 什么是Bitmap
- 常见图片压缩方式
- 3-Kotlin
- Kotlin使用笔记1-草稿
- Kotlin使用笔记2
- kotlin特性草稿
- Kotlin草稿-Delegation
- Kotlin草稿-Field
- Kotlin草稿-object
- 4-JavaScript
- 5-Python
- 6-Other
- Git
- Gradle
- Android中ProGuard配置和总结
- gradle使用笔记
- Nexus私服搭建
- 编译提速最佳实践
- 7-设计模式与架构
- 组件化
- 组件化探索(OKR)
- 1-参考列表
- 2-1-组件化概述
- 2-2-gradle配置
- 2-3-代码编写
- 2-4-常见问题
- 2-9-值得一读
- 8-数据结构与算法
- 0临时文件
- 汉诺塔
- 8-数据-1数据结构
- HashMap
- HashMap、Hashtable、HashSet 和 ConcurrentHashMap 的比较
- 迟到一年HashMap解读
- 8-数据-2算法
- 1个就够了
- Java常用排序算法(必须掌握的8大排序算法)
- 常用排序算法总结(性能+代码)
- 必须知道的八大种排序算法(java实现)
- 9-职业
- 阅读
- 书单
- 面试
- 面试-01-java
- Java面试题全集骆昊(上)
- Java面试题全集骆昊(下)
- Java面试题全集骆昊(中)
- 面试-02-android
- 40道Android面试题
- 面试-03-开源源码
- Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
- 面试-07-设计模式
- 面试-08-算法
- 面试-09-其他
- SUMMARY
- 版权说明
- temp111