Skip to content

modularization-spi-demo ​

1. 实验目标 ​

用最小 Android Host 入口验证“接口下沉 + SPI/注册表寻址”的模块化解耦思路(对应 docs/05-bridge-hybrid/07):

  • 接口下沉:调用方只依赖 IAnalyticsService / IFeatureRouter 契约,不依赖具体实现类
  • 运行时发现:通过 SPI 注册表模式在运行时找到 Analytics 实现与目标模块路由
  • 模块解耦:证明“业务模块之间不直接引用实现类,也能完成埋点广播与路由寻址”

当前宿主为单模块演示骨架:为了在 Android Host 中直接跑通,本实验用 SpiRegistry 模拟 ServiceLoader / META-INF/services 发现过程;真实多模块工程中,再把注册表替换为标准 Java SPI 或路由框架生成表。

2. 工程要点 ​

  • SpiArchitectureDemoActivity:Compose 页面,提供两个动作按钮
    • 广播埋点事件:发现多个 IAnalyticsService 实现并逐个调用
    • 跨模块路由:按模块名查找 IFeatureRouter 实现并返回目标路由
  • IAnalyticsService / IFeatureRouter:下沉契约层,调用方永远只面向接口
  • SpiRegistry:当前用内存注册表模拟运行时发现;注释里已标出正式多模块时应迁移到 META-INF/services/...
  • MainActivity / AndroidManifest 已接通入口:宿主 App 中可直接点击“打开 SPI 与模块化架构实验室”验证

3. 运行方式 ​

bash
cd labs/android/host
./gradlew installDebug

设备/模拟器打开 Android Host → 打开 SPI 与模块化架构实验室。

验证步骤:

  1. 点击“广播埋点事件 (多扩展发现)”
    • 观察日志区列出 2 个 Analytics 实现
    • 两个实现都会收到同一事件 App_Launch
  2. 点击“跨界解耦路由 (寻址)”
    • 观察日志区输出 OrderModule -> /order/detail_page
  3. 对照源码理解边界
    • 调用方没有直接 new 具体实现类
    • 扩展点靠契约与注册表收口,不靠模块硬引用

4. 预期现象 ​

  • 第一次按钮点击后,日志区显示“通过 SPI 发现 2 个 Analytics 实现”
  • 随后依次打印:
    • FirebaseAnalytics (Remote Module) 远端埋点模拟结果
    • LocalDBAnalytics (Offline Module) 本地 WAL/离线埋点模拟结果
  • 第二个按钮点击后,日志区显示模块路由寻址成功:OrderModule -> /order/detail_page

5. 常见误区 ​

误区实际情况
模块化就是把包名拆开真正关键是“依赖方向”被反转:调用方依赖契约,不依赖实现
只要用了 ServiceLoader 就自动模块化SPI 只是发现机制;接口下沉、边界收口、依赖隔离才是核心
单模块骨架演示没有价值这类 lab 的价值是先验证调用关系与解耦心智,再逐步迁移到真实多 module / dynamic feature 工程
路由框架一定优于 SPI页面跳转、服务发现、事件广播是不同问题;SPI / 路由 / EventBus 各有适用边界

6. 对应知识库文档 ​

7. 关键实现速览 ​

7.1 下沉契约:调用方只认接口 ​

kotlin
interface IAnalyticsService {
    val serviceName: String
    fun trackEvent(event: String, properties: Map<String, Any>): String
}

interface IFeatureRouter {
    val moduleName: String
    fun getDestination(): String
}

为什么看这段:它定义了“模块间只通过契约通信”的最小骨架,是后续替换成真实多模块 SPI 的前提。

7.2 注册表模拟运行时发现 ​

kotlin
object SpiRegistry {
    fun getAnalyticsServices(): List<IAnalyticsService> {
        return listOf(FirebaseAnalyticsServiceImpl(), LocalDBAnalyticsServiceImpl())
    }

    fun getRouter(moduleName: String): IFeatureRouter? {
        val routers = listOf(OrderFeatureRouterImpl())
        return routers.find { it.moduleName.equals(moduleName, ignoreCase = true) }
    }
}

为什么看这段:当前宿主并非真实多模块工程,所以用注册表模拟 ServiceLoader.load(...);文档里可以先学解耦关系,再过渡到正式 SPI。

7.3 UI 验证层:一键触发多实现发现 / 跨模块寻址 ​

kotlin
val services = SpiRegistry.getAnalyticsServices()
services.forEach { service ->
    val result = service.trackEvent("App_Launch", mapOf("source" to "spi_lab"))
    newLogs.add(" -> $result")
}

为什么看这段:它直接证明调用层不需要 import 具体业务模块类型,也能把同一事件分发给多个实现。

完整源码:

8. 与后续样板的边界 ​

  • 本实验是 CLOSURE 型任务:补齐已有源码入口、README、文档回链,证明 05 模块化 / SPI 不是空地
  • 它不替代后续“页面跳转协议 / 支付结果回流 / 分享权限桥 / 归因透传”这些真实业务桥接样板
  • 它也不等于真正的 Dynamic Feature / 多 module Gradle 工程;那部分继续由 08-dynamic-delivery-hot-update 与后续专项实验承接

站点构建时间:2026/8/24 23:43:17