系列三:组件化与模块化进阶 | 第8篇 组件化与模块化核心实战区别:大型项目架构的必由之路

系列三:组件化与模块化进阶 | 第8篇

组件化与模块化核心实战区别:大型项目架构的必由之路(企业级全案)

阅读警告

本文为超深度技术长文,预计阅读时长 40-60 分钟,代码量极大。

在前七篇中,我们解决了**"代码怎么写"的问题(架构思想、MVVM、状态管理)。
从这一篇开始,我们要解决
"代码放在哪" "团队怎么协作"**的问题。

如果你的项目编译一次要 5 分钟,改一行代码要等半天,或者两个人同时改代码天天冲突,那么这一篇就是为你写的。

我们将彻底厘清 模块化(Modularization)组件化(Componentization) 的区别,并从零搭建一套 可独立运行、可插拔、可并行编译、可灰度发布 的企业级工程架构。

全文包含:Gradle 黑魔法、路由源码级剖析、资源隔离方案、组件生命周期管理、以及大厂落地血泪史。


1 引子:单体工程的死亡螺旋

让我们先看一个典型的"巨型工程"在 2 年后的样子。这不是虚构,这是 90% 成长型公司的必经之路。

1.1 症状诊断

  1. 编译速度的黑洞:全量编译从 1 分钟变成 10 分钟。因为任何一个模块的改动,Gradle 都会认为整个 App 需要重新编译。开发者的时间成本呈指数级上升。
  2. Git 冲突的噩梦app 模块下有 50 个开发人员同时在改,每天合并代码时,冲突文件多达几十个。AndroidManifest.xml 永远是冲突的重灾区。
  3. 业务耦合的毒瘤:登录模块调用了支付模块的类,支付模块又依赖了商品模块的资源。代码像意大利面条一样纠缠在一起。想删一个功能?不敢删,因为不知道删了哪个地方会崩。
  4. 测试的地狱:改了一个工具类,回归测试要跑遍所有业务线。测试团队永远在加班。
  5. 发布的枷锁:一个业务线出了紧急 Bug,必须全量发包。没办法只更新某一个业务模块。

1.2 单体 vs 组件化 对比表

维度 单体工程 (Monolith) 组件化工程 (Component)
编译速度 慢(全量编译,10分钟+) 快(增量编译,只编改动的模块,1分钟)
并行开发 难(互相阻塞,Git 冲突多) 易(每人负责一个组件,互不干扰)
代码边界 模糊(互相引用,无强制约束) 清晰(通过路由通信,物理隔离)
独立调试 必须跑整个 App(启动慢) 组件可单独运行(秒启)
版本迭代 牵一发动全身(全量回归) 组件可独立发版(灰度发布)
技术栈升级 风险极大(牵一发而动全身) 风险可控(单个组件试点)

2 核心概念辨析:模块化 vs 组件化

这是 90% 的团队都会混淆的概念。请务必花 5 分钟理解透彻,这是后续所有架构的基石。

2.1 模块化(Modularization):按"技术职能"拆

定义 :将 App 拆分成多个 Library Module 。这些 Module 通常按 技术职能 划分,目的是为了代码复用

例子

  • module-network(网络封装:Retrofit、OkHttp、拦截器)
  • module-database(数据库操作:Room、GreenDAO)
  • module-utils(工具类:StringUtil、DateUtil)
  • module-ui(自定义 View、Style、Theme)
  • module-base(BaseActivity、BaseViewModel、BaseApplication)

特点

  • 不能独立运行(没有 Application,没有 Launcher Activity)。
  • 依赖关系:业务层依赖基础模块。
  • 目的:结构清晰,避免重复造轮子。

