从替代到粒度:Compose Multiplatform 的决策模式重构已开启跨平台新时代

摘要:2025 年 5 月,Compose Multiplatform 的 iOS 支持进入稳定版。JetBrains 的发布说明里有一个容易被忽略的表述:它不要求团队在"全共享"与"全原生"之间二选一,而是允许一次一个屏幕、一个功能地接入。多数评论将其归入"跨平台 UI 又多了一个选项"的叙事,但这个表述的实际含义更深:它把跨平台的衡量单位从"框架"换成了"粒度"。衡量单位一变,决策模式就跟着变;决策模式一变,一个新时代就真正开始了。
TL;DR

  • 替代逻辑的组织后果:框架替团队选边,共享越高让渡越多,选型变成路线站队。
  • 粒度可调的技术根基:CMP 双向互操作,共享边界从固定参数变成可移动的设计变量。
  • 决策模式转变:从启动期一次做对的预测型,转为迭代中低成本试错的实验型。
  • Conway 定律反转:过去架构决定组织,现在组织先定协作结构再选共享粒度。
  • 成本与经济效应:门槛从选边移到治理与能力;账本从复用率转向能力配置效率。
    中心论点:CMP 稳定版加上与 SwiftUI/UIKit 的双向互操作,已经完成了一次从替代逻辑到粒度逻辑的跨平台决策模式重构,而这次重构已经开启了跨平台开发的新时代。
    所谓"新时代":不是修辞上的拔高,而是指一个可观察的事实------共享边界的位置,从框架架构的固定参数,变成了团队可以主动放置的设计变量;跨平台决策,从"选边"变成"画线",从预测型决策变成实验型决策;定义权,从框架手里回到了团队手里。
    论证依据:这一重构不是理论推演------KINTO、TechnoAlpin、Instabee、Respawn Pro 等团队的生产实践已经展示了新旧两种决策模式的分野。粒度自由有明确的技术成本,这些成本最终转化为组织能力要求,也转化为可观察的经济效应。
    结论:CMP 的贡献不在于给出了更好的答案,而在于让问题本身变得可以被不同地回答。而"可以被不同地回答",正是新时代最本质的特征。

目录

一、替代逻辑的组织后果:新时代要终结的是什么

从 Cordova 到 Flutter,跨平台框架的底层命题高度一致:减少重复的最自然方式是用一种统一的编写方式替代多种。这个命题在代码层面成立,在组织层面却制造了一个隐藏的零和博弈------共享程度越高,平台原生能力被让渡得越多;原生投入越大,跨平台叙事越显得不彻底。团队被迫在"彻底跨平台"和"彻底原生"之间站队。

Cordova 用 WebView 替代原生 UI 层,被替代的体验差异变成一条需要持续修补的裂缝。Xamarin 用 C# 统一语言,却统一不了两套 UI 思维,组织上仍需维持平台专属能力。React Native 用桥接翻译两端指令,桥接层成为同时属于两个团队、又同时不属于任何团队的模糊地带。Airbnb 2018 年逐步放弃 RN,公开理由是性能与维护成本,但关键数据在组织侧:据 Airbnb 公开分享的数据,跨平台代码从未超过移动代码库的 15% 到 20%,而维护混合栈的复杂度超过了纯原生或纯跨平台任何一条路径。桥接层在技术上是可测量的延迟,在组织上却是不可测量的摩擦------一个问题跨越桥接层时,修复它需要两侧协调,协调成本高于修复本身。

Flutter 用自绘引擎将平台 UI 层整体替换为 Dart 世界,共享一致性更高,但继承了"全有或全无"的组织逻辑。团队需围绕 Dart、Widget 树和自绘管线重组能力结构;平台特有能力仍需同时写 Dart 接口和两端原生实现。add-to-app 提供了渐进式路径,但嵌入单位是"一个 Flutter 引擎实例",粒度停在模块级,引擎生命周期、路由和内存复杂度需额外处理。

替代逻辑的深层成本不是某一行代码写了两遍,而是每个团队被迫在"跨平台"和"原生"之间选择一个身份,然后为这个身份辩护。选型会议上的争论很少在比较技术指标,它们在争夺一个问题的答案:我们是谁?替代时代最深的成本,是它把技术选型变成了路线站队。新时代要终结的,正是这种非此即彼的站队逻辑------不是让跨平台战胜原生,也不是让原生战胜跨平台,而是让"选边"这个动作本身失去必要性。

下图概括了替代逻辑下各框架的组织后果:
#mermaid-svg-lCHYUCHnkYrG7Sjq{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-lCHYUCHnkYrG7Sjq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lCHYUCHnkYrG7Sjq .error-icon{fill:#552222;}#mermaid-svg-lCHYUCHnkYrG7Sjq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lCHYUCHnkYrG7Sjq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .marker.cross{stroke:#333333;}#mermaid-svg-lCHYUCHnkYrG7Sjq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lCHYUCHnkYrG7Sjq p{margin:0;}#mermaid-svg-lCHYUCHnkYrG7Sjq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .cluster-label text{fill:#333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .cluster-label span{color:#333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .cluster-label span p{background-color:transparent;}#mermaid-svg-lCHYUCHnkYrG7Sjq .label text,#mermaid-svg-lCHYUCHnkYrG7Sjq span{fill:#333;color:#333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .node rect,#mermaid-svg-lCHYUCHnkYrG7Sjq .node circle,#mermaid-svg-lCHYUCHnkYrG7Sjq .node ellipse,#mermaid-svg-lCHYUCHnkYrG7Sjq .node polygon,#mermaid-svg-lCHYUCHnkYrG7Sjq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lCHYUCHnkYrG7Sjq .rough-node .label text,#mermaid-svg-lCHYUCHnkYrG7Sjq .node .label text,#mermaid-svg-lCHYUCHnkYrG7Sjq .image-shape .label,#mermaid-svg-lCHYUCHnkYrG7Sjq .icon-shape .label{text-anchor:middle;}#mermaid-svg-lCHYUCHnkYrG7Sjq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lCHYUCHnkYrG7Sjq .rough-node .label,#mermaid-svg-lCHYUCHnkYrG7Sjq .node .label,#mermaid-svg-lCHYUCHnkYrG7Sjq .image-shape .label,#mermaid-svg-lCHYUCHnkYrG7Sjq .icon-shape .label{text-align:center;}#mermaid-svg-lCHYUCHnkYrG7Sjq .node.clickable{cursor:pointer;}#mermaid-svg-lCHYUCHnkYrG7Sjq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .arrowheadPath{fill:#333333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lCHYUCHnkYrG7Sjq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lCHYUCHnkYrG7Sjq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lCHYUCHnkYrG7Sjq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lCHYUCHnkYrG7Sjq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lCHYUCHnkYrG7Sjq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lCHYUCHnkYrG7Sjq .cluster text{fill:#333;}#mermaid-svg-lCHYUCHnkYrG7Sjq .cluster span{color:#333;}#mermaid-svg-lCHYUCHnkYrG7Sjq 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-lCHYUCHnkYrG7Sjq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lCHYUCHnkYrG7Sjq rect.text{fill:none;stroke-width:0;}#mermaid-svg-lCHYUCHnkYrG7Sjq .icon-shape,#mermaid-svg-lCHYUCHnkYrG7Sjq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lCHYUCHnkYrG7Sjq .icon-shape p,#mermaid-svg-lCHYUCHnkYrG7Sjq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lCHYUCHnkYrG7Sjq .icon-shape .label rect,#mermaid-svg-lCHYUCHnkYrG7Sjq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lCHYUCHnkYrG7Sjq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lCHYUCHnkYrG7Sjq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lCHYUCHnkYrG7Sjq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 减少重复:统一编写方式替代多种
Cordova:WebView 替代原生 UI
Xamarin:C# 统一语言
React Native:桥接层翻译两端
Flutter:自绘引擎替换 UI 层
体验裂缝需持续修补
两套 UI 思维并存
桥接层成为无人完整拥有的模糊地带
全有或全无的组织逻辑
团队被迫在跨平台与原生之间站队
新时代要终结的:非此即彼的选边逻辑

