Android组件化、模块化实现
前言
移动端平台不断发展,不断迭代更新,APP软件越来越复杂和庞大,维护和更新亦是如此。为了解决这些问题,降低软件的复杂性和耦合度,同时提高开发效率,模块化在移动端就变得势在必行。
模块化理解
模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体,完成整个系统所要求的功能。
通过以下类比可以更好地理解什么是模块化:
我们可以把软件看做是一辆汽车,开发一款软件的过程就是生产一辆汽车的过程。一辆汽车由车架、发动机、变数箱、车轮等一系列模块组成;同样,一款大型商业软件也是由各个不同的模块组成的。
汽车的这些模块是由不同的工厂生产的,一辆 BMW 的发动机可能是由位于德国的工厂生产的,它的自动变数箱可能是 Jatco(世界三大变速箱厂商之一)位于日本的工厂生产的,车轮可能是中国的工厂生产的,最后交给华晨宝马的工厂统一组装成一辆完整的汽车。这就类似于我们在软件工程领域里说的多团队并行开发,最后将各个团队开发的模块统一打包成我们可使用的 App 。
一款发动机、一款变数箱都不可能只应用于一个车型,比如同一款 Jatco 的 6AT 自动变速箱既可能被安装在 BMW 的车型上,也可能被安装在 Mazda 的车型上。这就如同软件开发领域里的模块重用。
到了冬天,特别是在北方我们可能需要开着车走雪路,为了安全起见往往我们会将汽车的公路胎升级为雪地胎;轮胎可以很轻易的更换,这就是我们在软件开发领域谈到的低耦合。一个模块的升级替换不会影响到其它模块,也不会受其它模块的限制;同时这也类似于我们在软件开发领域提到的可插拔。
模块化优缺点
优点
- 架构灵活,焦点分离
- 耦合低,模块间可自由组合、分解
- 方便单个模块功能调试、升级、测试,提升开发效率
- 多人协作只负责单独模块,互不干扰
缺点
- 系统分层,调用链会很长
- 模块间发送消息对比较损耗性能
模块化分层
组件和模块区别定义
- 组件:指的是单一的功能组件,地图组件、支付组件、分享组件等功能;
- 模块:指的是独立的业务模块,如首页模块、聊天模块、播放模块等,模块相对于组件来说粒度更大。
整个项目分为四层,从下至上分别是:
(1)宿主层:不做具体的项目功能实现,只负责集成业务模块,组装成一个完整的APP;
(2)业务模块层:将项目的每个大功能模块拆分成的一个一个单独的module,可独立运行,同产品的不同项目也可复用;
(3)业务组件层:用于业务模块间调用,例如支付组件 、地图组件、分享组件等等;
(4)基础组件层:基础组件层,与业务无关,与项目也无关,所有项目都可以全部复用,包含了各种开源库以及和业务无关的各种自研工具库;
模块化分层,其实就是将业务模块层的各个功能业务拆分层独立的业务模块;进行模块化的第一步就是业务模块划分,划分的粒度需要根据项目情况进行合理把控,这就需要对业务和项目有较为透彻的理解。
模块化过程
1、每个单独的Module 都可以单独作为Application编译成 APK运行,同时也可以作为依赖包Library来整体编译打包。于是,需要在每个Module下的 build.gradle
中加入如下代码:
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
isModule作为开关去控制是否为Application后者Library,只需要在项目gradle.properties
文件中加入如下配置:
isModule=false
2、作为Application时清单文件需要设置Application属性及启动Activity等,而Library则简单很多,因此就需要两套不同的AndroidManifest.xml
。同样,只要在每个Module下的 build.gradle
的android配置中加入如下设置:
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//release模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
}
}
}
保持原先拷贝一份AndroidManifest.xml
到新建的debug目录下即可,如图所示:
main目录下作为Library清单文件:
<application>
<activity android:name=".BottomAlignmentActivity" />
</application>
debug目录下作为Application中清单文件:
<application
android:icon="@drawable/ic_author"
android:label="@string/advancedtextview_app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity
android:name=".BottomAlignmentActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
3、宿主APP在Application和Library模式下,进行不同依赖配置,:
if (!isModule.toBoolean()) {
implementation project(path: ':advancedtextview')
}else{
implementation project(":libbase")
}
4、模块间跳转
宿主工程中依赖的库是可以直接引用的,通过startActivity
跳转,但组件之间不可以依赖。因此,当常规业务模块之间需要跳转引用,改如何处理呢?
- 隐式Intent,要跳转的活动在
Manifest.xml
中声明匹配规则,然后调用
Intent intent = new Intent(Intent.ACTION_VIEW, "<scheme>://<host>:<port>/<path>");
startActivity(intent);
- 利用反射
Class clazz=Class.fromName("com.nianlun.expample.MainActivity");
startActivity(this,clazz);
- 使用路由,这里推荐阿里的ARouter
一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。
Arouter使用
-
添加依赖和配置
android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } } dependencies { api 'com.alibaba:arouter-api:x.x.x' annotationProcessor 'com.alibaba:arouter-compiler:x.x.x' ... }
-
添加注解
// 在支持路由的页面上添加注解(必选) // 这里的路径需要注意的是至少需要有两级,/xx/xx @Route(path = "/test/activity") public class YourActivity extend Activity { ... }
-
初始化SDK
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效 ARouter.openLog(); // 打印日志 ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) } ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
-
发起路由操作
// 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();
更多进阶用法,可以跳转官方GitHub网站进行文档查看:https://github.com/alibaba/ARouter/blob/master/README_CN.md
问题解决
资源名冲突的问题,可以通过在 build.gradle
定义前缀的方式解决:
defaultConfig {
...
resourcePrefix "module_name_"
...
}
参考资料:Android 模块化探索与实践
以上就是模块化的基本过程和实现,具体项目中我们还是要理清业务之间的关系,进行具体划分,提取公共依赖,剔除冗余代码,逐步进行重构。
借用以前的代码例子,模块化处理了一下,采用Arouter进行跳转,具体可以查看我的Github查看,地址如下
https://github.com/MickJson/DevelopmentRecord
欢迎点击查阅及Star,我也会继续补充其它有用的知识及例子在项目上。
欢迎点赞/评论,你们的赞同和鼓励是我写作的最大动力!