架构图
#mermaid-svg-gq73EkDSPq4aAwQK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gq73EkDSPq4aAwQK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gq73EkDSPq4aAwQK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gq73EkDSPq4aAwQK .error-icon{fill:#552222;}#mermaid-svg-gq73EkDSPq4aAwQK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gq73EkDSPq4aAwQK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gq73EkDSPq4aAwQK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gq73EkDSPq4aAwQK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gq73EkDSPq4aAwQK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gq73EkDSPq4aAwQK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gq73EkDSPq4aAwQK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gq73EkDSPq4aAwQK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gq73EkDSPq4aAwQK .marker.cross{stroke:#333333;}#mermaid-svg-gq73EkDSPq4aAwQK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gq73EkDSPq4aAwQK p{margin:0;}#mermaid-svg-gq73EkDSPq4aAwQK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gq73EkDSPq4aAwQK .cluster-label text{fill:#333;}#mermaid-svg-gq73EkDSPq4aAwQK .cluster-label span{color:#333;}#mermaid-svg-gq73EkDSPq4aAwQK .cluster-label span p{background-color:transparent;}#mermaid-svg-gq73EkDSPq4aAwQK .label text,#mermaid-svg-gq73EkDSPq4aAwQK span{fill:#333;color:#333;}#mermaid-svg-gq73EkDSPq4aAwQK .node rect,#mermaid-svg-gq73EkDSPq4aAwQK .node circle,#mermaid-svg-gq73EkDSPq4aAwQK .node ellipse,#mermaid-svg-gq73EkDSPq4aAwQK .node polygon,#mermaid-svg-gq73EkDSPq4aAwQK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gq73EkDSPq4aAwQK .rough-node .label text,#mermaid-svg-gq73EkDSPq4aAwQK .node .label text,#mermaid-svg-gq73EkDSPq4aAwQK .image-shape .label,#mermaid-svg-gq73EkDSPq4aAwQK .icon-shape .label{text-anchor:middle;}#mermaid-svg-gq73EkDSPq4aAwQK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gq73EkDSPq4aAwQK .rough-node .label,#mermaid-svg-gq73EkDSPq4aAwQK .node .label,#mermaid-svg-gq73EkDSPq4aAwQK .image-shape .label,#mermaid-svg-gq73EkDSPq4aAwQK .icon-shape .label{text-align:center;}#mermaid-svg-gq73EkDSPq4aAwQK .node.clickable{cursor:pointer;}#mermaid-svg-gq73EkDSPq4aAwQK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gq73EkDSPq4aAwQK .arrowheadPath{fill:#333333;}#mermaid-svg-gq73EkDSPq4aAwQK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gq73EkDSPq4aAwQK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gq73EkDSPq4aAwQK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gq73EkDSPq4aAwQK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gq73EkDSPq4aAwQK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gq73EkDSPq4aAwQK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gq73EkDSPq4aAwQK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gq73EkDSPq4aAwQK .cluster text{fill:#333;}#mermaid-svg-gq73EkDSPq4aAwQK .cluster span{color:#333;}#mermaid-svg-gq73EkDSPq4aAwQK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gq73EkDSPq4aAwQK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gq73EkDSPq4aAwQK rect.text{fill:none;stroke-width:0;}#mermaid-svg-gq73EkDSPq4aAwQK .icon-shape,#mermaid-svg-gq73EkDSPq4aAwQK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gq73EkDSPq4aAwQK .icon-shape p,#mermaid-svg-gq73EkDSPq4aAwQK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gq73EkDSPq4aAwQK .icon-shape .label rect,#mermaid-svg-gq73EkDSPq4aAwQK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gq73EkDSPq4aAwQK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gq73EkDSPq4aAwQK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gq73EkDSPq4aAwQK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础模块 (Library)
App Module (壳)
MainActivity
module-network
module-database
module-ui
module-base

2.2 组件化(Componentization):按"业务线"拆

定义 :将 App 拆分成多个 Business Component 。这些 Component 通常按 业务线 划分,目的是为了业务解耦

例子

  • component-login(登录业务:手机号登录、微信登录、注册)
  • component-pay(支付业务:支付宝、微信、银联)
  • component-home(首页业务:Feed流、Banner、导航)
  • component-user(用户中心:个人信息、设置、收货地址)
  • component-order(订单业务:列表、详情、物流)

特点

  • 可以独立运行(有自己的 Application,有自己的 Launcher Activity)。
  • 不能直接互相依赖(通过路由跳转,通过接口下沉通信)。
  • 目的:业务隔离,并行开发,独立发布。