二、粒度可调的技术根基:新时代赖以成立的前提

CMP 的互操作能力不是修辞层面的"灵活",它有具体的架构根基。在 iOS 上,Compose 通过 Skia 在 Metal 上绘制自己的 UI 树。Skia 是 Chrome、Android 等系统长期使用的成熟 2D 图形库,也曾是 Flutter 的默认渲染引擎;Metal 是 Apple 平台的底层图形 API。这意味着 Compose 的渲染结果是一块由 Skia 绘制的 surface,而非对 UIKit 控件的逐层包装。关键含义在于:CMP 所继承的 Compose UI 架构,不要求它成为 UI 的唯一宿主。它不声称取代 UIKit 或 SwiftUI 的整个存在,可以作为一个视图控制器嵌入更大的原生应用,也可以在自己的布局树里为原生视图留出位置。

JetBrains 的文档给出了双向嵌入路径:ComposeUIViewController 可嵌入 UIKit 容器视图控制器;UIKitView 可将 MKMapView、UITextField 或任何 UIView 子类放进 Compose 布局树。SwiftUI 侧通过 UIHostingController 包装后参与 Compose 的尺寸协商。工程代价明确:互操作区域的触摸事件需要协作策略------默认的协作模式下,Compose 在检测到触摸后引入一个时间窗口,判断自身组件是否消费事件,未消费则交给原生侧;interactionMode 参数可切换为 NonCooperative,让触摸直接传递给互操作视图。尺寸协商要在 Compose 测量逻辑和 UIKit Auto Layout 两套约束系统之间桥接;Skia/UIKit 混合屏幕在内存压力下需更细致的 surface 分配审计,峰值内存取决于两套渲染体系的生命周期是否对齐。

下面用一段 Kotlin 代码演示这两个方向的实际写法:一边把 Compose 界面装进 UIKit 容器,一边在 Compose 布局里嵌入一个 MKMapView。

kotlin 复制代码
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.interop.UIKitView
import androidx.compose.ui.viewinterop.UIKitInteropInteractionMode
import androidx.compose.ui.window.ComposeUIViewController
import platform.MapKit.MKMapView
import platform.UIKit.UIViewController
import platform.UIKit.UIView

// 方向一:把 Compose 界面嵌入 UIKit 容器
// ComposeUIViewController 返回一个标准的 UIViewController,
// 可以直接被 UITabBarController、UINavigationController 或自定义容器持有。
fun makeComposeScreen(): UIViewController = ComposeUIViewController {
    // 这里写任意 Compose 内容,它由 Skia 在 Metal 上绘制,
    // 与宿主 UIKit 视图树是两套渲染体系,互不逐层包装。
    MapScreen()
}

// 方向二:在 Compose 布局中嵌入原生 MKMapView
@Composable
fun MapScreen() {
    UIKitView(
        factory = {
            // factory 只在首次组合时调用,负责创建原生视图实例
            MKMapView(frame = platform.CoreGraphics.CGRectZero.readValue())
        },
        modifier = Modifier.fillMaxSize(),
        // interactionMode 决定触摸事件由谁消费:
        // Cooperative(默认)------Compose 先判断自身组件是否消费触摸,
        //   未消费才交给原生视图,适合 Compose 与原生控件叠放的场景;
        // NonCooperative------触摸直接透传给原生视图,
        //   适合地图、滚动列表这类需要完整手势语义的原生控件。
        interactionMode = UIKitInteropInteractionMode.NonCooperative,
        // onResize 是尺寸协商的落点:Compose 测量完成后把最终尺寸回传给原生视图,
        // 原生侧据此更新 frame,避免与 UIKit Auto Layout 的约束系统互相打架。
        onResize = { view: UIView, rect ->
            view.setFrame(rect)
        }
    )
}

这段代码里有两个容易被忽略的关键点。其一是 interactionMode 的协作语义 :默认的 Cooperative 模式会在 Compose 检测到触摸后引入一个时间窗口,判断自身组件是否消费事件,未消费才交给原生侧;一旦把地图这类手势密集的控件嵌进来,通常要显式切到 NonCooperative,否则会出现「拖不动地图」或「手势被 Compose 抢走」的问题。其二是尺寸协商 :Compose 的测量逻辑与 UIKit 的 Auto Layout 是两套约束系统,onResize 回调是把前者结果同步给后者的唯一通道,原生视图的 frame 必须在这里更新,而不是依赖原生侧的约束自动求解------否则混合屏幕在旋转、分屏或键盘弹出时容易出现尺寸错位。

这些代价的性质变了。它们不再是"框架未能统一一切"的失败证明,而是粒度选择本身的技术成本。在替代逻辑里,未被共享的代码是框架的失败;在粒度逻辑里,未被共享的代码是组织主动选择的结果,有明确理由、所有者和成本预算。一个团队可以坦然地说"这个屏幕用原生写,因为交互密度太高,混合渲染的调优成本不划算",而不必觉得这句话背叛了跨平台。

React Native 的桥接在架构上也是混合,但混合位置由桥接层作为通信边界这一事实所规定,团队无法在"桥接多少"上获得连续调节------要么通过桥接通信,要么写原生模块,边界是框架设计给定的。CMP 的双向互操作把边界位置的决策权交给使用方:可以只共享 ViewModel 和网络层,UI 全部留在 SwiftUI;可以把一组复杂交互图表放进 Compose,其余保持原生;也可以从共享逻辑开始,逐步把 UI 迁移到 Compose。

一个容易被忽略但关键属性是可逆性 。替代时代的选型是一次性阵营决定------选了 Flutter,整个应用进入 Dart 世界;选了 RN,桥接层成为架构的固定组成部分。回退成本极高,决策必须一次做对,而"一次做对"在快速变化的移动产品里几乎不可能。粒度可调之后,共享边界变成一条可以移动的线:今天共享网络层和领域模型,明年推状态管理,后年把某个功能模块的 UI 纳入共享。每次移动是一次小规模实验,失败可退回,成功则继续推进。可逆的决策,才敢做得早。

JetBrains 和 Google 双方的支持结构强化了这一转变的可信度。Google 在 Android Studio 中提供 KMP Shared Module 模板,让 Android 开发者在现有应用中添加共享模块。Android 官方 Jetpack 库中,Room 和 DataStore 已稳定支持 KMP,ViewModel 也在 2025 年加入了 KMP 支持。共享代码使用的是平台一方 API 在跨平台语境下的延伸,而非第三方对平台能力的模仿。Ktor 提供跨平台网络栈,SQLDelight 和 Room 覆盖持久化,Coroutines 和 Flow 覆盖并发与响应式数据流。一个 Android 团队可以在现有项目里加一个 shared 模块,从网络 DTO 和领域模型开始,逐步观察收益和摩擦------这是增量,不是搬家。

粒度可调从架构属性变成了可用能力,这是新时代赖以成立的技术前提。没有这个前提,决策模式的重构就无从谈起,新时代也无从开启。CMP 稳定版的意义,不在于它发明了互操作,而在于它让互操作从实验性能力变成了生产级选项------新时代的硬件条件,到这一刻才算配齐。

