组件化是什么?组件化的定义是什么?组件化是什么时候形成的?
在项目开发中,一般会将公用的代码提取出来用于制作基础库Base module,将某些单独的功能封装到Library module中,根据业务来划分module,组内的每个人分别开发各自的模块,如下图所示:
项目初始架构图随着事件的推移,项目迭代的功能越来越多。扩展了一些业务模块之后,互相调用的情况就会增多,对某些库也增加了扩展和调用,工程的架构最后可能会演变成为这样:
项目迭代架构图可以看出,各种业务之间的耦合非常严重,导致代码非常难以维护,更加难以测试,扩展性和维护性非常差,这样的架构毫无章理可言,最终肯定会被替代。
这时,新的规划就出现了,这就是组件化,模块化,以及插件化。
多module划分业务和基础功能,这个概念将作为组件化的基础。
组件:指的是单一的功能组件,如视频组件,支付组件,路由组件等,每个组件都能单独抽离出来独自运行。
1)组件化概念
“组件化开发:就是将一个app分成多个Module,每个Module都是一个组件(也可以是一个基础库供组件依赖),开发的过程中我们可以单独调试部分组件,组件间不需要互相依赖,但可以相互调用,最终发布的时候所有组件以lib的形式被主app工程依赖并打包成1个apk。
2)基础组件化架构
用语言来形容一个抽象的架构不好理解。我们用一个非常基础的组件化架构来解释组件化基础。如图:
基础组件化架构上面的架构从上到下分为应用层,组件层和基础层|:
每一个业务模块彼此之间是没有任何关系的,彼此的代码和资源都是隔离的,并且不能够相互引用,每一个都是平行关系。
组件化的核心:
3)组件化的难点
实现组件化核心中的三点并不难,难点在于如何在组件化之后,代码还能像以前一样跳转和模块之间功能的调用(实现信息沟通)。
所以当你项目打算组件化了,你就要解决组件化之后会带来的两个核心问题:
总结一下:
使用传统架构时存在的问题:
使用组件化架构时的优势:
从上面的分析中,我们知道了应用组件化的关键问题在于分出项目组件之后,如何进行组件间信息交流,即跳转。
Android提供了很多不同的信息传递方式:本地广播BroadcastReceiver,事件总线EventBus,本地存储SharedPreference等等。但回到最基础的组件化架构中,可以看到模块之间是相互独立的,它们之间并不存在任何依赖。没有依赖,就无法产生关系,没有关系,就无法传递任何信息。使用传统Android的方式势必无法完成这种交流。
组件之间没有关系1) Router路由跳转
如上所示,我们需要借助路由跳转的方式来完成组件化时各个module之间的交流。
什么是路由?
路由的概念广泛运用在计算机网络中,指路由器从一个接口上收到数据包,根据路由包的目的地址进行定向并转发到另一个接口的过程。路由器用于连接多个逻辑分开的网络。
我们需要将各个组件看作一个个不同的网络,而Router(路由器)就是连接各个模块中页面跳转的中转站。这个中转站可以拦截不安全的跳转或者设定一些特定的拦截服务。
2)Router路由器的选择
CC - 3k 业界首个支持渐进式组件化改造的Android组件化开源框架,支持跨进程调用。
CC是一套Android的组件化框架,由CC核心API类库和cc-register插件组成。
ARouter - 10k A framework for assisting in the renovation of Android componentization (帮助 Android App 进行组件化改造的路由框架) 最早出现的组件化的一个实现方案。
Component - 1k 一个强大的组件化框架。
其他开源的第三方路由器还有,ActivityRouter,DeepLinkDispatch,OkDeepLink等,选择时还是那句话,适配最重要。
如果你的项目引入了RxJava,那么考量到兼容性问题,建议使用OkDeepLink;
如果没有引入RxJava,那么Arouter的接入成本低,且框架可控性高,是首选。
3)ARouter使用示例
Github地址:https://github.com/alibaba/ARouter
① 配置依赖文件
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
② 初始化SDK,推荐在Application中完成
// 这两行必须写在init之前,否则这些配置在init过程中将无效
if (isDebug()) {
ARouter.openLog(); // 打印日志
// 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
ARouter.openDebug();
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
③ 为Activity添加注解,做好跳转标记
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
④ 发起路由操作,跳转到目标Activity
// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
目标Activity通过读取传递的intent的方式就可以获取参数了。
intent = getIntent();
String title = intent.getStringExtra("key1");
String url = intent.getStringExtra("key2");
⑤ 添加混淆文件
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep public class com.alibaba.android.arouter.facade.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
# -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
总结一下路由方式的优势:
路由跳转和原生跳转对比
组件化的缺点在于旧项目重新适配组件化的开发需要相应的人力以及时间成本。
项目体积越来越大后,必定会有超过方法数65535的一天,要么选择MultiDex的方式分包解决,要么使用插件化的方式来完成项目。组件化和模块化的划分将更好地为项目插件化开路。插件化的模块化发布和正常发布有着非常大的差异,已经脱离了组件化和模块化的构建机制,插件化有着更高效的业务解耦。
共涉及四个部分——组件:app,library_1,library_2;公共库:lib_common。项目结构如图:
因为每个组件单独运行,所以各个组件都要有完善的依赖库。这样为了库的版本相同,最好就是单独进行配置。
1)外部配置config.build,里面写好各个库的版本,进行统一依赖;
ext {
android = [
compileSdkVersion : 28,
buildToolsVersion : "29.0.0",
minSdkVersion : 17,
targetSdkVersion : 28
]
dependencies = [
"junit" : 'junit:junit:4.12',
"support-v7" : 'com.android.support:appcompat-v7:28.0.0',
"support-design" : 'com.android.support:design:28.0.0',
"constraint-layout" : 'com.android.support.constraint:constraint-layout:1.1.3',
"base-recyclerview-adapter" : 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40',
"arouter-api" : 'com.alibaba:arouter-api:1.5.0',
"arouter-compiler" : 'com.alibaba:arouter-compiler:1.2.2',
"dagger" : 'com.google.dagger:dagger:2.24',
"dagger-compiler" : 'com.google.dagger:dagger-compiler:2.24',
"okhttp" : 'com.squareup.okhttp3:okhttp:3.4.1',
"retrofit" : 'com.squareup.retrofit2:retrofit:2.1.0',
"retrofit_gson" : 'com.squareup.retrofit2:converter-gson:2.1.0',
"rxjava_retrofit" : 'com.squareup.retrofit2:adapter-rxjava:2.1.0',
"rxandroid" : 'io.reactivex:rxandroid:1.2.1',
"rxjava" : 'io.reactivex:rxjava:1.2.1',
"okhttpfinal" : 'cn.finalteam:okhttpfinal:2.0.7'
]
}
别忘了在项目根build.gradle中进行配置。
apply from : "config.gradle"
2)在建好项目后我们需要给2个组件(library_1, library_2)配置一个是否单独编译的开关;
关于开关的配置位置这是一个问题,我们把它添加在gradle.properties文件中,这样我们每次修改值的时候就可以触发gradle的重新构建,便于我们单独编译module.(也可以直接配置在项目根build.gradle中,布尔值控制方式是一样的)
3)配置3个组件之间的依赖关系;
① 首先对于lib_common而言,作为公共库,要实现底部功能,需要依赖所有的库;
dependencies {
... ...
// ARouter
compile config.dependencies["arouter-api"]
annotationProcessor config.dependencies["arouter-compiler"]
// Dragger2
api config.dependencies["dagger"]
annotationProcessor config.dependencies["dagger-compiler"]
// OkHttp
implementation config.dependencies["okhttp"]
implementation config.dependencies["okhttpfinal"]
// Rxjava + Retrofit
implementation config.dependencies["retrofit"]
implementation config.dependencies["retrofit_gson"]
implementation config.dependencies["rxjava_retrofit"]
implementation config.dependencies["rxandroid"]
implementation config.dependencies["rxjava"]
}
② 其次对于app, library_1, library_2三个组件而言,都要依赖lib_common和路由器库;
dependencies {
... ...
// ARouter 在业务组件中,需要导入路由的编译期依赖库、以及所有的基础组件
compile config.dependencies["arouter-api"]
annotationProcessor config.dependencies["arouter-compiler"]
// Common
implementation project(':lib_common')
}
③ 然后对于可单独运行组件的library_1和libary_2而言,需要单独配置applicationId和Manifest文件;
if (!isNeedLib1Module.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
def config = rootProject.ext
android {
... ...
defaultConfig {
if (!isNeedLib1Module.toBoolean()) {
applicationId "com.cymchad.lib1.bottomsheet"
}
... ...
}
... ...
// 设置AndroidManifest根据开关进行修改
sourceSets {
main {
if (!isNeedLib1Module.toBoolean()) {
manifest.srcFile 'src/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
exclude '**/debug/**'
}
}
}
}
}
关于Mainfest文件,我们选择多配置一份,为了可以单独运行,注意对主Activity的注册。
4)对于主App而言,要控制library_1和Library_2的运行状态;
dependencies {
... ...
// 在壳中,导入各业务组件
if (isNeedLib1Module.toBoolean()) {
implementation project(':library_1')
}
if (isNeedLib2Module.toBoolean()) {
implementation project(':library_2')
}
// 万能适配器
implementation config.dependencies["base-recyclerview-adapter"]
// ARouter 在业务组件中,需要导入路由的编译期依赖库、以及所有的基础组件
compile config.dependencies["arouter-api"]
annotationProcessor config.dependencies["arouter-compiler"]
// Common
implementation project(':lib_common')
}
至此,借由路由器实现的组件化架构就搭建完毕。如果我们设置好gradle.properties下的开关,就可以单独运行某一组件。
单独运行的组件,若没有设置桌面图标,系统会Android小机器人的图标代替使用。然后会直接进入到组件中注册好的Activity中。
项目地址:https://github.com/shenbuqingyun/Component_ARouter 需要的可以参考一下。