架构图
#mermaid-svg-1KwVLUmNyQtjzhq6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1KwVLUmNyQtjzhq6 .error-icon{fill:#552222;}#mermaid-svg-1KwVLUmNyQtjzhq6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1KwVLUmNyQtjzhq6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .marker.cross{stroke:#333333;}#mermaid-svg-1KwVLUmNyQtjzhq6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1KwVLUmNyQtjzhq6 p{margin:0;}#mermaid-svg-1KwVLUmNyQtjzhq6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .cluster-label text{fill:#333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .cluster-label span{color:#333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .cluster-label span p{background-color:transparent;}#mermaid-svg-1KwVLUmNyQtjzhq6 .label text,#mermaid-svg-1KwVLUmNyQtjzhq6 span{fill:#333;color:#333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .node rect,#mermaid-svg-1KwVLUmNyQtjzhq6 .node circle,#mermaid-svg-1KwVLUmNyQtjzhq6 .node ellipse,#mermaid-svg-1KwVLUmNyQtjzhq6 .node polygon,#mermaid-svg-1KwVLUmNyQtjzhq6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1KwVLUmNyQtjzhq6 .rough-node .label text,#mermaid-svg-1KwVLUmNyQtjzhq6 .node .label text,#mermaid-svg-1KwVLUmNyQtjzhq6 .image-shape .label,#mermaid-svg-1KwVLUmNyQtjzhq6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-1KwVLUmNyQtjzhq6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1KwVLUmNyQtjzhq6 .rough-node .label,#mermaid-svg-1KwVLUmNyQtjzhq6 .node .label,#mermaid-svg-1KwVLUmNyQtjzhq6 .image-shape .label,#mermaid-svg-1KwVLUmNyQtjzhq6 .icon-shape .label{text-align:center;}#mermaid-svg-1KwVLUmNyQtjzhq6 .node.clickable{cursor:pointer;}#mermaid-svg-1KwVLUmNyQtjzhq6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .arrowheadPath{fill:#333333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1KwVLUmNyQtjzhq6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1KwVLUmNyQtjzhq6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1KwVLUmNyQtjzhq6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1KwVLUmNyQtjzhq6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1KwVLUmNyQtjzhq6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1KwVLUmNyQtjzhq6 .cluster text{fill:#333;}#mermaid-svg-1KwVLUmNyQtjzhq6 .cluster span{color:#333;}#mermaid-svg-1KwVLUmNyQtjzhq6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1KwVLUmNyQtjzhq6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1KwVLUmNyQtjzhq6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-1KwVLUmNyQtjzhq6 .icon-shape,#mermaid-svg-1KwVLUmNyQtjzhq6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1KwVLUmNyQtjzhq6 .icon-shape p,#mermaid-svg-1KwVLUmNyQtjzhq6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1KwVLUmNyQtjzhq6 .icon-shape .label rect,#mermaid-svg-1KwVLUmNyQtjzhq6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1KwVLUmNyQtjzhq6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1KwVLUmNyQtjzhq6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1KwVLUmNyQtjzhq6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础组件 (Foundation Modules)
业务组件 (Business Components)
Shell App (壳工程)
空壳 Application
component-login
component-pay
component-home
component-order
module-network
module-storage
module-widget
module-base

2.3 终极关系图(企业标准)

双层架构(这是大厂的标准答案)

  1. 底层(Level 1):基础模块(Modularization)。纯技术能力,无业务逻辑。
  2. 中层(Level 2):业务组件(Componentization)。纯业务逻辑,独立运行。
  3. 顶层(Level 3):壳工程(Shell)。空壳,只负责组装和配置。