CMP 的双向互操作与共享边界可移动性,可以用下图概括:
#mermaid-svg-guyUqPtszfIsivJV{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-guyUqPtszfIsivJV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-guyUqPtszfIsivJV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-guyUqPtszfIsivJV .error-icon{fill:#552222;}#mermaid-svg-guyUqPtszfIsivJV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-guyUqPtszfIsivJV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-guyUqPtszfIsivJV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-guyUqPtszfIsivJV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-guyUqPtszfIsivJV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-guyUqPtszfIsivJV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-guyUqPtszfIsivJV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-guyUqPtszfIsivJV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-guyUqPtszfIsivJV .marker.cross{stroke:#333333;}#mermaid-svg-guyUqPtszfIsivJV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-guyUqPtszfIsivJV p{margin:0;}#mermaid-svg-guyUqPtszfIsivJV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-guyUqPtszfIsivJV .cluster-label text{fill:#333;}#mermaid-svg-guyUqPtszfIsivJV .cluster-label span{color:#333;}#mermaid-svg-guyUqPtszfIsivJV .cluster-label span p{background-color:transparent;}#mermaid-svg-guyUqPtszfIsivJV .label text,#mermaid-svg-guyUqPtszfIsivJV span{fill:#333;color:#333;}#mermaid-svg-guyUqPtszfIsivJV .node rect,#mermaid-svg-guyUqPtszfIsivJV .node circle,#mermaid-svg-guyUqPtszfIsivJV .node ellipse,#mermaid-svg-guyUqPtszfIsivJV .node polygon,#mermaid-svg-guyUqPtszfIsivJV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-guyUqPtszfIsivJV .rough-node .label text,#mermaid-svg-guyUqPtszfIsivJV .node .label text,#mermaid-svg-guyUqPtszfIsivJV .image-shape .label,#mermaid-svg-guyUqPtszfIsivJV .icon-shape .label{text-anchor:middle;}#mermaid-svg-guyUqPtszfIsivJV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-guyUqPtszfIsivJV .rough-node .label,#mermaid-svg-guyUqPtszfIsivJV .node .label,#mermaid-svg-guyUqPtszfIsivJV .image-shape .label,#mermaid-svg-guyUqPtszfIsivJV .icon-shape .label{text-align:center;}#mermaid-svg-guyUqPtszfIsivJV .node.clickable{cursor:pointer;}#mermaid-svg-guyUqPtszfIsivJV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-guyUqPtszfIsivJV .arrowheadPath{fill:#333333;}#mermaid-svg-guyUqPtszfIsivJV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-guyUqPtszfIsivJV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-guyUqPtszfIsivJV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-guyUqPtszfIsivJV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-guyUqPtszfIsivJV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-guyUqPtszfIsivJV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-guyUqPtszfIsivJV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-guyUqPtszfIsivJV .cluster text{fill:#333;}#mermaid-svg-guyUqPtszfIsivJV .cluster span{color:#333;}#mermaid-svg-guyUqPtszfIsivJV 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-guyUqPtszfIsivJV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-guyUqPtszfIsivJV rect.text{fill:none;stroke-width:0;}#mermaid-svg-guyUqPtszfIsivJV .icon-shape,#mermaid-svg-guyUqPtszfIsivJV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-guyUqPtszfIsivJV .icon-shape p,#mermaid-svg-guyUqPtszfIsivJV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-guyUqPtszfIsivJV .icon-shape .label rect,#mermaid-svg-guyUqPtszfIsivJV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-guyUqPtszfIsivJV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-guyUqPtszfIsivJV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-guyUqPtszfIsivJV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原生侧
Compose Multiplatform 侧
Compose UI 树(Skia 在 Metal 上绘制)
UIKitView:嵌入原生视图
UIKit 容器视图控制器
SwiftUI(经 UIHostingController 包装)
共享边界可移动:网络层 → 领域模型 → 功能模块 → 屏幕 → 单个视图
可逆性:每次移动是小规模实验,失败可退回

五种方案在四个维度上的差异,可以横向对照如下:

方案 共享边界由谁决定 边界可调粒度 回退成本 互操作方向
Cordova 框架:WebView 替代原生 UI 层 应用级,边界固定在 WebView 与原生之间 高,需整体迁移 单向,Web 内容嵌入原生壳
Xamarin 框架:以 C# 统一语言 语言级,UI 仍需两套思维 高,需整体迁移 单向,共享代码调用平台 API
React Native 框架:桥接层作为通信边界 模块级,桥接多少无法连续调节 高,桥接层成为架构固定组成部分 单向,经桥接层翻译两端指令
Flutter 框架:自绘引擎替换 UI 层 模块级,add-to-app 以引擎实例为嵌入单位 高,整个应用进入 Dart 世界 单向,平台能力经插件窄门进入
CMP 团队:共享边界由使用方主动放置 连续可调,可从网络层到单个视图逐级移动 低,每次移动是小规模实验,失败可退回 双向,Compose 与 SwiftUI/UIKit 互相嵌入

三、决策模式的重构:新时代最核心的制度变化

替代时代的跨平台选型是"高门槛、低频率、高回退成本"决策。它发生在项目启动阶段,由架构师或技术负责人主导,输出框架对比矩阵,团队选一个,接下来两三年沿着这个选择走。前提是选型不可逆,所以必须一次做对------这要求决策者在信息不完整的情况下预测未来两三年的团队结构、产品节奏和技术演进。于是选型会议往往演变成信仰辩论:支持 Flutter 的人列举共享率和渲染一致性,支持原生的人列举体验细节和平台能力,双方都有道理,但道理之间无法比较,因为它们在回答不同的问题。

粒度时代把这个决策拆成多个"低门槛、高频率、低回退成本"的小决策:第一个迭代回答"这个模块的网络层要不要共享",第三个迭代回答"这个列表页的 UI 要不要用 Compose 写",第六个迭代回答"这个复杂图表要不要从原生迁移到 Compose"。每个小决策的影响范围有限,回退成本低,可以在两三周内看到真实反馈。决策依据从"框架对比矩阵"变成"这个具体接缝上的协作成本"。这是从预测型决策 到实验型决策的转变。

这个转变不是停留在方法论文档里的建议,它已经在生产团队的工作方式中发生。KINTO 的 KMP 团队不是先做了一次"该不该用 CMP"的全局决策,而是在库这一层先共享,再观察 iOS 团队的集成反馈;TechnoAlpin 不是把整个应用迁到 CMP,而是先在隐藏调试屏幕里验证三个功能,再推到生产。这些团队没有在"替代"和"原生"之间选边,他们在画线,然后根据线的反馈决定下一步画到哪里。

实验型决策改变了讨论的质量。当讨论对象是具体模块的共享边界时,争论聚焦在可验证的事实上:这个模块的 UI 在两个平台上的差异有多大?共享后谁来维护互操作边界的调优?回退到原生需要多少工作量?这些问题有答案,或至少可以被实验验证。当讨论对象是"该不该用跨平台框架"时,争论滑向抽象的价值判断,因为问题太大,无法用任何一个具体案例证伪。粒度时代把大问题拆成小问题,不是为了逃避大问题,而是为了让大问题变得可以被诚实地回答。

可逆性还改变了团队对失败的容忍度。替代时代选型失败意味着一次昂贵的迁移,团队会本能地回避可能失败的选择,倾向于"最安全"的方案------继续纯原生,或选择已被大厂验证的框架。这种风险规避是理性的,但它抑制了实验。粒度时代一次小规模共享实验的失败成本很低,团队可以承担更多实验,也就能更快找到真正适合自己的共享边界。创新往往不是来自一次正确的宏大决策,而是来自多次低成本试错后的收敛。

决策权从架构层下放到工程层,带来的不只是决策质量的提升,还有工程师对技术选择的 ownership 感。一个参与了"这个屏幕该不该共享"讨论的工程师,比一个被告知"我们用 Flutter"的工程师,更可能对最终技术方案负责。决策模式的重构,正是新时代最核心的制度变化------它把"谁来决定共享边界"这个问题,从架构师的办公桌搬到了工程师的迭代会议里。新时代不是靠某一次发布宣告成立的,而是靠这种决策方式的日常化运转逐步确立的。

