文章目录
- [1. 组件化概述](#1. 组件化概述)
- [2. 组件化的一般结构](#2. 组件化的一般结构)
- [3. 组件化的流程](#3. 组件化的流程)
-
- [3.1 组件模式和集成模式的转换](#3.1 组件模式和集成模式的转换)
- [3.2 统一管理项目依赖库](#3.2 统一管理项目依赖库)
-
- [3.2.1 Gradle Version Catalog(TOML 方式)](#3.2.1 Gradle Version Catalog(TOML 方式))
- [3.2.2 config.gradle(传统 Groovy 方式)](#3.2.2 config.gradle(传统 Groovy 方式))
- [3.3 组件之间AndroidManifest合并问题](#3.3 组件之间AndroidManifest合并问题)
- [3.4 Application的获取](#3.4 Application的获取)
- [3.5 组件之间调用和通信](#3.5 组件之间调用和通信)
- [3.6 组件之间资源名冲突](#3.6 组件之间资源名冲突)
- [4. 总结](#4. 总结)
1. 组件化概述
如果一个APP所有的代码和界面都写在同一个模块,即app模块,那么就会造成很多的问题:整个模块变得非常庞大,编译时电脑会非常卡,代码之间耦合严重等。而且如果涉及到多人协作开发,那么每个人都要熟悉如此之多的代码,增加项目的维护成本。又因为单一工程下的代码耦合严重,每修改一处代码都要重新编译打包运行,导致非常耗时,所以就有了模块化和组件化的概念。
模块:将一个程序按照其功能做拆分,分成相互独立的模块,以便每个模块只包含与其功能相关的内容,比如登录模块、首页模块等等。
组件:组件指的是单一的功能组件,如登录组件、视频组件、支付组件等,每个组件都可以以一个单独的module开发,并且可以单独抽出来作为SDK对外发布使用。可以说往往一个模块包含了一个或多个组件。
那么组件化有什么优势呢?
组件化基于可重用的目的,将应用拆分成多个独立组件,以减少耦合。
-
提升编译与构建速度:只需编译当前修改的模块,同时Gradle支持多模块并行编译,进一步缩短了构建时间。
-
降低代码耦合,提升可维护性:模块间通过接口、路由或事件通信、而不是直接依赖实现类,代码结构清晰,职责单一,便于理解和重构。
-
提升开发效率:不同业务模块由不同成员负责,互不干扰。各模块可独立开发、测试,减少Git冲突。
-
增强功能复用能力:类似引用的第三方库,可以将基础组件或功能剥离。在新项目中微调或直接使用。
2. 组件化的一般结构
组件化工程模型,

组件间的依赖关系是上层依赖下层,修改频率是上层高于下层。
-
APP壳工程 :项目的"空壳"主模块,在这个模块中,不包含任何具体业务逻辑 ,但是要依赖所有需要集成的业务组件,并且声明全局
Application。简单来说,它是最终APK的"容器",负责组装各组件。 -
集成模式:所有业务组件以library(库)形式被"app壳工程"依赖,最终合并打包成一个完整的APK,整个App是一个统一的整体,各业务组件不能独立运行。
-
组件模式 :每个业务组件可作为独立的
Application运行,拥有自己的入口(Launcher Activity)和Application类,通常通过gradle.properties中的开关(如isModule=false)控制。支持单独开发、调试、测试某个功能模块。因为无需构建整个项目,编译速度极快。 -
业务组件:按业务功能划分的独立模块,如"商品详情","订单管理"等。在组件模式下是完整App(有Launcher Activity、Application),在集成模式下是library,通过路由(ARouter)与其他组件通信。
-
功能组件:提供通用技术能力的模块,与具体业务无关。如网络请求封装(Retrofit + OkHttp),图片加载(Glide)等。注:功能组件始终作为library,不可独立运行。
-
Main组件:一种特殊的业务组件,包含App的启动页(Splash)、登录页、主界面。
-
Common组件:一种特殊的功能组件,是整个项目的地基。主要作用有:统一管理第三方库依赖,声明App需要的权限,封装通用工具类,存放全局资源等
解释完上面一些基本的概念,再来看看这张组件化工程模型图。由图可知,业务组件之间是独立的,并没有相互依赖关系,这些业务组件在集成模式下是一个个library,被app壳工程所依赖,组成一个具有完整功能的App。但是在组件模式下,业务组件又变成了一个个Application,它们可以独立开发调试,由于在组件模式下,业务组件的代码量向比于完整项目差了很远,因此在编译运行时可以显著减少时间。
解决了组件分层问题,现在又有新的问题产生了:由于各个业务模块相互独立,那么怎么实现一个模块中的某个页面跳转到另一个模块呢?也就是组件之间是如何进行通信的?

这张图展示了组件化工程模型下的业务关系,由于业务之间不再直接引用和依赖,传统方式无法实现组件之间的通信。而是引入了"路由"的概念,通过"路由"这样一个中转站间接产生联系。
3. 组件化的流程
3.1 组件模式和集成模式的转换
Android项目中Module主要有两种属性,分别是:
-
application属性,可以单独调试的Android程序:
groovyapply plugin: 'com.android.application' -
library属性,不能独立运行,一般是Android程序依赖的库文件:
groovyapply plugin: 'com.android.library'
每个模块的build.gradle下都应定义这两个属性,根据一个总的开关来判断此时应该是组件模式还是集成模式。这个总开关定义在gradle.properties(Android项目的根目录下),这个文件的一个重要的属性:在Android项目中任何一个build.gradle文件中都可以把gradle.properties中的常量读出来。
在gradle.properties中定义一个常量值表示是否是组件模式开发(true表示是,false表示否):
groovy
# 每次修改后,记得点击"Sync Project"同步
isModule=false
然后在业务组件的build.gradle中读取isModule总开关,但是由于gradle.properties中的数据类型都是String类型,而我们需要的是Boolean值,所以需要转换一下:
groovy
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
3.2 统一管理项目依赖库
为了方便我们统一管理第三方库,提供统一的依赖第三方库的入口,主要有两种主流方式,分别为Gradle Version Catalog(TOML 方式) ,config.gradle(传统 Groovy 方式) 。第一种方式建议用于新项目或已升级构建环境的项目,传统Groovy方式兼容旧项目,无需升级Gradle,适合Java/Groovy项目
3.2.1 Gradle Version Catalog(TOML 方式)
在项目根目录下创建gradle/lib.versions.toml
toml
[versions]
# 所有版本号集中定义在这里,使用语义化命名
agp = "8.13.2" # Android Gradle Plugin
compileSdk = "36"
minSdk = "21"
targetSdk = "36"
# 第三方库版本
retrofit = "2.11.0"
glide = "4.16.0"
arouter = "1.5.2"
junit = "5.10.0"
androidx-core = "1.12.0"
okhttp = "4.12.0"
[libraries]
# 格式:alias = { group = "...", name = "...", version.ref = "..." }
# 或直接写 version(不推荐,不利于统一管理)
# AndroidX
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-core" }
# 网络
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
# 图片加载
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
# 路由
arouter-api = { group = "com.alibaba", name = "arouter-api", version.ref = "arouter" }
arouter-compiler = { group = "com.alibaba", name = "arouter-compiler", version.ref = "arouter" }
# 测试
junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
[plugins]
# 插件别名(用于 plugins {} 块)
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
分析:
-
[versions]下只定义版本字符串,比如retrofit = "2.11.0",方便统一管理版本号 -
[libraries]下定义第三方库别名,一般格式:alias = { group = "...", name = "...", version.ref = "..." },alias是在代码中引用该依赖时所使用的别名,比如传统引入依赖:com.squareup.retrofit2:retrofit:2.11.0在这里就可以这样写:retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" },version.ref就是上面在[version]中定义的版本号。 -
[plugins]中定义插件别名,基本思想与[libraries]一致
关于引入依赖:
在Groovy DSL(build.gradle)中:
groovy
implementation libs.retrofit
在Kotlin DSL(build.gradle.kts)中:
kotlin
implementation(libs.retrofit)
3.2.2 config.gradle(传统 Groovy 方式)
-
在项目根目录下创建
config.gradle:groovyext { // Gradle中的一个扩展快,允许在项目范围内定义额外的属性或对象 // Android SDK 配置 android = [ compileSdkVersion: 36, minSdkVersion : 21, targetSdkVersion : 34, versionCode : 100, versionName : "1.0.0" ] // 所有第三方库的版本号集中定义 versions = [ appcompat : "1.6.1", constraintlayout: "2.1.4", junit : "4.13.2", junit5 : "5.10.0", mockito : "5.7.0", retrofit : "2.11.0", gson : "2.10.1", okhttp : "4.12.0", glide : "4.16.0", arouter : "1.5.2" ] // 依赖库完整坐标(group:name:version) // 使用versions中定义的版本号来构建完整的依赖字符串 deps = [ // AndroidX appcompat : "androidx.appcompat:appcompat:${versions.appcompat}", constraintlayout : "androidx.constraintlayout:constraintlayout:${versions.constraintlayout}", // Test junit4 : "junit:junit:${versions.junit}", junit5 : "org.junit.jupiter:junit-jupiter:${versions.junit5}", mockito : "org.mockito:mockito-core:${versions.mockito}", // Network retrofit : "com.squareup.retrofit2:retrofit:${versions.retrofit}", retrofitGson : "com.squareup.retrofit2:converter-gson:${versions.retrofit}", okhttpLogging : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}", // Image glide : "com.github.bumptech.glide:glide:${versions.glide}", // Router arouterApi : "com.alibaba:arouter-api:${versions.arouter}", arouterCompiler : "com.alibaba:arouter-compiler:${versions.arouter}" ] }可以利用Groovy中的字符串插值
${}避免重复写版本号,这样后续如果要修改版本号,只用修改一行。 -
构建好
config.gradle后,在settings.gradle中引入该配置:groovy// 应用 config.gradle 脚本 apply from: 'config.gradle'这样
ext中定义的内容就会成为rootProject的扩展属性,所有子模块都能访问 -
在各个模块的
build.gradle中使用:groovy// app/build.gradle android { compileSdk rootProject.ext.android.compileSdkVersion defaultConfig { minSdk rootProject.ext.android.minSdkVersion targetSdk rootProject.ext.android.targetSdkVersion versionCode rootProject.ext.android.versionCode versionName rootProject.ext.android.versionName } } dependencies { implementation rootProject.ext.deps.appcompat implementation rootProject.ext.deps.retrofit implementation rootProject.ext.deps.retrofitGson implementation rootProject.ext.deps.glide annotationProcessor rootProject.ext.deps.arouterCompiler testImplementation rootProject.ext.deps.junit4 androidTestImplementation rootProject.ext.deps.junit5 }
3.3 组件之间AndroidManifest合并问题
在组件化开发中,当项目处于组件模式时,每个业务模块需要一个完整的AndroidManifest.xml,包含<application>和启动Activity(带LAUNCHER),才能作为独立App运行。在集成模式时,最终APK只能有一个主AndroidManifest.xml(来自App壳工程),其他模块的AndroidManifest.xml需要被合并进去,且不能包含冲突的内容(如多个LAUNCHER,重复的Application等)。
通用做法是使用多套Manifest文件,通过Gradle配置动态切换,核心思想就是为每个业务模块准备两套AndroidManifest.xml,一套用于独立运行,一套用于集成。通过Gradle动态指定使用哪一套。
比如,我现在有两个模块:

分别是common、module_user两个模块,在module_user下创建manifest文件夹,这个清单文件是模块单独运行时的清单文件,包含Application和启动页

xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name="debug.UserDebugApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ARouter">
<activity
android:name=".LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
当前模块只有一个LoginActivity,Application是在当前模块包下创建的UserDebugApplication:
java
// debug/UserDebugApplication
package debug;
import com.example.common.BaseApplication;
public class UserDebugApplication extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
}
}
只用继承BaseApplication,重写onCreate方法,作为module_user模块独立运行时的Application。关于全局Context的管理,会在下一个小节详细介绍。
而main模块下的AndroidManifest只注册当前模块的四大组件:
xml
<!--src/main/AndroidManifest.xml-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.module_user">
<application>
<activity
android:name=".LoginActivity"
android:exported="false" />
</application>
</manifest>
这样,就有了不同模式下的AndroidManifest文件,使用Gradle动态配置清单文件:
groovy
// ...
android {
// ...
sourceSets {
main {
manifest.srcFile isModule ?
'src/main/AndroidManifest.xml' :
'src/main/manifest/AndroidManifest.xml'
}
}
// ...
}
// ...
当isModule为true,表示当前处于组件模式开发,在build.gradle中指定清单文件为src/main/AndroidManifest.xml,编译器会自动合并到app模块下的清单文件中。否则作为一个独立的APK运行,使用src/main/manifest/AndroidManifest.xml。
3.4 Application的获取
在组件化开发中,每个模块(module)可能是一个独立的library或Application。不同模块独立运行时要正确获取到主App的Application实例,才能正确被打包编译。另外,在自己组件中开发时需要获取全局的Context ,这时就可以在common模块中引入BaseApplication,所有业务组件和主App的Application都继承自common模块中的BaseApplication。这样无论在哪种模式下,只要最终的Application继承了BaseApplication,就能通过它安全地获取到全局Context。
那么怎样控制集成模式下模块自己的Application不会和主App地Application冲突呢?这主要是利用Gradle地sourceSets和isModule标志,在不同模式下加载不同的AndroidManifest.xml和Java代码(排除debug/**包)
-
在common模块中新建
BaseApplication,提供全局单例,数据初始化等操作:javapackage com.example.common; import android.app.Application; import android.content.Context; import com.alibaba.android.arouter.launcher.ARouter; public class BaseApplication extends Application { private static BaseApplication instance; @Override public void onCreate() { super.onCreate(); instance = this; // 直接开启日志和调试模式,不依赖 BuildConfig.DEBUG(library的DEBUG恒为false) ARouter.openLog(); ARouter.openDebug(); // 初始化ARouter ARouter.init(this); } // 获取全局Context public static Context getAppContext() { return instance.getApplicationContext(); } }注意,这里ARouter的初始化不要对
BuildConfig.DEBUG进行判断,因为library的DEBUG恒为false,否则模块在单独调试时ARouter初始化不成功,会导致应用崩溃。 -
在业务组件(
module_user)中新建实例Application继承common中的BaseApplication:
javapackage debug; import com.example.common.BaseApplication; public class UserDebugApplication extends BaseApplication { @Override public void onCreate() { super.onCreate(); } } -
在模块单独调试的Manifest中指定Application为我们自己新建的Application:
xml<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application android:name="debug.UserDebugApplication" ...> ... </application> </manifest> -
在业务模块中的
build.gradle中使用sourceSets和isApplication指定两种模式下的AndroidManifest.xml:groovyandroid { sourceSets { main { manifest.srcFile isApplication ? 'src/main/manifest/AndroidManifest.xml' : 'src/main/AndroidManifest.xml' // 在集成模式下排除debug文件夹 if (!isApplication) { java.exclude 'debug/**' } } } }
3.5 组件之间调用和通信
在组件化开发时,组件之间时没有依赖关系的,我们无法使用显示调用来跳转页面,因为组件化的目的之一就是解决组件之间的强依赖关系。那么怎么实现组件之间的调用和通信呢,这个时候就需要引入"路由"的概念了,在第一节提到过,路由就是起到一个转发的作用。
主流做法是阿里巴巴开源的ARouter路由框架,用于组件化架构下的模块间通信。它通过注解 + APT (Annotation Processing Tool)在编译期自动生成路由映射表,避免了反射带来的性能损耗。
一般步骤:
-
添加依赖,首先在
lib.versions.toml中添加版本号和libraries:toml[versions] // ... arouter-api = "1.5.2" arouter-compiler = "1.5.2" // ... [libraries] arouter-api = { group = "com.alibaba", name = "arouter-api", version.ref = "arouter-api" } arouter-compiler = { group = "com.alibaba", name = "arouter-compiler", version.ref = "arouter-compiler" } // ...建议是在
common组件中添加依赖(common/build.gradle):groovydependencies { // ARouter依赖 api libs.arouter.api // Arouter注解器依赖 annotationProcessor libs.arouter.compiler // ... }最后,在其他模块依赖
common模块,添加注解器参数:groovyandroid { // ... defaultConfig { // ... // 配置ARouter注解器参数 javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } // ... } } dependencies { api libs.arouter.api annotationProcessor libs.arouter.compiler implementation project(':common') // ... }其中,配置注解器参数部分,是因为
ARouter在多模块项目中工作时,需要知道每个模块的唯一名称,以便在编译期为每个模块生成独立的路由映射文件,如果不指定模块名,或者多个模块使用了相同的模块名,会导致路由注册冲突或找不到页面的问题。因此,在每个使用了ARouter注解(如@Route)的模块的build.gradle中,都需要通过AROUTER_MODULE_NAME明确指定该模块的名称。注:
-
虽然在common模块中添加了arouter和annotationProcessor注解器依赖,但为了防止编译报错,需要在每个添加了Route注解的模块下添加这两个依赖。
-
project.getName()可以自动获取当前模块的目录名作为模块名,避免硬编码。
至此,业务组件中的完整
build.gradle:groovy// 判断gradle.properties中是否存在isMOdule字段 def isApplication = project.hasProperty('isModule') && project.property('isModule').toBoolean() // 根据isModule字段决定当前模块是以集成模式还是组件模式运行 if (isApplication) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' } android { namespace 'com.example.module_user' compileSdk libs.versions.compileSdk.get().toInteger() defaultConfig { // 如果是组件模式,指定Application if (isApplication) { applicationId "com.example.module_user" } minSdk libs.versions.minSdk.get().toInteger() targetSdk libs.versions.targetSdk.get().toInteger() testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" // ARouter注解器配置 javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } // 组件模式启用manifest下完整的清单文件 sourceSets { main { manifest.srcFile isApplication ? 'src/main/manifest/AndroidManifest.xml' : 'src/main/AndroidManifest.xml' // 集成模式下排除debug文件夹 if (!isApplication) { java.exclude 'debug/**' } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } } dependencies { // 依赖基础common模块 implementation project(':common') // ARouter和注解器依赖 api libs.arouter.api annotationProcessor libs.arouter.compiler implementation libs.appcompat implementation libs.material implementation libs.activity implementation libs.constraintlayout } -
-
初始化
ARouter在common模块下的
BaseApplication中初始化:javapackage com.example.common; import android.app.Application; import android.content.Context; import com.alibaba.android.arouter.launcher.ARouter; public class BaseApplication extends Application { private static BaseApplication instance; @Override public void onCreate() { super.onCreate(); instance = this; // 直接开启日志和调试模式,不依赖 BuildConfig.DEBUG(library的DEBUG恒为false) ARouter.openLog(); ARouter.openDebug(); // 初始化ARouter ARouter.init(this); } // 获取全局Context public static Context getAppContext() { return instance.getApplicationContext(); } } -
在common模块中定义路由路径(
RouterPath),统一管理所有模块的ARouter路径。由于这里我只有一个模块,所有就只有一个路径:java// common/RouterPath public class RouterPath { public static class User { public static final String LOGIN = "/user/login"; } }如果模块较多,建议按业务模块分内部类,结构清晰。
-
目标页面添加注解:
java// 用户模块中的LoginActivity import com.alibaba.android.arouter.facade.annotation.Route; import com.example.common.RouterPath; // 添加注解 @Route(path = RouterPath.User.LOGIN) public class LoginActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_login); } }路径必须以
/开头,且至少两级(如/user/login),否则编译报错。 -
实现组件之间的跳转和参数传递
在其他模块的Activity或Fragment中,如果要跳转到用户模块的
LoginActivity中:java// 跳转到登录页 ARouter.getInstance() .build(RouterPath.User.LOGIN) .navigation(); // 带参数跳转 ARouter.getInstance() .build(RouterPath.User.LOGIN) .withString("userId", "12345") .withInt("age", 25) .navigation(); // 获取返回结果(startActivityForResult) ARouter.getInstance() .build(RouterPath.User.LOGIN) .navigation(this, 1001); // requestCode = 1001如果要在目标页面拿到传递的数据:
java@Route(path = RouterPath.User.LOGIN) public class LoginActivity extends AppCompatActivity { @Autowired String userId; @Autowired(name = "age") // 可指定key int userAge; private static final String TAG = "ARouter"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_login); // 自动注入参数 ARouter.getInstance().inject(this); Log.d(TAG, "userId = " + userId + ", age = " + userAge); } }使用
@Autowired标记要接收的字段,在onCreate()中调用ARouter.getInstance().inject(this);接收字段,字段名需与
withXxx("key", value)中的key一致(或通过name指定)日志结果:

-
跨组件服务调用(IProvider)
在非组件化项目中,如果模块A需要调用模块B的某个功能(比如获取用户信息),通常会直接引用B的类,但是这就导致编译时强依赖,无法独立编译模块、修改B模块可能影响A模块、不利于并行开发和单元测试。而跨组件服务调用就解决了这个问题,ARouter服务通过"接口+路由"解耦:A模块只依赖接口(定义在common模块),B模块提供实现,两者无直接代码依赖。
举个例子,订单模块需要显示当前登录用户的昵称。这里我就不新建模块了,直接在app模块中显示用户昵称。大致实现思路:
common模块定义IUserService接口,module_user模块实现该接口,提供getUserName();,app模块通过ARouter获取服务并调用。这样做的优点在于app模块无需知道用户数据如何存储(
SharedPreferences/Room/ 网络),只需调用接口。在common模块中定义服务接口:
javaimport com.alibaba.android.arouter.facade.template.IProvider; public interface IUserService extends IProvider { String getNameByID(String id); void login(); }在
module_user中实现该服务:javaimport android.content.Context; import com.alibaba.android.arouter.facade.annotation.Route; import com.example.common.IUserService; @Route(path = "/service/user") public class UserServiceImpl implements IUserService { @Override public String getNameByID(String id) { return "User_" + id; } @Override public void login() { // 登录逻辑 } @Override public void init(Context context) { // ARouter会自动调用 } }其他任何模块如果要获取用户名称,只需使用
ARouter调用:javaIUserService userService = (IUserService) ARouter.getInstance() .build("/service/user") .navigation(); if (userService != null) { String name = userService.getNameByID("12345"); Log.d(TAG, "name = " + name); }或者使用更简洁的方式:
javaIUserService userService = ARouter.getInstance().navigation(IUserService.class);查看日志,成功获取到用户名称:

3.6 组件之间资源名冲突
因为拆分出了很多业务组件和功能组件,在把这些组件合并到app壳工程时就有可能出现资源名冲突问题。比如A组件和B组件都有一张叫做"ic_back"的图标,这时候在集成模式下打包app就会编译出错,解决这个问题最简单的做法就是在项目中约定资源文件命名规则,比如强制使每个资源文件的名称以组件名开始,这个可以根据实际情况和开发人员制定规则。当然Gradle也提供了解决办法,通过在组件的build.gradle中添加如下的代码:
groovy
android {
resourcePrefix "user_" // 注意:必须以小写字母开头,结尾加下划线
}
规则:
- 必须是小写字母开头。
- 通常以模块名 +
_结尾(如user_,order_)- 所有
res/下的资源(layout,drawable,string)等必须以此开头,否则编译报错。
4. 总结
- 架构分层:自顶向下为App壳工程、业务组件、功能组件、Common基础库,上层依赖下层,实现解耦。
- 模式切换 :通过
gradle.properties中的isModule开关,控制业务组件在可独立运行的组件模式 与作为依赖库的集成模式间切换。 - 统一配置:推荐使用Gradle Version Catalog (TOML) 统一管理所有依赖版本,避免冲突。
- 清单与全局Context :采用双套
AndroidManifest.xml解决清单合并冲突;通过Common模块的BaseApplication提供统一的全局Context。 - 通信方案 :引入ARouter路由框架 ,通过注解和路径实现跨组件页面跳转与参数传递;通过
IProvider接口实现跨组件服务调用,彻底解耦。 - 工程细节 :通过
resourcePrefix或命名规约规避资源冲突,并配置ARouter的模块名以保证路由表正确生成。