#mermaid-svg-lSGKwqcXnER9QnsE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lSGKwqcXnER9QnsE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lSGKwqcXnER9QnsE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lSGKwqcXnER9QnsE .error-icon{fill:#552222;}#mermaid-svg-lSGKwqcXnER9QnsE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lSGKwqcXnER9QnsE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lSGKwqcXnER9QnsE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lSGKwqcXnER9QnsE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lSGKwqcXnER9QnsE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lSGKwqcXnER9QnsE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lSGKwqcXnER9QnsE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lSGKwqcXnER9QnsE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lSGKwqcXnER9QnsE .marker.cross{stroke:#333333;}#mermaid-svg-lSGKwqcXnER9QnsE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lSGKwqcXnER9QnsE p{margin:0;}#mermaid-svg-lSGKwqcXnER9QnsE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lSGKwqcXnER9QnsE .cluster-label text{fill:#333;}#mermaid-svg-lSGKwqcXnER9QnsE .cluster-label span{color:#333;}#mermaid-svg-lSGKwqcXnER9QnsE .cluster-label span p{background-color:transparent;}#mermaid-svg-lSGKwqcXnER9QnsE .label text,#mermaid-svg-lSGKwqcXnER9QnsE span{fill:#333;color:#333;}#mermaid-svg-lSGKwqcXnER9QnsE .node rect,#mermaid-svg-lSGKwqcXnER9QnsE .node circle,#mermaid-svg-lSGKwqcXnER9QnsE .node ellipse,#mermaid-svg-lSGKwqcXnER9QnsE .node polygon,#mermaid-svg-lSGKwqcXnER9QnsE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lSGKwqcXnER9QnsE .rough-node .label text,#mermaid-svg-lSGKwqcXnER9QnsE .node .label text,#mermaid-svg-lSGKwqcXnER9QnsE .image-shape .label,#mermaid-svg-lSGKwqcXnER9QnsE .icon-shape .label{text-anchor:middle;}#mermaid-svg-lSGKwqcXnER9QnsE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lSGKwqcXnER9QnsE .rough-node .label,#mermaid-svg-lSGKwqcXnER9QnsE .node .label,#mermaid-svg-lSGKwqcXnER9QnsE .image-shape .label,#mermaid-svg-lSGKwqcXnER9QnsE .icon-shape .label{text-align:center;}#mermaid-svg-lSGKwqcXnER9QnsE .node.clickable{cursor:pointer;}#mermaid-svg-lSGKwqcXnER9QnsE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lSGKwqcXnER9QnsE .arrowheadPath{fill:#333333;}#mermaid-svg-lSGKwqcXnER9QnsE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lSGKwqcXnER9QnsE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lSGKwqcXnER9QnsE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lSGKwqcXnER9QnsE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lSGKwqcXnER9QnsE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lSGKwqcXnER9QnsE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lSGKwqcXnER9QnsE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lSGKwqcXnER9QnsE .cluster text{fill:#333;}#mermaid-svg-lSGKwqcXnER9QnsE .cluster span{color:#333;}#mermaid-svg-lSGKwqcXnER9QnsE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lSGKwqcXnER9QnsE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lSGKwqcXnER9QnsE rect.text{fill:none;stroke-width:0;}#mermaid-svg-lSGKwqcXnER9QnsE .icon-shape,#mermaid-svg-lSGKwqcXnER9QnsE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lSGKwqcXnER9QnsE .icon-shape p,#mermaid-svg-lSGKwqcXnER9QnsE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lSGKwqcXnER9QnsE .icon-shape .label rect,#mermaid-svg-lSGKwqcXnER9QnsE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lSGKwqcXnER9QnsE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lSGKwqcXnER9QnsE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lSGKwqcXnER9QnsE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Level 1: 基础模块 (Foundation Modules)
module-network (网络)
module-storage (存储)
module-widget (UI)
module-base (基类)
module-router (路由)
Level 2: 业务组件 (Business Components)
component-login
component-pay
component-order
component-mine
component-im
Level 3: 壳工程 (Shell App)
app-shell (空壳)


3 实战:从零搭建组件化工程(手把手,含 Gradle 黑魔法)

现在,我们动手把一个单体工程拆成组件化工程。请跟着我的步骤操作。

3.1 第一步:工程目录规划

创建一个干净的 Project,目录结构如下:

复制代码
ProjectRoot/
├── app-shell/              # 壳工程(空壳,只组装)
│   └── src/main/java/
│       └── AppShell.kt
│
├── components/             # 业务组件(可独立运行)
│   ├── component-login/
│   │   ├── src/main/java/
│   │   ├── src/debug/java/   # 独立运行时的配置
│   │   └── build.gradle
│   ├── component-home/
│   ├── component-pay/
│   └── component-mine/
│
├── modules/               # 基础模块(不可独立运行)
│   ├── module-base/       # 基类、路由、工具
│   ├── module-network/    # 网络封装
│   ├── module-storage/    # 数据库、SP
│   └── module-ui/         # 自定义 View、Style
│
├── build.gradle
├── settings.gradle
└── gradle.properties

3.2 第二步:创建模块(Gradle 配置)

settings.gradle 中注册所有模块。这是总控开关

gradle 复制代码
// settings.gradle.kts
include(":app-shell")
include(":component-login")
include(":component-home")
include(":component-pay")
include(":component-mine")
include(":module-base")
include(":module-network")
include(":module-storage")
include(":module-ui")

3.3 第三步:定义组件的"独立运行"开关(核心黑魔法)