从预测型决策到实验型决策的转变,可以表示为:
#mermaid-svg-xcgl7R1yfdvrWVLa{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-xcgl7R1yfdvrWVLa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xcgl7R1yfdvrWVLa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xcgl7R1yfdvrWVLa .error-icon{fill:#552222;}#mermaid-svg-xcgl7R1yfdvrWVLa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xcgl7R1yfdvrWVLa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xcgl7R1yfdvrWVLa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xcgl7R1yfdvrWVLa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xcgl7R1yfdvrWVLa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xcgl7R1yfdvrWVLa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xcgl7R1yfdvrWVLa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xcgl7R1yfdvrWVLa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xcgl7R1yfdvrWVLa .marker.cross{stroke:#333333;}#mermaid-svg-xcgl7R1yfdvrWVLa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xcgl7R1yfdvrWVLa p{margin:0;}#mermaid-svg-xcgl7R1yfdvrWVLa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xcgl7R1yfdvrWVLa .cluster-label text{fill:#333;}#mermaid-svg-xcgl7R1yfdvrWVLa .cluster-label span{color:#333;}#mermaid-svg-xcgl7R1yfdvrWVLa .cluster-label span p{background-color:transparent;}#mermaid-svg-xcgl7R1yfdvrWVLa .label text,#mermaid-svg-xcgl7R1yfdvrWVLa span{fill:#333;color:#333;}#mermaid-svg-xcgl7R1yfdvrWVLa .node rect,#mermaid-svg-xcgl7R1yfdvrWVLa .node circle,#mermaid-svg-xcgl7R1yfdvrWVLa .node ellipse,#mermaid-svg-xcgl7R1yfdvrWVLa .node polygon,#mermaid-svg-xcgl7R1yfdvrWVLa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xcgl7R1yfdvrWVLa .rough-node .label text,#mermaid-svg-xcgl7R1yfdvrWVLa .node .label text,#mermaid-svg-xcgl7R1yfdvrWVLa .image-shape .label,#mermaid-svg-xcgl7R1yfdvrWVLa .icon-shape .label{text-anchor:middle;}#mermaid-svg-xcgl7R1yfdvrWVLa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xcgl7R1yfdvrWVLa .rough-node .label,#mermaid-svg-xcgl7R1yfdvrWVLa .node .label,#mermaid-svg-xcgl7R1yfdvrWVLa .image-shape .label,#mermaid-svg-xcgl7R1yfdvrWVLa .icon-shape .label{text-align:center;}#mermaid-svg-xcgl7R1yfdvrWVLa .node.clickable{cursor:pointer;}#mermaid-svg-xcgl7R1yfdvrWVLa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xcgl7R1yfdvrWVLa .arrowheadPath{fill:#333333;}#mermaid-svg-xcgl7R1yfdvrWVLa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xcgl7R1yfdvrWVLa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xcgl7R1yfdvrWVLa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xcgl7R1yfdvrWVLa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xcgl7R1yfdvrWVLa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xcgl7R1yfdvrWVLa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xcgl7R1yfdvrWVLa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xcgl7R1yfdvrWVLa .cluster text{fill:#333;}#mermaid-svg-xcgl7R1yfdvrWVLa .cluster span{color:#333;}#mermaid-svg-xcgl7R1yfdvrWVLa 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-xcgl7R1yfdvrWVLa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xcgl7R1yfdvrWVLa rect.text{fill:none;stroke-width:0;}#mermaid-svg-xcgl7R1yfdvrWVLa .icon-shape,#mermaid-svg-xcgl7R1yfdvrWVLa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xcgl7R1yfdvrWVLa .icon-shape p,#mermaid-svg-xcgl7R1yfdvrWVLa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xcgl7R1yfdvrWVLa .icon-shape .label rect,#mermaid-svg-xcgl7R1yfdvrWVLa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xcgl7R1yfdvrWVLa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xcgl7R1yfdvrWVLa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xcgl7R1yfdvrWVLa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 粒度时代:实验型决策
替代时代:预测型决策
决策模式重构
项目启动阶段一次性选型
框架对比矩阵
选定框架,沿用两三年
高门槛、低频率、高回退成本
第一个迭代:网络层要不要共享
第三个迭代:列表页 UI 要不要用 Compose
第六个迭代:复杂图表要不要迁移
低门槛、高频率、低回退成本
观察反馈,继续画线或回退

四、人的能力边界重配置:新时代首先改变的是人

KINTO Technologies 的 Mobile KMP 团队提供了一个清晰样本。团队原本完全由 Android 工程师组成。引入 KMP 和 CMP 后,三名成员的能力结构发生重组:一名从 Android 扩展到 KMP、CMP 和 iOS;一名原本同时具备 iOS 和 Android 背景,转向 KMP 和 CMP;一名从 API 和 Android 背景进入 KMP、CMP 和 iOS。团队以库的形式向 iOS 侧提供共享逻辑和 UI 组件,iOS 团队通过集成共享模块获得功能,无需参与共享代码的内部实现。

关键不在于"Android 工程师学会了 iOS"这句励志叙事,而在于组织的共享粒度选择直接改变了团队的能力构成和协作界面。团队没有要求 iOS 侧放弃 SwiftUI,也没有要求 Android 侧停止使用 Compose;它选择在"库"这一层共享,于是共享边界同时成为能力扩展的边界和协作契约的边界。iOS 团队不需要理解 Kotlin 协程的调度细节,只需消费稳定的库接口;KMP 团队不需要接管 iOS 应用的导航和生命周期,只需保证库在 iOS 运行时的行为符合预期。两边各自保留擅长领域的完整所有权,同时在共享层上形成一个有明确责任人的协作面。

TechnoAlpin 的案例展示了相反方向的运用。其 iOS 应用以 SwiftUI 为主体构建,但团队将三个功能------湿球温度计算器、机房交互图、相关的 Canvas 密集型界面------放进 Compose Multiplatform。这些功能的共同特征是:交互逻辑和视觉结构在两个平台上本质相同,各写一遍是对资源的重复消耗,但不足以构成把整个应用迁移到 CMP 的理由。团队先在内部隐藏调试屏幕中验证 CMP 稳定性,然后扩展到生产功能。这是以功能为单位的粒度行使:跨平台不是应用级的身份标签,而是针对特定问题域的局部工具选择。CMP 在这里扮演的角色不是"替代 SwiftUI 的框架",而是"在 SwiftUI 应用内部处理某一类问题的共享渲染层"。TechnoAlpin 在 Canvas 中渲染大量文本时曾遭遇 iOS 侧帧率跌至个位数,最终通过离屏位图缓存缓解,更根本的修复要等到后续 CMP 版本的上游补丁。这个细节说明,粒度选择不仅决定了"谁写什么",也决定了"谁在出问题时负责调优"。

JetBrains 公开的生产用例覆盖了从极高共享到局部嵌入的整个区间:Respawn Pro 在 Android 和 iOS 之间共享 96% 的代码;Instabee 从现有 Android 代码库出发用 CMP 产出 iOS 应用,共享起点是已有资产;Sony 某项目报告 93% 的 Kotlin 占比和 6 名工程师的团队规模;Physics Wallah 在 CMP for iOS 早期 alpha 阶段即开始采用,服务千万级下载量产品。Netflix 公开分享过在 iOS 和 Android 生产应用中使用 KMP 共享业务逻辑的经验,Cash App 和 McDonald's 也在不同程度上采用 KMP。这些数字的意义不在于"共享率越高越好",而在于它们覆盖了不同粒度档位,说明同一套技术栈已经被不同组织形态以不同粒度使用。共享率不再是一个框架强加的目标,而是组织根据自身团队结构、产品节奏和平台策略主动选定的参数。

这些团队选择共享粒度的依据,往往不是技术上的"能不能共享",而是组织上的"共享之后谁来拥有"。KINTO 选择库作为共享单位,因为库有清晰的接口和责任人;TechnoAlpin 选择功能作为共享单位,因为功能有明确的验收标准和边界;Instabee 以现有 Android 代码库为起点,因为希望复用已有团队的知识资产。粒度选择同时是技术决策和 ownership 决策。

