目录
  1. 1. 一、组件化的核心问题
  2. 2. 二、模块隔离:api vs implementation
  3. 3. 三、组件管理器:ComponentManager 设计
  4. 4. 四、路由表生成:APT 与 ActivityRouter 方案
    1. 4.1. 4.1 APT 如何生成路由表
    2. 4.2. 4.2 拦截器链
  5. 5. 五、服务发现:IService 接口模式
  6. 6. 六、Application 生命周期委派
  7. 7. 七、工程结构最佳实践
  8. 8. 八、面试常问题目
解读开源框架系列-组件化框架设计

一、组件化的核心问题

随着 Android 项目规模增长,单体工程(Monolithic Project)会面临严重的工程问题:

  1. 编译速度:修改一行代码需要重新编译整个项目,clean build 可能需要数十分钟。
  2. 协作冲突:多人同时修改同一个模块,Git 冲突频繁。
  3. 代码边界模糊:模块之间可以直接引用对方的类,依赖关系如蛛网般纠缠。
  4. 复用困难:一个模块无法独立抽取给其他项目使用。

组件化(Componentization)的解决方案是:将一个庞大的 App 工程拆分为多个可独立编译、独立运行、可复用的业务模块

组件化的四个核心能力:

  • 模块隔离:模块之间通过 apiimplementation 控制依赖传递。
  • 路由通信:模块间通过 URL Scheme 方式导航,无需持有对方的 Activity/Fragment 类引用。
  • 服务发现:模块暴露 IService 接口,通过服务容器获取实现。
  • 生命周期委派:Application 的生命周期事件分发给各模块。

二、模块隔离:api vs implementation

Gradle 的 apiimplementation 是模块隔离的基础:

// 模块 A 的 build.gradle
dependencies {
// implementation:依赖不传递,只有 A 内部可用
implementation 'com.squareup.okhttp3:okhttp:4.9.0'

// api:依赖会传递给依赖 A 的模块
api 'com.google.code.gson:gson:2.8.9'
}

// 模块 B 依赖模块 A
dependencies {
implementation project(':module_a')
// B 可以使用 gson(因为 A 用了 api)
// B 不能直接使用 okhttp(因为 A 用了 implementation)
}

implementation 的实质:编译时 B 看不到 A 的 implementation 依赖的符号(符号在 A 的编译 classpath 中但不在 B 的编译 classpath 中),但运行时这些类仍然存在(因为最终 APK 包含所有传递依赖)。这强制开发者只能通过 A 暴露的公开 API 使用 A,不能直接依赖 A 的内部实现。

在大型组件化工程中,通常还配置 resourcePrefix 来隔离资源名冲突:

// module_user 的 build.gradle
android {
resourcePrefix "user_" // 该模块所有资源必须以 user_ 开头
}

这避免了不同模块定义了同名资源文件(如两个模块都有 bg_button.xml)导致的资源覆盖问题。

三、组件管理器:ComponentManager 设计

一个标准的组件管理器负责组件的注册、初始化和生命周期管理:

public class ComponentManager {
private static ComponentManager instance;
private final Map<String, IComponent> componentMap = new LinkedHashMap<>();

public static ComponentManager getInstance() { /* 单例 */ }

// 注册组件
public void registerComponent(String name, IComponent component) {
componentMap.put(name, component);
}

// 初始化所有组件(在 Application.onCreate 中调用)
public void initComponents(Application app) {
for (IComponent comp : componentMap.values()) {
comp.onCreate(app);
}
}

// 按需获取组件暴露的服务
public <T extends IService> T getService(Class<T> serviceClass) {
for (IComponent comp : componentMap.values()) {
if (serviceClass.isAssignableFrom(comp.getClass())) {
return (T) comp;
}
}
return null;
}
}

// 组件接口
public interface IComponent {
void onCreate(Application app);
void onTerminate();
// 可扩展: onLowMemory(), onTrimMemory() 等
}

// 业务服务接口
public interface IUserService extends IService {
boolean isLoggedIn();
UserInfo getCurrentUser();
}

在编译期,每个模块通过 APT 自动生成注册代码:

// 自动生成的 UserComponentRegister.java(通过 APT 生成)
public class UserComponentRegister implements IComponentRegister {
@Override
public void register(ComponentManager manager) {
manager.registerComponent("user", new UserComponent());
}
}

四、路由表生成:APT 与 ActivityRouter 方案

路由是组件化最核心的基础设施。模块 A 需要跳转到模块 B 的页面,但不能直接 import 模块 B 的 Activity 类(这会破坏组件隔离)。

ARouter(阿里开源,https://github.com/alibaba/ARouter)是使用最广泛的方案:

// 使用 @Route 注解标记目标页面
@Route(path = "/user/LoginActivity")
public class LoginActivity extends AppCompatActivity {
@Autowired
String username; // 自动从 URL 参数中注入

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this); // 注入参数
}
}

// 跳转代码(不 import LoginActivity)
ARouter.getInstance().build("/user/LoginActivity")
.withString("username", "alice")
.navigation();

4.1 APT 如何生成路由表

ARouter 的 arouter-compiler 模块在编译期扫描所有 @Route 注解:

// arouter-compiler 的核心处理逻辑(简化)
// com.alibaba.android.arouter.compiler.processor.RouteProcessor
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
for (Element element : routeElements) {
Route route = element.getAnnotation(Route.class);
String path = route.path();
// 生成形如: atlas.put("/user/LoginActivity", RouteMeta.build(...))
// RouteMeta 包含 Class 引用、路由类型等
}
// 写入生成的文件:ARouter$$Group$$user.java
}

最终生成的代码:

// ARouter$$Group$$user.java(自动生成)
public class ARouter$$Group$$user implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/user/LoginActivity",
RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class,
"/user/LoginActivity", "user", 0, 0));
}
}