这是组件化的灵魂。我们需要一个开关,控制组件是 独立运行 (开发时)还是 集成运行(打包时)。

gradle.properties 中定义全局变量:

properties 复制代码
# 组件独立运行开关
# true = 独立运行(开发时,有 Application 和 Launcher)
# false = 集成运行(打包时,作为 Library 被壳工程依赖)
isLoginComponentDebug = true
isHomeComponentDebug = false
isPayComponentDebug = false
isMineComponentDebug = false

3.4 第四步:配置组件的 build.gradle

这是最关键的一步。组件需要根据开关切换 applicationlibrary 插件。

component-login/build.gradle.kts:

kotlin 复制代码
plugins {
    // 根据开关动态应用插件
    if (isLoginComponentDebug.toBoolean()) {
        id("com.android.application")
    } else {
        id("com.android.library")
    }
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.component.login"
    compileSdk = 34

    defaultConfig {
        minSdk = 21
        // 只有作为 Application 时才需要 applicationId
        if (isLoginComponentDebug.toBoolean()) {
            applicationId = "com.example.component.login"
        }
        targetSdk = 34
    }

    // 源集配置:区分 debug 和 release
    sourceSets {
        getByName("main") {
            // Manifest 文件分两套
            if (isLoginComponentDebug.toBoolean()) {
                manifest.srcFile("src/debug/AndroidManifest.xml")
            } else {
                manifest.srcFile("src/main/AndroidManifest.xml")
            }
        }
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

dependencies {
    implementation(project(":module-base")) // 依赖基础模块
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
}

3.5 第五步:壳工程(Shell)的配置

壳工程是一个空的 Application,只负责组装组件。它不写任何业务逻辑。

app-shell/build.gradle.kts:

kotlin 复制代码
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.appshell"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.appshell"
        minSdk = 21
        targetSdk = 34
    }
}

dependencies {
    implementation(project(":module-base"))

    // 根据开关依赖组件
    // 注意:如果组件是独立运行模式,壳工程就不能依赖它
    if (!isLoginComponentDebug.toBoolean()) {
        implementation(project(":component-login"))
    }
    if (!isHomeComponentDebug.toBoolean()) {
        implementation(project(":component-home"))
    }
    if (!isPayComponentDebug.toBoolean()) {
        implementation(project(":component-pay"))
    }
    if (!isMineComponentDebug.toBoolean()) {
        implementation(project(":component-mine"))
    }
}

3.6 第六步:Manifest 的隔离策略

组件作为 Library 时,不能有 applicationId,也不能有自己的 Launcher Activity。

1. 组件的公共 Manifest (component-login/src/main/AndroidManifest.xml)

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 注意:没有 applicationId -->
    <application>
        <!-- 这里只注册组件内的 Activity,不要写 Launcher -->
        <activity
            android:name=".LoginActivity"
            android:exported="true" />
    </application>
</manifest>

2. 组件的 Debug Manifest (component-login/src/debug/AndroidManifest.xml)

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:name=".LoginApplication"
        android:allowBackup="true"
        android:label="登录组件(Debug)">
        <!-- 独立运行时的入口 -->
        <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>

3. 壳工程的 Manifest (app-shell/src/main/AndroidManifest.xml)

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:name=".AppShell"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">
        <!-- 壳工程的唯一入口 -->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

4 组件通信:ARouter 源码级剖析与实战

组件不能互相依赖,那怎么跳转?答案是 路由(Router) 。市面上路由框架很多,但 ARouter 是阿里出品,最成熟,也是大厂标配。

4.1 为什么不用 Intent 隐式跳转?

  1. 无法传递复杂对象:Intent 只能传基本类型和 Serializable/Parcelable。像 Bitmap、自定义对象集合很难传。
  2. 无法获取返回值StartActivityForResult 在组件化下很难用,因为不知道目标 Activity 的类名。
  3. URL 硬编码Intent intent = new Intent("com.example.login.LoginActivity") 容易写错,且重构困难。
  4. 无法拦截:无法统一做登录校验、权限校验、埋点。

4.2 ARouter 的核心原理(源码级)

ARouter 通过 注解处理器(APT) 在编译期生成映射表。