同一套技术方案在两个团队手中走向不同结局,原因在此。视其为成本工具的团队一路共享到 UI,撞在体验细节上------目标函数是复用率,而复用率在 UI 层的边际收益递减最快,体验差异在这一层最容易被用户感知。视其为协作工具的团队从最薄的一层起步,先让两边的人在同一份代码里建立共同语言,再慢慢向上推进------目标函数是协作质量,而协作质量在薄共享层上最容易验证和迭代。前者拿到的是复用率,后者得到的是一个能同时理解两端的人。复用率是一个季度就能看到的数字,而一个能同时理解两端的人,是三年后还在产生复利的东西。

粒度时代最深远的人才影响,在于它打破了"平台专家"和"跨平台工程师"的二元身份。一个工程师可以同时是 iOS 专家和共享层贡献者,身份不再由所使用的框架定义,而由所负责的接缝定义。一个原本认为"只写 Swift"的工程师,可能在参与共享层实验后发现,对 Kotlin 协程的理解反过来加深了对 Swift async/await 的认知。这种跨边界的认知迁移,是单平台深耕很难产生的副产品。新时代首先改变的是人------不是人的技能清单,而是人对"我能负责什么边界"的想象。决策模式重构释放出的空间,最终要由人来填充;而人填充这个空间的方式,又反过来定义了新时代的实际形态。

不同团队在共享粒度上的选择,构成了一个从高共享到局部嵌入的连续区间:
#mermaid-svg-LtqEhMgUtWJSMI5H{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-LtqEhMgUtWJSMI5H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LtqEhMgUtWJSMI5H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LtqEhMgUtWJSMI5H .error-icon{fill:#552222;}#mermaid-svg-LtqEhMgUtWJSMI5H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LtqEhMgUtWJSMI5H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LtqEhMgUtWJSMI5H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LtqEhMgUtWJSMI5H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LtqEhMgUtWJSMI5H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LtqEhMgUtWJSMI5H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LtqEhMgUtWJSMI5H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LtqEhMgUtWJSMI5H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LtqEhMgUtWJSMI5H .marker.cross{stroke:#333333;}#mermaid-svg-LtqEhMgUtWJSMI5H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LtqEhMgUtWJSMI5H p{margin:0;}#mermaid-svg-LtqEhMgUtWJSMI5H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LtqEhMgUtWJSMI5H .cluster-label text{fill:#333;}#mermaid-svg-LtqEhMgUtWJSMI5H .cluster-label span{color:#333;}#mermaid-svg-LtqEhMgUtWJSMI5H .cluster-label span p{background-color:transparent;}#mermaid-svg-LtqEhMgUtWJSMI5H .label text,#mermaid-svg-LtqEhMgUtWJSMI5H span{fill:#333;color:#333;}#mermaid-svg-LtqEhMgUtWJSMI5H .node rect,#mermaid-svg-LtqEhMgUtWJSMI5H .node circle,#mermaid-svg-LtqEhMgUtWJSMI5H .node ellipse,#mermaid-svg-LtqEhMgUtWJSMI5H .node polygon,#mermaid-svg-LtqEhMgUtWJSMI5H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LtqEhMgUtWJSMI5H .rough-node .label text,#mermaid-svg-LtqEhMgUtWJSMI5H .node .label text,#mermaid-svg-LtqEhMgUtWJSMI5H .image-shape .label,#mermaid-svg-LtqEhMgUtWJSMI5H .icon-shape .label{text-anchor:middle;}#mermaid-svg-LtqEhMgUtWJSMI5H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LtqEhMgUtWJSMI5H .rough-node .label,#mermaid-svg-LtqEhMgUtWJSMI5H .node .label,#mermaid-svg-LtqEhMgUtWJSMI5H .image-shape .label,#mermaid-svg-LtqEhMgUtWJSMI5H .icon-shape .label{text-align:center;}#mermaid-svg-LtqEhMgUtWJSMI5H .node.clickable{cursor:pointer;}#mermaid-svg-LtqEhMgUtWJSMI5H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LtqEhMgUtWJSMI5H .arrowheadPath{fill:#333333;}#mermaid-svg-LtqEhMgUtWJSMI5H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LtqEhMgUtWJSMI5H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LtqEhMgUtWJSMI5H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LtqEhMgUtWJSMI5H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LtqEhMgUtWJSMI5H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LtqEhMgUtWJSMI5H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LtqEhMgUtWJSMI5H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LtqEhMgUtWJSMI5H .cluster text{fill:#333;}#mermaid-svg-LtqEhMgUtWJSMI5H .cluster span{color:#333;}#mermaid-svg-LtqEhMgUtWJSMI5H 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-LtqEhMgUtWJSMI5H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LtqEhMgUtWJSMI5H rect.text{fill:none;stroke-width:0;}#mermaid-svg-LtqEhMgUtWJSMI5H .icon-shape,#mermaid-svg-LtqEhMgUtWJSMI5H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LtqEhMgUtWJSMI5H .icon-shape p,#mermaid-svg-LtqEhMgUtWJSMI5H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LtqEhMgUtWJSMI5H .icon-shape .label rect,#mermaid-svg-LtqEhMgUtWJSMI5H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LtqEhMgUtWJSMI5H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LtqEhMgUtWJSMI5H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LtqEhMgUtWJSMI5H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Respawn Pro:共享 96% 代码
Sony 某项目:93% Kotlin 占比
Instabee:以现有 Android 代码库为起点
KINTO:以库为单位共享逻辑与 UI 组件
TechnoAlpin:仅三个功能放进 CMP
共享粒度由组织决定,而非框架给定

五、Conway 定律的反转:新时代的组织逻辑

Conway 定律的经典表述是:系统的架构会复制组织的沟通结构。在跨平台语境下,这条定律长期被动发挥作用------选择 Flutter,团队就被推向"一个围绕 Dart 和 Widget 树组织起来的团队包揽所有端、平台能力经插件窄门进入"的结构;选择 RN,团队就被推向"JS/React 团队与原生侧围绕桥接层分工"的双侧结构。两种框架预设的组织形态不同,但共同点是:形态由框架给定,团队只能在给定形态内分配人员,无法调节边界位置。选择的成本很高:要么接受框架暗示的分工,要么在框架之外额外建设协调机制来抵消其影响。大多数团队选择前者,因为后者需要额外的管理精力。

当共享粒度变成连续变量之后,Conway 定律的作用方向出现了反转。团队可以先决定自己想要什么样的协作结构,然后选择与之匹配的共享粒度。 希望保持 iOS 和 Android 各自端到端功能所有权的组织,可以把共享层收窄到网络、存储和领域模型;希望以少数全栈移动工程师覆盖多端的组织,可以把共享层扩展到 UI,同时保留通过互操作接入平台特有能力的通道;拥有强势 iOS 设计传统的组织,可以让 SwiftUI 继续拥有界面主导权,只在逻辑密集或 Canvas 密集型的功能模块中引入 CMP。技术选型所回答的不再是"哪个框架更好",而是"我们想要什么样的团队边界,以及哪条共享线最能承载这条边界"。

这与团队拓扑学中"断裂面"的思路形成呼应:软件系统存在自然接缝,沿着接缝切分可以使每一块落在一个团队的认知负荷和所有权范围之内。CMP 的贡献在于把"接缝位置"从框架的固定架构中解放出来,使其成为可以由组织意图放置的设计参数。过去,接缝位置是框架作者在架构层面预先设定的------桥接层的位置、渲染引擎的归属、原生模块的接口形态------团队只能在给定接缝上分配人员。现在,接缝可以沿着屏幕边界、功能模块边界、甚至单个视图的边界划定。