ARouter 运行时通过 path 的第一段(如 “user”)作为 group,懒加载对应的 Group 文件,然后从 atlas 中找到目标 Class,完成跳转。

4.2 拦截器链

ARouter 支持全局拦截器,可在页面跳转前做登录检查等逻辑:

@Interceptor(priority = 1, name = "登录拦截器")
public class LoginInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if (postcard.getExtra() == NEED_LOGIN && !UserManager.isLogin()) {
callback.onInterrupt(new RuntimeException("请先登录"));
// 可在此处拦截并跳转到登录页
} else {
callback.onContinue(postcard);
}
}
}

五、服务发现:IService 接口模式

除了页面路由,模块之间还需要能力调用(如从用户模块获取登录状态)。ARouter 的 IService 模式:

// 在公共层(common module)定义接口
public interface ILoginService extends IProvider {
boolean isLogin();
String getUserToken();
void logout();
}

// 在具体业务模块实现
@Route(path = "/user/LoginService")
public class LoginServiceImpl implements ILoginService {
@Override
public boolean isLogin() {
return UserManager.getInstance().isLogin();
}
@Override
public String getUserToken() { /* ... */ }
@Override
public void init(Context context) { /* ... */ }
}

// 在任何模块中调用(无需 import LoginServiceImpl)
ILoginService loginService = ARouter.getInstance().navigation(ILoginService.class);
if (loginService != null && loginService.isLogin()) {
// ...
}

navigation(Class) 方法会查找该接口的 @Route 标记的实现类,通过反射实例化并返回。这样依赖关系从”编译期强依赖”变为”运行时通过接口松耦合”。

六、Application 生命周期委派

组件化后,每个业务模块都需要在 Application 启动时执行初始化(如初始化数据库、注册第三方 SDK)。但 modules 之间不能直接通信,需要一个代理机制:

// BaseApplication.java(主模块)
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 通过 ARouter 获取所有 IModule 实现并调用 init
List<IModule> modules = getAllModulesByApt();
for (IModule module : modules) {
module.init(this);
module.registerLifecycle(lifecycleCallbacks);
}
}
}

// 每个模块实现 IModule
public class UserModuleInit implements IModule {
@Override
public void init(Application app) {
// 初始化用户数据库、第三方登录 SDK 等
}
}

各模块的 IModule 实现类通过 APT 自动收集,主模块无需逐一引用。

七、工程结构最佳实践

├── app/                    # 主模块(壳工程,只做拼装和 Application 初始化)
├── common/ # 公共层(BaseActivity、工具类、网络框架封装)
│ ├── common-base/
│ └── common-service/ # 各模块暴露的 IService 接口定义
├── module_user/ # 用户模块(可独立运行 runAlone = true)
├── module_im/ # 即时通讯模块
├── module_pay/ # 支付模块
└── buildSrc/ # Gradle 构建脚本的统一配置

runAlone 模式:每个业务模块在开发时可以单独作为 App 运行,通过 Gradle 配置:

// module_user 的 build.gradle
boolean runAlone = isRunAlone.toBoolean()

if (runAlone) {
apply plugin: 'com.android.application'
applicationId "com.example.module.user"
} else {
apply plugin: 'com.android.library'
}

runAlone = true 时,该模块就是一个独立的 Application,有自己的 AndroidManifest.xml,开发者可以独立启动和调试,无需编译其他模块。这在团队规模较大时极大地提升了开发效率。

八、面试常问题目

Q1: implementation 和 api 的区别?为什么组件化中推荐使用 implementation?

api 依赖会传递:A 用 api 依赖 B,C 依赖 A,则 C 可以访问 B 的类。implementation 依赖不传递:C 不能访问 B 的类。组件化中使用 implementation 可以强制模块间通过接口通信,避免绕过 A 直接依赖其内部实现,实现真正的封装。同时 implementation 减少了编译时的依赖传递,显著提升增量编译速度——修改 B 时只需重新编译 B 和直接依赖 B 的 A,不需要重新编译 C。

Q2: ARouter 如何解决模块间的循环依赖问题?

模块间的循环依赖指的是 A 依赖 B 且 B 依赖 A。组件化通过提取公共层(common module)来解决:A 和 B 共同依赖 common,将需要共享的接口定义放到 common 中,通过 IService 运行时发现。这样 A 和 B 在编译期完全独立,没有相互引用。

Q3: 组件化和插件化的区别是什么?

组件化是工程结构的设计方法,所有组件最终合并到一个 APK 中,在编译期完成组装。插件化是运行时的技术,不同的业务模块可以独立打包为 APK,运行时动态加载。组件化解决的是开发和协作效率问题,插件化解决的是动态部署和热更新问题。实际项目中两者可以结合使用:在组件化基础上,对需要动态发布的组件做插件化改造。

Q4: resourcePrefix 机制是如何防止资源冲突的?

Gradle 在构建打包阶段,会将所有 library 模块和 app 模块的资源合并。如果两个模块有同名资源文件,后处理的会覆盖先处理的(取决于依赖顺序)。resourcePrefix 强制该模块的资源名以指定前缀开头,编译时通过 Lint 检查(ResourceName 规则)报错。这不是运行时机制,而是构建期约束。


参考源码路径:

  • ARouter:https://github.com/alibaba/ARouter
  • ARouter Compiler:arouter-compiler/src/main/java/com/alibaba/android/arouter/compiler/processor/RouteProcessor.java
  • DexPathList:libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
  • Android Gradle Plugin Dependency Resolution:tools/base/build-system/gradle-core
  • Resource Prefix Lint Check:tools/base/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ResourceNameDetector.java
打赏
  • 微信
  • 支付宝

评论