流程详解

  1. 编译期(APT)

    • 扫描所有 @Route(path = "/login/activity")
    • 生成类 ARouter$$Group$$login,里面有一个 HashMap<String, RouteMeta>
    • Key 是 "/login/activity",Value 是 LoginActivity.class
  2. 运行期(Init)

    • ARouter.init(application) 被调用。
    • 通过反射加载所有生成的 ARouter$$Group$$* 类。
    • 把 HashMap 加载到内存中。
  3. 运行期(Navigation)

    • ARouter.getInstance().build("/login/activity").navigation()
    • 在内存 Map 中查找 "/login/activity" 对应的 Class。
    • 调用 startActivity(new Intent(context, LoginActivity.class))

流程图
ARouter 内存映射表 注解处理器 开发者 ARouter 内存映射表 注解处理器 开发者 #mermaid-svg-V9vbTHg034YK0kj4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V9vbTHg034YK0kj4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V9vbTHg034YK0kj4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V9vbTHg034YK0kj4 .error-icon{fill:#552222;}#mermaid-svg-V9vbTHg034YK0kj4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V9vbTHg034YK0kj4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V9vbTHg034YK0kj4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V9vbTHg034YK0kj4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V9vbTHg034YK0kj4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V9vbTHg034YK0kj4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V9vbTHg034YK0kj4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V9vbTHg034YK0kj4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V9vbTHg034YK0kj4 .marker.cross{stroke:#333333;}#mermaid-svg-V9vbTHg034YK0kj4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V9vbTHg034YK0kj4 p{margin:0;}#mermaid-svg-V9vbTHg034YK0kj4 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-V9vbTHg034YK0kj4 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-V9vbTHg034YK0kj4 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-V9vbTHg034YK0kj4 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-V9vbTHg034YK0kj4 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-V9vbTHg034YK0kj4 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-V9vbTHg034YK0kj4 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-V9vbTHg034YK0kj4 .sequenceNumber{fill:white;}#mermaid-svg-V9vbTHg034YK0kj4 #sequencenumber{fill:#333;}#mermaid-svg-V9vbTHg034YK0kj4 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-V9vbTHg034YK0kj4 .messageText{fill:#333;stroke:none;}#mermaid-svg-V9vbTHg034YK0kj4 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-V9vbTHg034YK0kj4 .labelText,#mermaid-svg-V9vbTHg034YK0kj4 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-V9vbTHg034YK0kj4 .loopText,#mermaid-svg-V9vbTHg034YK0kj4 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-V9vbTHg034YK0kj4 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-V9vbTHg034YK0kj4 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-V9vbTHg034YK0kj4 .noteText,#mermaid-svg-V9vbTHg034YK0kj4 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-V9vbTHg034YK0kj4 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-V9vbTHg034YK0kj4 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-V9vbTHg034YK0kj4 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-V9vbTHg034YK0kj4 .actorPopupMenu{position:absolute;}#mermaid-svg-V9vbTHg034YK0kj4 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-V9vbTHg034YK0kj4 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-V9vbTHg034YK0kj4 .actor-man circle,#mermaid-svg-V9vbTHg034YK0kj4 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-V9vbTHg034YK0kj4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 写 @Route(path="/login/activity")生成 Map {"/login/activity": LoginActivity.class}ARouter.init()加载 Map 到内存build("/login/activity").navigation()查找 Class返回 LoginActivity.classstartActivity(LoginActivity)

4.3 实战:集成 ARouter(企业级配置)

1. 基础模块 module-base 中添加依赖(作为统一出口)

gradle 复制代码
// module-base/build.gradle.kts
dependencies {
    // Arouter API
    implementation("io.github.alibaba:arouter-api:1.5.2")
    
    // 注意:Compiler 不能放在 base 里,因为每个组件都要用自己的 Compiler
}

2. 每个业务组件中添加 Compiler 依赖

gradle 复制代码
// component-login/build.gradle.kts
dependencies {
    // Arouter Compiler (注解处理器)
    kapt("io.github.alibaba:arouter-compiler:1.5.2")
}

3. 初始化(壳工程中)

kotlin 复制代码
// app-shell/AppShell.kt
class AppShell : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            ARouter.openLog()
            ARouter.openDebug() // 开启调试模式(如果在 Instant Run 模式下运行,必须开启)
        }
        ARouter.init(this)
    }
}

4. 使用(跳转与传参)

kotlin 复制代码
// 跳转
ARouter.getInstance()
    .build("/pay/activity")
    .withString("orderId", "123456")
    .withInt("price", 999)
    .navigation()

