“终于懂了” 系列:Android组件化,全面掌握! (上)双节
作者:胡飞洋
链接:https://juejin.im/post/6881116198889586701
导语
这篇文章最近很火,我也有一些自己的看法:现在去很多公司面试,除了你具备基本的能够写一个高性能app的能力后,一般都会在自己的app里面加一些现有的相对较666的技术,这些技术我们称之为开源框架。
比如,我们以前木匠做一个桌子,我们需要把木头削成木板,然后把木板拼成桌面,然后再去用同样的方式做桌子的腿;现在,我们只需要买做好的桌子板和做好的桌子腿,然后我们就可以用他们拼接就可以自己做一个桌子了。
所以,现在开放代码已经不再需要你去锯木头了,你只需要拿着一个SDK就可以实现一个功能了,那么这个就是我们的开源技术。
那么,这些技术一般包含热升级,热修复,AppInstant,强制更新,组件化路由架构Arouter,RxJava,IOC架构方法,Hook技术,图片加载(Glide),网络访问(Retrofit,okHttp)等等,当然,这些技术你不能只会用,你需要知道他的原理,有时候,你还需要知道如何对这些架构进行改进。
另外,非常重要的一点,Android开发者不愿意接受新知识,所以对于现在Google推出的新的技术方案毫无感觉,这些都是被controlC和controlV所毒害,开源让开发者失去了自我,失去了基本的编码能力,失去了深入学习的信心和毅力,所以对于很多google推出的新技术好组件(GoogleI/O大会新技术),好框架已经很多人不愿意去学习了这是不应该的,我们需要好好学习啊。
注意
为此我把网络上一些学习架构比较好的资源进行了一个整理, 需要的可以查看我的【GitHub】需要直接下载的可以点击这里【设计思想解读开源框架系统学习】
PS:(文章结尾会介绍一下相关学习资源,视频+文档)
一、背景
随着项目逐渐扩展,业务功能越来越多,代码量越来越多,开发人员数量也越来越多。此过程中,你是否有过以下烦恼?
-
项目模块多且复杂,编译一次要5分钟甚至10分钟?太慢不能忍?
-
改了一行代码 或只调了一点UI,就要run整个项目,再忍受一次10分钟?
-
合代码经常发生冲突?很烦?
-
被人偷偷改了自己模块的代码?很不爽?
-
做一个需求,发现还要去改动很多别人模块的代码?
-
别的模块已实现的类似功能,自己要用只能去复制一份代码再改改?
-
“这个不是我负责的,我不管”,代码责任范围不明确?
-
只做了一个模块的功能,但改动点很多,所以要完整回归测试?
-
做了个需求,但不知不觉导致其他模块出现bug?
如果有这些烦恼,说明你的项目需要进行 组件化 了。
上半年,我所在项目进行了大重构,也完成了组件化改造。所以终于学习实践了这样一个“高端知识”,也看了一些文章,于是就有了这篇文章来作为总结和分享~
二、组件化的理解
2.1 模块化
在介绍组件化之前,先说说模块化。我们知道在Android Studio中,新建工程默认有一个App module,然后还可以通过File->New->New Module 新建module。那么这里的“module” 实际和我们说的“模块”基本是一个概念了。也就是说,原本一个 App模块 承载了所有的功能,而模块化就是拆分成多个模块放在不同的Module里面,每个功能的代码都在自己所属的 module 中添加。
已京东为例,大致可以分为 “首页”、“分类”、“发现”、“购物车”、“我的”、“商品详情” 六个模块。![](https://img.haomeiwen.com/i22459598/0fc1a4535395b25c.png)
![](https://img.haomeiwen.com/i22459598/f701b2494362d44b.png)
这是一般项目都会采用的结构。另外通常还会有一个通用基础模块module_common,提供BaseActivity/BaseFragment、图片加载、网络请求等基础能力,然后每个业务模块都会依赖这个基础模块。 那么业务模块之间有没有依赖呢?很显然是有的。例如 “首页”、“分类”、“发现”、“购物车”、“我的”,都是需要跳转到“商品详情” 的,必然是依赖“商品详情” ;而“商品详情”是需要能添加到“购物车”能力的;而“首页”点击搜索显然是“分类”中的搜索功能。所以这些模块之间存在复杂的依赖关系。
![](https://img.haomeiwen.com/i22459598/49dc0c0b151b3f16.png)
模块化 在各个业务功能比较独立的情况下是比较合理的,但多个模块中肯定会有页面跳转、数据传递、方法调用 等情况,所以必然存在以上这种依赖关系,即模块间有着高耦合度。 高耦合度 加上 代码量大,就极易出现上面提到的那些问题了,严重影响了团队的开发效率及质量。
为了 解决模块间的高耦合度问题,就要进行组件化了。
2.2 组件化介绍 — 优势及架构
组件化,去除模块间的耦合,使得每个业务模块可以独立当做App存在,对于其他模块没有直接的依赖关系。 此时业务模块就成为了业务组件。
而除了业务组件,还有抽离出来的业务基础组件,是提供给业务组件使用,但不是独立的业务,例如分享组件、广告组件;还有基础组件,即单独的基础功能,与业务无关,例如 图片加载、网络请求等。这些后面会详细说明。
组件化带来的好处 就显而易见了:
- 加快编译速度:每个业务功能都是一个单独的工程,可独立编译运行,拆分后代码量较少,编译自然变快。
- 提高协作效率:解耦 使得组件之间 彼此互不打扰,组件内部代码相关性极高。 团队中每个人有自己的责任组件,不会影响其他组件;降低团队成员熟悉项目的成本,只需熟悉责任组件即可;对测试来说,只需重点测试改动的组件,而不是全盘回归测试。
- 功能重用:组件 类似我们引用的第三方库,只需维护好每个组件,一建引用集成即可。业务组件可上可下,灵活多变;而基础组件,为新业务随时集成提供了基础,减少重复开发和维护工作量。
下图是我们期望的组件化架构:
![](https://img.haomeiwen.com/i22459598/f6ec71def72d42ef.png)
- 组件依赖关系是上层依赖下层,修改频率是上层高于下层。
- 基础组件是通用基础能力,修改频率极低,作为SDK可共公司所有项目集成使用。
- common组件,作为支撑业务组件、业务基础组件的基础(BaseActivity/BaseFragment等基础能力),同时依赖所有的基础组件,提供多数业务组件需要的基本功能,并且统一了基础组件的版本号。所以 业务组件、业务基础组件 所需的基础能力只需要依赖common组件即可获得。
- 业务组件、业务基础组件,都依赖common组件。但业务组件之间不存在依赖关系,业务基础组件之间不存在依赖关系。而 业务组件 是依赖所需的业务基础组件的,例如几乎所有业务组件都会依赖广告组件 来展示Banner广告、弹窗广告等。
- 最上层则是主工程,即所谓的“壳工程”,主要是集成所有的业务组件、提供Application唯一实现、gradle、manifest配置,整合成完备的App。
2.3 组件化开发的问题点
我们了解了组件化的概念、优点及架构特点,那么要想实施组件化,首先要搞清楚 要解决问题点有哪些?
核心问题是 业务组件去耦合。那么存在哪些耦合的情况呢?前面有提到过,页面跳转、方法调用、事件通知。 而基础组件、业务基础组件,不存在耦合的问题,所以只需要抽离封装成库即可。 所以针对业务组件有以下问题:
- 业务组件,如何实现单独运行调试?
- 业务组件间 没有依赖,如何实现页面的跳转?
- 业务组件间 没有依赖,如何实现组件间通信/方法调用?
- 业务组件间 没有依赖,如何获取fragment实例?
- 业务组件不能反向依赖壳工程,如何获取Application实例、如何获取Application onCreate()回调(用于任务初始化)?
下面就来看看如何解决这些问题。
三、组件独立调试
每个 业务组件 都是一个完整的整体,可以当做独立的App,需要满足单独运行及调试的要求,这样可以提升编译速度提高效率。
如何做到组件独立调试呢?有两种方案:
- 单工程方案,组件以module形式存在,动态配置组件的工程类型;
- 多工程方案,业务组件以library module形式存在于独立的工程,且只有这一个library module。
3.1 单工程方案
3.1.1 动态配置组件工程类型
单工程模式,整个项目只有一个工程,它包含:App module 加上各个业务组件module,就是所有的代码,这就是单工程模式。 如何做到组件单独调试呢?
我们知道,在 AndroidStudio 开发 Android 项目时,使用的是 Gradle 来构建,Android Gradle 中提供了三种插件,在开发中可以通过配置不同的插件来配置不同的module类型。
- Application 插件,id: com.android.application
- Library 插件,id: com.android.library
区别比较简单, App 插件来配置一个 Android App 工程,项目构建后输出一个 APK 安装包,Library 插件来配置一个 Android Library 工程,构建后输出 ARR 包。
![](https://img.haomeiwen.com/i22459598/73d4fa9d8848df95.png)
![](https://img.haomeiwen.com/i22459598/252a55e3f47699c0.png)
显然我们的 App module配置的就是Application 插件,业务组件module 配置的是 Library 插件。想要实现 业务组件的独立调试,这就需要把配置改为 Application 插件;而独立开发调试完成后,又需要变回Library 插件进行集成调试。
如何让组件在这两种调试模式之间自动转换呢? 手动修改组件的 gralde 文件,切换 Application 和 library ?如果项目只有两三个组件那么是可行的,但在大型项目中可能会有十几个业务组件,一个个手动修改显得费力笨拙。
我们知道用AndroidStudio创建一个Android项目后,会在根目录中生成一个gradle.properties文件。在这个文件定义的常量,可以被任何一个build.gradle读取。 所以我们可以在gradle.properties中定义一个常量值 isModule,true为即独立调试;false为集成调试。然后在业务组件的build.gradle中读取 isModule,设置成对应的插件即可。代码如下:
//gradle.properties
#组件独立调试开关, 每次更改值后要同步工程
isModule = false
//build.gradle
//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换
if (isModule.toBoolean()){
apply plugin: 'com.android.application'
}else {
apply plugin: 'com.android.library'
}
3.1.2 动态配置ApplicationId 和 AndroidManifest
我们知道一个 App 是需要一个 ApplicationId的 ,而组件在独立调试时也是一个App,所以也需要一个 ApplicationId,集成调试时组件是不需要ApplicationId的;另外一个 APP 也只有一个启动页, 而组件在独立调试时也需要一个启动页,在集成调试时就不需要了。所以ApplicationId、AndroidManifest也是需要 isModule 来进行配置的。
//build.gradle (module_cart)
android {
...
defaultConfig {
...
if (isModule.toBoolean()) {
// 独立调试时添加 applicationId ,集成调试时移除
applicationId "com.hfy.componentlearning.cart"
}
...
}
sourceSets {
main {
// 独立调试与集成调试时使用不同的 AndroidManifest.xml 文件
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/moduleManifest/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
...
}
可见也是使用isModule分别设置applicationId、AndroidManifest。其中独立调试的AndroidManifest是新建于目录moduleManifest,使用 manifest.srcFile 即可指定两种调试模式的AndroidManifest文件路径。
![](https://img.haomeiwen.com/i22459598/ecc730c3ac4588e6.png)
moduleManifest中新建的manifest文件 指定了Application、启动activity:
//moduleManifest/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hfy.module_cart" >
<application android:name=".CartApplication"
android:allowBackup="true"
android:label="Cart"
android:theme="@style/Theme.AppCompat">
<activity android:name=".CartActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
原本自动生成的manifest,未指定Application、启动activity:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hfy.module_cart">
<application>
<activity android:name=".CartActivity"></activity>
</application>
</manifest>
独立调试、集成调试 ,分别使用“assembleDebug”构建结果如下:
![](https://img.haomeiwen.com/i22459598/b6052c4b59954f8f.png)
3.2 多工程方案
3.2.1 方案概述
多工程方案,业务组件以library module形式存在于独立的工程。独立工程 自然就可以独立调试了,不再需要进行上面那些配置了。
例如,购物车组件 就是 新建的工程Cart 的 module_cart模块,业务代码就写在module_cart中即可。app模块是依赖module_cart。app模块只是一个组件的入口,或者是一些demo测试代码。
![](https://img.haomeiwen.com/i22459598/a1fe8b69586388c8.png)
那么当所有业务组件都拆分成独立组件时,原本的工程就变成一个只有app模块的壳工程了,壳工程就是用来集成所有业务组件的。
3.2.1 maven引用组件
那么如何进行集成调试呢?使用maven引用组件:1、发布组件的arr包 到公司的maven仓库,2、然后在壳工程中就使用implemention依赖就可以了,和使用第三方库一毛一样。另外arr包 分为 快照版本(SNAPSHOT) 和 正(Realease)式版本,快照版本是开发阶段调试使用,正式版本是正式发版使用。具体如下:
首先,在module_cart模块中新建maven_push.gradle文件,和build.gradle同级目录
apply plugin: 'maven'
configurations {
deployerJars
}
repositories {
mavenCentral()
}
//上传到Maven仓库的task
uploadArchives {
repositories {
mavenDeployer {
pom.version = '1.0.0' // 版本号
pom.artifactId = 'cart' // 项目名称(通常为类库模块名称,也可以任意)
pom.groupId = 'com.hfy.cart' // 唯一标识(通常为模块包名,也可以任意)
//指定快照版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
snapshotRepository(url: 'http://xxx/maven-snapshots/') {
authentication(userName: '***', password: '***')
}
//指定正式版本 maven仓库url, todo 请改为自己的maven服务器地址、账号密码
repository(url: 'http://xxx/maven-releases/') {
authentication(userName: '***', password: '***')
}
}
}
}
// type显示指定任务类型或任务, 这里指定要执行Javadoc这个task,这个task在gradle中已经定义
task androidJavadocs(type: Javadoc) {
// 设置源码所在的位置
source = android.sourceSets.main.java.sourceFiles
}
// 生成javadoc.jar
task androidJavadocsJar(type: Jar) {
// 指定文档名称
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
// 打包main目录下代码和资源的task,生成sources.jar
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
//配置需要上传到maven仓库的文件
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
maven_push.gradle主要就是发布组件ARR的配置:ARR的版本号、名称、maven仓地址账号等。
然后,再build.gradle中引用:
//build.gradle
apply from: 'maven_push.gradle'
接着,点击Sync后,点击Gradle任务uploadArchives,即可打包并发布arr到maven仓。
![](https://img.haomeiwen.com/i22459598/ee30219473d72d84.png)
最后,壳工程要引用组件ARR,需要先在壳工程的根目录下build.gradle中添加maven仓库地址:
allprojects {
repositories {
google()
jcenter()
//私有服务器仓库地址
maven {
url 'http://xxx'
}
}
}
复制代码
接着在app的build.gradle中添加依赖即可:
dependencies {
...
implementation 'com.hfy.cart:cart:1.0.0'
//以及其他业务组件
}
可见,多工程方案 和我们平时使用第三方库是一样的,只是我们把组件ARR发布到公司的私有maven仓而已。
实际上,我个人比较建议 使用多工程方案的。
- 单工程方案没法做到代码权限管控,也不能做到开发人员职责划分明确,每个开发人员都可以对任意的组件进行修改,显然还是会造成混乱。
- 多工程把每个组件都分割成单独的工程,代码权限可以明确管控。集成测试时,通过maven引用来集成即可。并且业务组件和业务基础组件也可以 和 基础组件一样,可以给公司其他项目复用。
注意,我在Demo里 使用的是多工程方案,并且是 把ARR发到JitPack仓,这样是为了演示方便,和发到公司私有maven仓是一个意思。 1、需要根目录下build.gradle中添加JitPack仓地址:maven { url 'jitpack.io' } ; 2、JitPack是自定义的Maven仓库,不过它的流程极度简化,只需要输入Github项目地址就可发布项目。
设计思想解读开源框架
第一章、 热修复设计
![](https://img.haomeiwen.com/i22459598/d09788b02ac78006.png)
![](https://img.haomeiwen.com/i22459598/dc4e886c48c33192.png)
第二章、 插件化框架设计
![](https://img.haomeiwen.com/i22459598/08c4fdd060e96a3b.png)
![](https://img.haomeiwen.com/i22459598/87e8ef03979af738.png)
第三章、 组件化框架设计
![](https://img.haomeiwen.com/i22459598/9bfd5459603541db.png)
![](https://img.haomeiwen.com/i22459598/929db5a099ebbb21.png)
需要这份1042页PDF的Android框架学习笔记的可以查看我的【GitHub 】觉得还不错的,记得点个 star!
附:2246页学习笔记
![](https://img.haomeiwen.com/i24216715/49590e46895049b4.png)