单工程深入挖掘,项目规模不断扩张,问题也逐渐显现。这时,或许应当考虑进行模块化改革。这样的转变是众多开发者共同经历的,其中既有机遇也有挑战。
为什么要开始组件化
单独进行工程开发,一旦项目规模扩大,麻烦随之增多。比如某家公司,起初为了迅速推出项目,选择了单一工程模式,但随着时间的推移,每次对功能进行微调,都需要在庞大的工程中费时寻找,极大地影响了开发速度。在开发过程中,累积的代码容易导致混乱。这就像一个杂乱无章的储藏室,东西随意堆放,需要用时难以寻觅。
项目在单一工程模式中运行时间过长,代码的维护难度极大。我的一位朋友就遇到过这样的情况,新加入的开发人员要花很多时间去理解那些老旧的代码。在这种情况下,采用组件化设计似乎是一条可行的道路。
模块化与组件化辨析
模块
程序被按照功能划分成了模块。例如,社交软件中的登录模块,它主要负责用户登录系统的验证和互动。而首页模块,就像商场的前窗,主要用来展示各类信息。这两个模块功能清晰,且互不干扰。这种拆分使得每个模块都能各尽其责。
组件
组件的功能趋向单一化。比如视频应用中的视频模块,主要聚焦于视频播放的各个环节。而支付模块则独立处理支付的安全性和流程问题。此外,这些组件可以独立进行开发,就像小型工作室那样闭门造车,完成后再对外发布成SDK。通常,一个模块中会包含一个或多个这样的组件。
组件化的好处
减少耦合性
组件化的主要好处在于它以可重复利用为核心,减少了相互依赖。在众多大型项目中,一处变动往往会导致全局动摇。然而,在实施组件化之后,各个组件之间相对独立。比如我参与过的那个办公软件项目,在应用了组件化之后,当我们需要对某个业务逻辑进行修改时,只需对相应的组件进行调整,对其他部分的干扰极小。
独立开发
不同的开发团队或个人可以并行开展不同模块的开发工作,这显著提升了开发效率。例如,一些规模较大的团队会按模块划分,分别专注于用户界面交互模块,以及数据处理模块。
//构建后输出一个 APK 安装包
apply plugin: 'com.android.application'
//构建后输出 ARR 包
apply plugin: 'com.android.library'
//配置一个 Android Test 工程
apply plugin: 'com.android.test'
组件化要面临的问题
组件间的依赖关系
//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换
if(isDebug.toBoolean()){
//构建后输出一个 APK 安装包
apply plugin: 'com.android.application'
}else{
//构建后输出 ARR 包
apply plugin: 'com.android.library'
}
在组件的层级结构中,上层组件对下层组件有依赖,同时上层的修改次数较多,这种情况带来了一定的风险。若上层组件频繁变动,下层组件便需作出相应的调整。以一个电商App的组件化改造项目为例,前端界面这一上层组件变动频繁,这就要求底层数据处理组件持续调整接口以实现适配。
项目结构混乱的风险
若组件界限模糊,会使项目架构变得更加杂乱无章。就像刚开始学习拼图,若拼块放置错误,拼图时便会出现多处不匹配的情况。
组件分层的具体策略
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.scc.module.collect">
<application
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/Theme.SccMall">
<activity android:name=".CollectActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
合理划分基础组件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.scc.module.collect">
<application
android:allowBackup="true"
android:supportsRtl="true"
>
<activity android:name=".CollectActivity"/>
</application>
</manifest>
项目若因基础组件过多而显得庞大,不妨另设一个层级,并在其中进一步划分这些基础组件。我的同事在处理项目时就采取了这种做法,之前基础组件杂乱无章,后来经过清晰分层,开发效率显著提升。
构建类型的动态配置
defaultConfig {
if(isDebug.toBoolean()){
//独立调试的时候才能设置applicationId
applicationId "com.scc.module.collect"
}
}
sourceSets {
main {
if (isDebug.toBoolean()) {
//独立调试
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
//集成调试
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
工程类型构建
在项目开发过程中,我们需借助相应工具进行搭建,例如在Android开发中采用Gradle,同时还有众多插件可供选择。这些插件用于构建各式各样的工程。这一流程颇似厨师烹饪时选用不同的厨具。
动态配置build文件
与创建插件相似,构建文件同样需要灵活调整。这在组件化开发中尤为关键。就好比建筑师根据不同的建筑要求来修改图纸。
组件化的最终效果
独立调试
升级插件或依赖库版本时,若项目众多,修改起来颇为棘手。采用组件化设计后,每个组件可独立调试。这就像为每个组件配备了一个独立的小实验室,可以单独进行升级实验,彼此之间互不干扰。
界面跳转解决方案
ext{
//组件独立调试开关, 每次更改值后要同步工程
isDebug = true
android = [
// 编译 SDK 版本
compileSdkVersion: 31,
// 最低兼容 Android 版本
minSdkVersion : 21,
// 最高兼容 Android 版本
targetSdkVersion : 31,
// 当前版本编号
versionCode : 1,
// 当前版本信息
versionName : "1.0.0"
]
applicationid = [
app:"com.scc.sccmall",
main:"com.scc.module.main",
webview:"com.scc.module.webview",
login:"com.scc.module.login",
collect:"com.scc.module.collect"
]
dependencies = [
"appcompat" :'androidx.appcompat:appcompat:1.2.0',
"material" :'com.google.android.material:material:1.3.0',
"constraintlayout" :'androidx.constraintlayout:constraintlayout:2.0.1',
"livedata" :'androidx.lifecycle:lifecycle-livedata:2.4.0',
"viewmodel" :'androidx.lifecycle:lifecycle-viewmodel:2.4.0',
"legacyv4" :'androidx.legacy:legacy-support-v4:1.0.0',
"splashscreen" :'androidx.core:core-splashscreen:1.0.0-alpha01'
]
libARouter= 'com.alibaba:arouter-api:1.5.2'
libARouterCompiler = 'com.alibaba:arouter-compiler:1.5.2'
libGson = 'com.google.code.gson:gson:2.8.9'
}
界面跳转的困难
apply from:"config.gradle"
在Android系统中,界面切换原本操作简便。然而,由于组件化开发中各组件之间没有依赖关系,无法直接访问类,因此无法继续采用原有的直接跳转方法。
使用ARouter框架解决
//build.gradle
//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换
if(isDebug.toBoolean()){
//构建后输出一个 APK 安装包
apply plugin: 'com.android.application'
}else{
//构建后输出 ARR 包
apply plugin: 'com.android.library'
}
android {
compileSdkVersion 31
defaultConfig {
if(isDebug.toBoolean()){
//独立调试的时候才能设置applicationId
applicationId "com.scc.module.collect"
}
minSdkVersion 21
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (isDebug.toBoolean()) {
//独立调试
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
//集成调试
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
// implementation root.dependencies.appcompat
// implementation root.dependencies.material
// implementation root.dependencies.constraintlayout
// implementation root.dependencies.livedata
// implementation root.dependencies.viewmodel
// implementation root.dependencies.legacyv4
// implementation root.dependencies.splashscreen
// implementation root.libARouter
//上面内容在lib_common中已经添加咱们直接依赖lib_common
implementation project(':lib_common')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
ARouter能助力App实现组件化升级,实现模块间的路由、通讯及解耦。使用时需先设置依赖,接着初始化SDK,随后即可进行路由操作。以资讯类App为例,应用此框架后,组件间的切换变得非常流畅。
互动环节
我已经将实现单工程转组件化的关键点进行了介绍,大家在实际尝试组件化过程中,面临的最大难题是什么?期待大家为这篇文章点赞并转发,同时也欢迎在评论区留下你们的见解和讨论。