// 接收参数
@Route(path = "/pay/activity")
class PayActivity : AppCompatActivity() {
    @Autowired(name = "orderId")
    lateinit var orderId: String

    @Autowired(name = "price")
    var price: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ARouter.getInstance().inject(this) // 自动注入
        Log.d("PayActivity", "orderId=$orderId, price=$price")
    }
}

4.4 拦截器(企业级核心:登录校验)

场景:未登录用户点击"我的订单",直接跳转到登录页。

kotlin 复制代码
@Interceptor(priority = 8, name = "登录拦截器")
class LoginInterceptor : IInterceptor {
    override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
        val path = postcard?.path
        // 需要登录的页面
        if (path == "/order/activity" || path == "/pay/activity") {
            if (!UserManager.isLogin) {
                // 中断路由,跳转到登录
                ARouter.getInstance().build("/login/activity").navigation()
                callback?.onInterrupt(RuntimeException("未登录"))
                return
            }
        }
        // 放行
        callback?.onContinue(postcard)
    }

    override fun init(context: Context?) {}
}

5 资源隔离与冲突解决(大坑预警)

组件化最大的坑不是代码,而是 资源。资源冲突会导致编译直接失败,或者运行时出现诡异的样式错乱。

5.1 资源命名冲突

如果组件 A 和组件 B 都有一个 btn_confirm.xml,编译时会报错:Resource entry is already defined

解决方案强制资源前缀(Resource Prefix)

gradle.properties 中配置

properties 复制代码
# component-login
resourcePrefix = login_
# component-pay
resourcePrefix = pay_
# component-home
resourcePrefix = home_

build.gradle 中强制

gradle 复制代码
android {
    resourcePrefix 'login_'
}

命名规范(强制执行)

  • Layout: login_activity_main.xml, pay_activity_index.xml
  • Drawable: login_ic_wechat.png, pay_bg_alipay.webp
  • String: login_btn_confirm, pay_title_price
  • Style: LoginTheme, PayButtonStyle

5.2 公共资源下沉

有些资源是全局通用的(如 colors.xml, styles.xml, ic_launcher.png, strings.xml 中的 App 名称)。

策略

  1. 放在 module-basemodule-common 中。
  2. 组件依赖 module-base
  3. 组件自己的资源只给自己用。

注意module-base 中的资源越少越好,否则会成为新的瓶颈。

5.3 Theme 隔离

每个组件可以有自己的 Theme,但最终要继承壳工程的 Theme。

xml 复制代码
<!-- module-base/themes.xml -->
<style name="BaseAppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    <!-- 全局定义:颜色、字体、形状 -->
    <item name="colorPrimary">@color/base_color_primary</item>
</style>

<!-- component-login/themes.xml -->
<style name="LoginTheme" parent="BaseAppTheme">
    <!-- 登录页特有:比如背景是白色 -->
    <item name="android:windowBackground">@color/white</item>
</style>

6 组件初始化:Application 的拆分

以前我们只有一个 Application。现在组件化后,每个组件都需要初始化(如推送、地图、数据库、IM SDK)。

问题:组件没有 Application,怎么初始化?

6.1 方案一:ContentProvider(推荐,无侵入)

Android 在初始化 Application 时,会先初始化所有 ContentProvider。我们可以利用这个机制。

原理

  1. 每个组件定义一个 InitProvider
  2. App 启动时,系统自动调用所有 Provider 的 onCreate

实现

kotlin 复制代码
// module-base/BaseInitProvider.kt
class BaseInitProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        // 初始化基础库(网络、日志、数据库)
        initBaseLibs()
        return true
    }
    // ... 其他方法空实现
}

// component-login/LoginInitProvider.kt
class LoginInitProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        // 初始化登录模块(推送、IM)
        initLoginSdk()
        return true
    }
}

优点 :无侵入,自动调用。

缺点:Provider 过多会影响启动速度(需优化)。

6.2 方案二:接口代理(手动调用,可控)

定义一个初始化接口,壳工程启动时依次调用。

实现

kotlin 复制代码
// module-base/IComponentApplication.kt
interface IComponentApplication {
    fun onCreate(app: Application)
    fun onTerminate() {}
}

// component-login/LoginApplication.kt
class LoginApplication : IComponentApplication {
    override fun onCreate(app: Application) {
        initLoginSdk()
    }
}

