一、组件化的核心问题 随着 Android 项目规模增长,单体工程(Monolithic Project)会面临严重的工程问题:
编译速度 :修改一行代码需要重新编译整个项目,clean build 可能需要数十分钟。
协作冲突 :多人同时修改同一个模块,Git 冲突频繁。
代码边界模糊 :模块之间可以直接引用对方的类,依赖关系如蛛网般纠缠。
复用困难 :一个模块无法独立抽取给其他项目使用。
组件化(Componentization)的解决方案是:将一个庞大的 App 工程拆分为多个可独立编译、独立运行、可复用的业务模块 。
组件化的四个核心能力:
模块隔离 :模块之间通过 api 和 implementation 控制依赖传递。
路由通信 :模块间通过 URL Scheme 方式导航,无需持有对方的 Activity/Fragment 类引用。
服务发现 :模块暴露 IService 接口,通过服务容器获取实现。
生命周期委派 :Application 的生命周期事件分发给各模块。
二、模块隔离:api vs implementation Gradle 的 api 和 implementation 是模块隔离的基础:
// 模块 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 来隔离资源名冲突:
android { resourcePrefix "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); } 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 () ; } public interface IUserService extends IService { boolean isLoggedIn () ; UserInfo getCurrentUser () ; }
在编译期,每个模块通过 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(path = "/user/LoginActivity") public class LoginActivity extends AppCompatActivity { @Autowired String username; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); ARouter.getInstance().inject(this ); } } ARouter.getInstance().build("/user/LoginActivity" ) .withString("username" , "alice" ) .navigation();
4.1 APT 如何生成路由表 ARouter 的 arouter-compiler 模块在编译期扫描所有 @Route 注解:
@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(); } }
最终生成的代码:
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 模式:
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) { } } ILoginService loginService = ARouter.getInstance().navigation(ILoginService.class);if (loginService != null && loginService.isLogin()) { }
navigation(Class) 方法会查找该接口的 @Route 标记的实现类,通过反射实例化并返回。这样依赖关系从”编译期强依赖”变为”运行时通过接口松耦合”。
六、Application 生命周期委派 组件化后,每个业务模块都需要在 Application 启动时执行初始化(如初始化数据库、注册第三方 SDK)。但 modules 之间不能直接通信,需要一个代理机制:
public class BaseApplication extends Application { @Override public void onCreate () { super .onCreate(); List<IModule> modules = getAllModulesByApt(); for (IModule module : modules) { module .init(this ); module .registerLifecycle(lifecycleCallbacks); } } } public class UserModuleInit implements IModule { @Override public void init (Application app) { } }
各模块的 IModule 实现类通过 APT 自动收集,主模块无需逐一引用。
七、工程结构最佳实践 ├── app/ # 主模块(壳工程,只做拼装和 Application 初始化) ├── common/ # 公共层(BaseActivity、工具类、网络框架封装) │ ├── common-base/ │ └── common-service/ # 各模块暴露的 IService 接口定义 ├── module_user/ # 用户模块(可独立运行 runAlone = true) ├── module_im/ # 即时通讯模块 ├── module_pay/ # 支付模块 └── buildSrc/ # Gradle 构建脚本的统一配置
runAlone 模式:每个业务模块在开发时可以单独作为 App 运行,通过 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