"代码复用率"作为单一指标,在粒度视角下失去了作为决策终点的资格。一个 60% 共享率的架构,如果共享线恰好落在团队协作最顺畅的接缝上,可能比一个 90% 共享率但迫使两个团队围绕巨型共享模块持续谈判的架构更健康。Airbnb 的教训从反面说明了这一点:RN 的共享率理论上可达 30% 到 40%,但桥接层制造了一个需要两侧共同承担、却没有人能够完整拥有的模糊地带。CMP 的互操作设计并不能自动消除模糊地带,但它提供了更丰富的接缝选项,让组织有机会把模糊地带放在一个有人能够对其负责的位置上。你可以让互操作边界落在屏幕级别,由一个功能团队完整拥有这个屏幕的两侧;也可以让边界落在库级别,由共享库团队拥有接口契约,由平台团队拥有集成行为。关键在于,边界的位置是一个可以被讨论、被记录、被复盘的设计决策。

组织设计视角还带来一个反直觉的结论:共享粒度不是越细越好,也不是越粗越好,而是要与组织的协作带宽匹配。 共享层太薄,跨平台收益不足以覆盖互操作成本;共享层太厚,共享模块变成无人能够完整理解的庞然大物,协作带宽被会议和协调消耗殆尽。最优粒度不是技术常数,而是随团队规模、能力分布、产品阶段动态变化的变量。粒度时代的组织能力,核心在于持续感知这个变量并做出调整。Conway 定律的反转,就是新时代的组织逻辑:过去是架构决定组织,现在是组织决定架构。 这一反转不是某一天突然发生的,而是决策模式重构在日常协作中逐步落地之后,组织侧必然出现的连锁反应。

Conway 定律在粒度时代的作用方向反转,可以这样表示:
#mermaid-svg-gyfZ6UZ2FgxTkbVF{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-gyfZ6UZ2FgxTkbVF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .error-icon{fill:#552222;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .marker.cross{stroke:#333333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF p{margin:0;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .cluster-label text{fill:#333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .cluster-label span{color:#333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .cluster-label span p{background-color:transparent;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .label text,#mermaid-svg-gyfZ6UZ2FgxTkbVF span{fill:#333;color:#333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .node rect,#mermaid-svg-gyfZ6UZ2FgxTkbVF .node circle,#mermaid-svg-gyfZ6UZ2FgxTkbVF .node ellipse,#mermaid-svg-gyfZ6UZ2FgxTkbVF .node polygon,#mermaid-svg-gyfZ6UZ2FgxTkbVF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .rough-node .label text,#mermaid-svg-gyfZ6UZ2FgxTkbVF .node .label text,#mermaid-svg-gyfZ6UZ2FgxTkbVF .image-shape .label,#mermaid-svg-gyfZ6UZ2FgxTkbVF .icon-shape .label{text-anchor:middle;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .rough-node .label,#mermaid-svg-gyfZ6UZ2FgxTkbVF .node .label,#mermaid-svg-gyfZ6UZ2FgxTkbVF .image-shape .label,#mermaid-svg-gyfZ6UZ2FgxTkbVF .icon-shape .label{text-align:center;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .node.clickable{cursor:pointer;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .arrowheadPath{fill:#333333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gyfZ6UZ2FgxTkbVF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gyfZ6UZ2FgxTkbVF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gyfZ6UZ2FgxTkbVF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .cluster text{fill:#333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .cluster span{color:#333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF 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-gyfZ6UZ2FgxTkbVF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gyfZ6UZ2FgxTkbVF rect.text{fill:none;stroke-width:0;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .icon-shape,#mermaid-svg-gyfZ6UZ2FgxTkbVF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .icon-shape p,#mermaid-svg-gyfZ6UZ2FgxTkbVF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .icon-shape .label rect,#mermaid-svg-gyfZ6UZ2FgxTkbVF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gyfZ6UZ2FgxTkbVF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gyfZ6UZ2FgxTkbVF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gyfZ6UZ2FgxTkbVF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 粒度时代:组织决定架构
替代时代:架构决定组织
共享粒度成为连续变量
选定框架
框架预设组织形态
团队在给定形态内分配人员
先确定想要的协作结构
选择匹配的共享粒度
共享线落在自然接缝上
随团队能力与产品阶段移动这条线

六、粒度自由的技术与治理成本:新时代的门槛

CMP 的粒度灵活性有明确的技术和运维代价,这些代价反馈到组织设计上。粒度时代把门槛从"选边"转移到了"为所选的边界负责"。新时代不是没有门槛的时代,而是门槛换了位置的时代。

二进制体积是最常被提及的约束。CMP 在 iOS 上携带 Skia 渲染栈和 Kotlin/Native 运行时,最小应用的二进制体积相比纯 SwiftUI 应用多出数 MB 到十 MB 量级,社区常见数据在 9 MB 左右,具体数字随版本、架构切片和链接选项变化。在包体敏感场景中------目标安装包控制在 10 MB 以下的工具型应用,或面向新兴市场低端设备的应用------这个增量可能直接改变技术选型结论。链接器裁剪、资源压缩、按需加载可以缓解,但需要额外工程投入,优化空间有上限。

内存和渲染性能是第二条约束线。Skia 与 UIKit 混合屏幕在 3 GB 内存设备上面临额外 surface 分配压力,需要针对性审计和缓存策略。Compose 与原生渲染管线有不同资源生命周期,混合屏幕的峰值内存取决于两者生命周期是否重叠、surface 是否及时释放。TechnoAlpin 的 Canvas 文本渲染帧率问题是一个具体例证:选择在 iOS 上使用 Compose 渲染,意味着接受一套与原生渲染管线不同的性能特征,并在需要时投入工程资源去理解和调优。

学习曲线和调试能力是第三条约束线。Kotlin/Native 的内存模型与 JVM 有差异,Swift 与 Kotlin 的互操作需要理解类型映射、异常传播、线程约束。一个跨语言边界上的 bug 可能同时涉及 Kotlin 协程调度、Objective-C 运行时消息转发、Swift ARC 语义。CMP 互操作的可靠性建立在团队对边界的理解之上:把互操作当黑盒,边界就是事故高发区;有人在边界上建立技术纵深,边界就可以成为稳定的协作契约。

从组织角度看,这些代价转化为能力要求。行使 CMP 粒度选择权的团队,不仅需要知道"可以共享什么",还需要知道"共享之后谁负责维护共享层的性能与内存行为"。KINTO 的 KMP 团队能够以库的形式向 iOS 侧提供服务,前提是团队内部有人理解 iOS 侧的集成语义和运行时行为。JetBrains 路线图中 Swift Export 在 2026 年走向稳定,目标是让 Kotlin 与 Swift 之间的互操作更加惯用化,减少包装层和手工桥接代码,长期将降低跨语言边界的摩擦。过渡期内,跨边界调试能力和知识分布仍是组织需要主动建设的东西。

粒度思维的完整表述必须包含一个限定条件:粒度自由的价值,取决于组织是否有能力在所选粒度上承担相应的工程责任。 把共享线画在某个位置,意味着在那个位置上建立足够的技术纵深和清晰的 ownership,而不是把"灵活"当作"不需要深入理解任何一侧"的借口。一个只在逻辑层共享的团队仍然需要有人理解 KMP 生成的 iOS 框架如何被 Swift 调用、内存如何管理、异常如何传播;一个在 UI 层高度共享的团队仍然需要有人理解 Skia 渲染在 iOS 上的性能特征和平台交互惯例。粒度自由降低的是"必须全有或全无"的强制成本,不是"理解你所选择的边界"的必要成本。前者是框架强加的,后者是工程固有的。