// AppShell.kt
class AppShell : Application() {
    override fun onCreate() {
        super.onCreate()
        // 手动调用(可以通过反射,或者维护一个列表)
        LoginApplication().onCreate(this)
        PayApplication().onCreate(this)
        HomeApplication().onCreate(this)
    }
}

优点 :启动顺序可控,方便排查问题。

缺点:需要手动维护调用列表。

6.3 方案三:Jetpack Startup(官方推荐,替代 ContentProvider)

Google 推出了 App Startup 库,专门解决组件初始化问题。

实现

kotlin 复制代码
// module-base/BaseInitializer.kt
class BaseInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        initBaseLibs()
    }
    override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}

// component-login/LoginInitializer.kt
class LoginInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        initLoginSdk()
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        // 依赖 BaseInitializer,确保先初始化基础库
        return listOf(BaseInitializer::class.java)
    }
}

优点 :官方支持,性能好,依赖关系清晰。

缺点:需要引入新库。


7 企业级组件化工程结构(最终形态)

复制代码
ProjectRoot/
├── app-shell/              # 壳工程(空壳,只组装)
│   └── src/main/java/
│       └── AppShell.kt
│
├── components/             # 业务组件(可独立运行)
│   ├── component-login/
│   │   ├── src/main/java/
│   │   │   └── LoginActivity.kt
│   │   ├── src/debug/java/   # 独立运行时的配置
│   │   └── build.gradle
│   ├── component-home/
│   ├── component-pay/
│   └── component-mine/
│
├── modules/               # 基础模块(不可独立运行)
│   ├── module-base/       # 基类、路由、工具
│   ├── module-network/    # 网络封装
│   ├── module-storage/    # 数据库、SP
│   └── module-ui/         # 自定义 View、Style
│
├── build.gradle
├── settings.gradle
└── gradle.properties

8 总结:组件化的"军规"

  1. 组件之间零依赖 :只能通过路由通信,不能 implementation project(:component-login)
  2. 资源必须加前缀:防止冲突,这是红线。
  3. 基础模块下沉:通用代码往下沉,业务代码往上浮。
  4. 独立运行优先:开发时组件能独立跑,不依赖壳工程。
  5. 壳工程要薄:壳工程只做组装和全局配置,不包含业务逻辑。
  6. 初始化要收敛:统一用 Startup 或 Provider,不要在 Application 里写一堆 init。

下一篇预告

系列三:组件化与模块化进阶 | 第9篇:组件化架构从零搭建实战(Gradle 极速配置与编译加速)

我们将深入 Gradle 的 Configuration Cache、Build Cache、并行编译 ,把 10 分钟的编译缩短到 1 分钟以内。同时会讲 多环境配置(Dev/Test/Prod)多渠道打包


如果你的项目已经到了"编译一次去喝杯咖啡"的阶段,请把这篇转给技术负责人。组件化不是选择题,而是生存题。

相关推荐
zhangfeng11332 小时前
光驱动的 AI 算力卡,也就是光子计算(Photonic Computing)芯片,用光子(光)代替电子来做矩阵乘法和数据传输
人工智能·语言模型·矩阵·架构·transformer·芯片
段一凡-华北理工大学3 小时前
工业领域的Hadoop架构学习~系列文章15:机器学习与大数据融合 - 工业智能的算法引擎
大数据·人工智能·hadoop·机器学习·架构·工业智能体·高炉炼铁智能化
会Tk矩阵群控的小木3 小时前
小红书矩阵系统2026最新技术架构与多账号自动化运营实战
运维·矩阵·架构·自动化·个人开发
Solis程序员3 小时前
解决双写不一致!Canal+Outbox+Kafka 高可靠事件驱动架构
redis·分布式·架构·kafka·canal
商业模式源码开发4 小时前
知识付费推三返一模式详解:规则设计、分红算法与合规架构
算法·架构·推三返一
GIOTTO情4 小时前
智能媒介投放技术迭代:从人工规则调控到AI全域动态调度的架构演进
人工智能·架构
珠海西格电力4 小时前
西格电力零碳园区管理系统的技术架构是怎样的?
大数据·运维·人工智能·物联网·架构·能源
zhangfeng11334 小时前
定制化,面向大语言模型的GPU,Etched 把 Transformer 架构直接“烧“进硅片
语言模型·架构·transformer·芯片