还有一个容易被忽视的隐性成本:选择本身的治理负担。 替代时代站完队之后,很多决策自动确定------选了 Flutter,就不需要再讨论某个屏幕该用什么写。粒度时代把这种"一次性解决"的便利拿走了,团队需要在每个接缝上做选择,而选择需要信息、讨论和记录。如果组织没有建立支撑高频小决策的机制------清晰的 ownership 规则、可复用的决策模板、定期的边界复盘------粒度自由就会退化成决策瘫痪。这是粒度时代最容易被低估的门槛:它不是技术门槛,而是治理门槛。一个没有治理能力的组织,在替代时代至少还能靠框架的强制分工运转;在粒度时代,它会被自己的自由度压垮。新时代的门槛,不在技术栈里,在组织的决策能力里。 这也是为什么决策模式的重构不是一次性的制度切换,而是一个需要持续建设的过程------门槛换了位置,能力也得跟着换位置。

边界决策记录模板

治理门槛要落地,第一步是把「画线」这个动作变成可复用的记录。下面这份模板可以直接复制到团队的决策文档里,每画一条共享线就填一行:

接缝位置 共享内容 不共享内容 owner(两侧责任人) 验证指标 回退方案 复盘时间点
例:订单列表页 网络层、领域模型、列表状态管理 列表 UI、平台特有交互与动效 共享层:KMP 团队 A;原生侧:iOS 团队 B 首屏耗时、崩溃率、两侧集成工时 列表 UI 回退为纯 SwiftUI,共享层接口保持不变 上线后第 4 周

这份模板与「九、结语」中「把共享边界当成显式的设计产物」是同一件事的两个面:结语提出的是原则,模板给出的是承载原则的载体。结语要求边界被记录、被评审、被复盘,模板里的七个字段正好对应这三步------接缝位置与共享/不共享内容回答「线画在哪里」,owner 与验证指标回答「谁负责、怎么判断成败」,回退方案与复盘时间点回答「失败了怎么退、什么时候回头看」。填满一行,边界就从口头约定变成可交接、可追责的设计产物;空着的行,则提醒团队还有哪条线没被认真对待。

七、粒度时代的经济效应:从复用率到能力配置效率

前六节讨论的是技术根基、决策模式和人的重配置,但它们最终都要回答一个问题:粒度时代到底带来了什么样的经济效应?如果这个新时代不能在成本、收益和资源配置上给出可观察的改善,那它就只是一个概念上的精致,而不是一次真正的模式转换。

传统跨平台框架的经济账本,核心指标只有一个:复用率。共享 80% 的代码,意味着节省 80% 的重复编写成本;共享 40%,就只节省 40%。这个账本简单、可计算,也容易写进汇报材料。但它有一个严重的缺陷:它只计算了"写代码"的成本,没有计算"决策错误"的成本、"协作摩擦"的成本和"平台判断力丧失"的成本。替代时代的高复用率架构,往往在这些隐性成本上付出更多。

粒度时代的经济账本,核心指标从一个变成了三个:复用率、试错成本和能力配置效率。

复用率从目标降格为参数,带来的是边际收益的重新计算。 在替代逻辑下,共享率越高越好,因为每一次未共享的代码都被视为框架的失败。在粒度逻辑下,共享率有一个最优区间:共享层太薄,跨平台收益不足以覆盖互操作成本;共享层太厚,共享模块的维护成本和协作摩擦开始吃掉复用收益。KINTO 选择在"库"这一层共享,TechnoAlpin 选择在"功能"这一层共享,Instabee 选择以现有代码库为起点------这些选择的依据不是"能不能共享更多",而是"共享到这个位置,边际收益是否仍然为正"。一个团队可以理直气壮地说"我们只共享 40%",因为剩下的 60% 是平台判断力的来源,而平台判断力是竞争力的组成部分。复用率不再是一个道德指标,而是一个经济参数------它被优化,但不被崇拜。

试错成本的大幅下降,是粒度时代最直接的经济效应。 替代时代的选型是一次性阵营决定,回退成本极高,所以决策必须一次做对。而"一次做对"在快速变化的移动产品里几乎不可能------这意味着替代时代的决策本身就携带着高昂的隐含风险成本。粒度时代把共享边界变成一条可以移动的线,每次移动是一次小规模实验,失败成本低,回退路径清晰。KINTO 先共享网络层,观察集成反馈,再决定是否往上推;TechnoAlpin 先在隐藏调试屏幕里验证三个功能,再扩展到生产。这些实验的经济意义不在于"省了多少代码",而在于用极低的成本获得了关于"什么该共享、什么不该共享"的真实信息。在一个不确定性极高的技术环境里,低成本试错本身就是一种经济资产。

能力配置效率的提升,是粒度时代最容易被低估的经济效应。 替代时代的经济叙事是"降低人力成本"------用更少的人写更多的端。这个叙事暗含一个假设:人的能力是给定的,跨平台工具的任务是让给定能力覆盖更多端。粒度时代反转了这个假设:人的能力边界本身可以被重新配置。一个 Android 工程师借 CMP 进入 iOS 开发,获得的不是"替代 iOS 工程师的能力",而是"理解另一端约束条件的能力"。这种能力在单平台深耕中极难获得,而它在跨团队协作中的经济价值,远高于多写几行共享代码。一个能同时理解两端约束的工程师,在共享契约的谈判桌上能做出更准确的判断,在互操作边界出问题时能更快定位根因,在技术选型时能更诚实地评估成本。这些能力不直接体现在复用率数字上,但它们降低了协作摩擦成本、决策错误成本和事故修复成本------而这些成本在替代时代往往被忽略,却真实地消耗着组织的经济效率。

把这三个指标放在一起,粒度时代的经济逻辑就清楚了:它不是在"用更少的人写更多的端"这个维度上比替代时代更优,而是在"用更合理的资源配置写出更可持续的产品"这个维度上,开辟了一个替代时代无法进入的空间。 替代时代的经济学是规模经济学------共享率越高,单位成本越低。粒度时代的经济学是配置经济学------共享线画得越准,组织能力与产品需求的匹配度越高。前者追求的是效率的极致,后者追求的是效率与判断力的平衡。

这个转变还有一个更长尾的经济含义:平台判断力的保留,本身是一种经济资产。 替代时代的高共享率架构,往往以让渡平台判断力为代价------当所有 UI 都由一套代码生成时,平台特有的交互惯例、性能特征和设计语言就被抹平了。抹平在短期内降低了成本,在长期内却削弱了产品在平台上的竞争力。粒度时代允许团队在共享逻辑的同时保留平台 UI 的判断力,在共享 UI 的同时保留平台特有能力接入的判断力。这种"保留判断力"的能力,在竞争激烈的移动市场里,往往比多共享 20% 的代码更值钱。一个产品三年后的样子,很大程度上取决于团队在关键接缝上是否保留了做出独立判断的能力------而这个能力,正是粒度时代在经济上最持久的贡献。

八、新时代的实质:定义权已经转移

"写一次,处处运行"是以代码为中心的理想。它的问题不在于不切实际,而在于默认了最重要的变量是被复用的那部分。CMP 稳定版的实际指向恰好相反:跨平台成熟到某个阶段,重心不再是代码本身,而是人的组合方式可以被怎样重新排列。

所谓从替代到粒度,含义正在于此:团队自己决定共享的边界,而不是由框架替他们决定。过去十年,这个定义权一直在框架手里------Cordova 定义 WebView 的边界,Xamarin 定义语言统一的边界,RN 定义桥接层的边界,Flutter 定义自绘引擎的边界。团队能做的,是在框架给定的边界内分配人员、调整流程、修补裂缝。CMP 的互操作设计和稳定版发布,第一次把边界位置交回到团队手里:可以画在网络层、领域模型层、某个功能模块、某个屏幕,甚至屏幕里的某个视图。画在哪里,由团队结构、产品节奏和平台策略决定。

定义权的转移已经发生在三个层面,这三个层面共同构成了新时代的实际内容。

技术评估层面,复用率从一个目标降格为一个参数。它仍然可以被测量、比较、优化,但不再是判断跨平台是否成功的唯一标尺。评估重心从"共享了多少"转向"共享线画得对不对",判断标准是组织的协作结构和 ownership 分布,不是框架的宣传口径。

组织设计层面,跨平台选型从一次性的阵营决定变成持续的设计过程。团队先确定想要的协作结构,再选择匹配的共享粒度,然后随着团队能力、产品阶段和平台策略的变化不断移动这条线。这条线本身是一份组织契约:它记录了谁负责哪一层、谁和谁在哪个接缝上协作、哪个模糊地带由谁兜底。这份契约会随着组织学习进化:一个最初只敢共享网络层的团队,在经历几次成功的接缝实验后,可能发现自己在 UI 层共享上的协作能力比预想中更强,于是主动把线往上推。粒度时代的组织学习,不是学习某个框架的 API,而是学习自己团队的协作边界在哪里、能推到哪里。

人的发展层面,跨平台工具第一次从"降低人力成本"前移到了"重新配置人的能力边界"。一个 Android 工程师借 CMP 进入 iOS 开发,不是为了替代 iOS 工程师,而是为了获得一种此前被平台边界隔开的能力:理解另一端的约束条件。一个 iOS 工程师反向学习 Kotlin 进入共享层,也不是为了放弃 Swift,而是为了在共享契约的谈判桌上拥有发言权。这种双向的能力扩展,在替代逻辑下不可想象,因为替代逻辑预设了一方赢、一方输;在粒度逻辑下它是自然的,因为粒度逻辑预设的是组合,不是替代。

代码复用是可以算出来的数字。而一个 Android 工程师第一次读懂 iOS 那边的约束条件,这件事算不出来,却往往决定了一个产品三年后的样子。一个团队第一次在共享层上就接口契约达成共识,这个共识的文本可以写进文档,但它带来的协作默契写不进任何 KPI。一个组织第一次意识到自己可以在不放弃平台判断力的前提下获得跨平台效率,这个认知转变不会被记入任何技术白皮书,却是 CMP 这类工具最持久的产出。

回到开头那个信号。JetBrains 没有说"CMP 可以替代原生",它说的是"你可以一次一个屏幕、一个功能地接入"。这句话的谦逊里藏着一种已经完成的成熟:跨平台不再是一个关于框架能力的宣言,而是一个关于组织选择权的承诺。替代时代不会立刻消失,因为"选边"在心理上仍然比"画线"简单------站队只需要一次表态,画线则需要持续的判断和负责。但定义权已经转移了,这就是新时代的实质。一个团队现在可以诚实地说:我们不需要选边,我们只需要画一条线,然后对这条线负责。

这条线画在哪里,比"写一次"还是"写两次"重要得多。因为线的一边和另一边,站着的是人;而人怎么组合,决定了代码最终能写出什么。新时代真正的标志,不是某一天所有团队都放弃了选边,而是当有人问"你们是跨平台团队还是原生团队"时,团队可以不再需要回答这个问题------它只需要说清楚,自己的共享线画在哪里,以及为什么画在那里。一个能这样回答问题的团队,才是一个在粒度时代真正站稳的团队。

Compose Multiplatform 稳定版完成的,是一次从替代到粒度的决策模式重构。这次重构已经开启了一个新时代:框架退后一步,把定义权交还给组织;代码退后一步,把人的组合推到前台。它带来的经济效应是开发成本的大幅下降和公司利润的大幅增长------复用率不再是唯一的成功标尺,试错成本的结构性下降让实验取代了豪赌,能力配置效率的上升让人的边界本身成为可优化的资产,平台判断力的保留让短期成本节约不再以长期竞争力为代价。但前提是组织把决策治理和能力建设做到位:粒度自由不会自动兑现为利润,它只是把兑现利润的可能性交到了团队手里。它定义的从来不只是共享多少行代码,而是团队以什么样的边界组织自己、以什么样的方式让不同的人走到一起、以什么样的经济逻辑衡量这一切是否值得。这个故事没有标准答案,但它第一次允许每个团队写出自己的版本。而写版本的权利,就是新时代本身。

九、结语:把定义权用起来

定义权转移只是前提,不是结果。一个团队即使拿到了画线的权利,如果不去使用它,新时代对它而言仍然只是别人的叙事。真正把定义权用起来,需要三件具体的事。

第一,把共享边界当成显式的设计产物。 替代时代,边界藏在框架的架构里,没人需要为它写文档;粒度时代,边界是团队自己画的,就必须被记录、被评审、被复盘。一份好的边界说明至少回答三个问题:这条线画在哪里、为什么画在这里、谁对这条线的两侧负责。没有这份说明,粒度自由就会退化成口头约定,人员一变动,边界就失守。

第二,为每一次移动边界建立低成本的验证回路。 粒度时代的核心优势是可逆性,但可逆性只有在能被快速验证时才成立。团队需要一条从「提出假设」到「拿到反馈」的短路径:先在影响面最小的位置试,用可观测的指标判断成败,再决定推进还是回退。这条回路建不起来,所谓实验型决策就只是把大赌注拆成了许多没人跟踪的小赌注。

第三,把能力建设放在边界上,而不是放在框架上。 替代时代培养的是「Flutter 工程师」「RN 工程师」;粒度时代需要的是「能对某条接缝负责的工程师」。前者随框架兴衰而贬值,后者随组织复杂度上升而增值。团队在选人、培养、分工时,如果仍然按框架划分身份,就会把刚刚拿到的定义权又交还出去。

这三件事都不新,也不难,难的是持续。粒度自由不是一次性的架构决定,而是一种需要日常维护的组织能力:边界要有人守,实验要有人跟,能力要有人带。新时代不会因为某个框架发布了稳定版就自动到来,它只会因为一批团队开始认真对待自己画的每一条线而逐步成形。

回到最初那个容易被忽略的表述------「一次一个屏幕、一个功能地接入」。它听起来像一句降低迁移门槛的产品话术,实际上是一份把决定权交还给使用者的声明。框架能做的,到此为止;剩下的,是组织自己的功课。而这份功课做得怎么样,最终会写进产品的样子里,也会写进团队里每个人的能力结构里。

这大概就是「新时代」这个词最朴素的含义:不是工具变强了,而是选择变具体了。具体到每一个屏幕、每一个功能、每一条接缝,具体到需要有人站出来说------这条线,我们画在这里,并且我们为它负责。

相关推荐
三少爷的鞋3 小时前
Kotlin 协程,重点就在「协」这个字上
android·kotlin
贾伟康6 小时前
【健康菜谱助手|06】HarmonyOS ArkTS 烹饪计时器实战:处理倒计时、暂停、恢复和到期反馈
软件测试·移动开发·harmonyos·arkts·arkui
贾伟康10 小时前
【健康菜谱助手|05】HarmonyOS ArkTS 购物清单实战:去重食材并同步勾选、数量与清空动作
软件测试·移动开发·harmonyos·arkts·arkui
贾伟康21 小时前
【健康菜谱助手|01】HarmonyOS ArkTS 菜谱详情页实战:组织食材、营养、步骤和收藏状态
软件测试·移动开发·harmonyos·arkts·arkui
恶猫21 小时前
7-Zip - 免费开源的压缩解压王者
开源·免费·跨平台·winrar·7-zip·压缩·解压缩
传奇开心果编程21 小时前
【Rust入门练中学】第6课:结构体与方法
开发语言·rust·结构体·编程语言理论·结构体方法·编程通用语言
传奇开心果编程1 天前
【Jetpack Compose娓娓道来】 第25课:ViewModel就是UI界面的大脑
android·kotlin·移动开发·jetpack compose·编程语言理论·编程框架·声明式ui
贾伟康1 天前
【健康菜谱助手|04】HarmonyOS ArkTS 一周膳食计划实战:管理日期、餐次与菜谱替换的联动
软件测试·移动开发·harmonyos·arkts·arkui·1024程序员节
贾伟康1 天前
【健康菜谱助手|02】HarmonyOS ArkTS 烹饪步骤实战:把分步操作、完成状态和返回恢复做稳
软件测试·移动开发·harmonyos·arkts·arkui