服务器驱动UI的SDUI范式讲解之Flet

到9月份,Flet的正式版1.0终于发布,也表示着Flet可以正式投入到生产中使用了额。这对于Python开发者来说是个很好的消息!按捺不住这个心情,我决定写一个相关的笔记来讲解Flet的具体渲染逻辑是怎么实现的,为什么python可以编写flutter应用。

Flet 将 Python 代码作为描述 UI 的“语言”,并通过一个中间层(JSON 协议)来驱动 Flutter 渲染,这非常契合 SDL(场景描述语言)的核心理念。但它比传统 SDL 多了一个关键环节:服务器驱动的实时双向同步。

Flet使用了一个客户端-服务器架构,核心是一个被同步维护的控制树,当Python代码在声明组件的时候,Flet在底层:

构建控制树,构建出包含组件结构和属性的树状对象;序列化为json,框架会将这个python控制树序列化为json格式;websocket传输,通过websocket连接,将json更新实时推送到flutter客户端;flutter客户端反序列化json,并构建出对应原生的Widget进行渲染。

我之前说python在这里充当了类似sdl的功能,但是sdl一般是静态的,而flet的python端则是活的后端逻辑,持续运行并维护状态,用户点击按钮事件、事件会回传到python服务器,python代码处理后会更新状态,flet再自动生成新的json差异推送给前端,这个本质上是一种服务器驱动UI的架构,所以叫做SDUI。

这个和我们之前使用的Nuxt Content的内容驱动不一样,我们内容驱动是View通过服务器接收Model,这个在传统架构就叫做数据驱动/内容驱动。SDUI不仅是传数据,还要传结构。

传统MVC/MVVM或者前后端分离架构中,服务器只是返回纯粹的业务数据,前端写死UI结构,拿到数据之后,把数据填进UI里,这个时候决定UI的就是前端代码了,服务器只是提供内容。SDUI架构里,服务器不只是给数据,他给结构,服务器直接返回一段描述UI的json,叫做UI Schema:

// 服务器返回的 UI 描述 (包含结构 + 数据)
{
  "type": "Card",
  "children": [
    { "type": "Heading", "text": "Kiriyama" },
    { "type": "Text", "text": "Age: 20" }
  ]
}

前端拿到这个 JSON 后,不再是自己写死 <h1> 和 <p>,而是用一个“渲染引擎”去解析它。前端看到 type: "Card" 就调出卡片组件,看到 type: "Heading" 就渲染标题。

这个时候,决定 UI 长什么样的是服务器。如果服务器明天把 Heading 换成 Image,前端根本不需要改代码,甚至不需要重新发版,界面就变了。注意,在这个环节里面控制器发生了转移,UI架构里,谁决定页面的层级结构和组件类型,谁就是driver,如果服务器一起把排版结构都发布过来了,前端就是渲染器了,这个就是服务器驱动,有点像SSR。

Flet就是一个极端SDUI+远程状态同步机制,你在Python代理里写:

ft.Column([
    ft.Text("Hello"),
    ft.Button("Click me")
])

Flet底层会把这段python对象序列化成上面类似的ui schema,通过ws发给flutter客户端,然后flutter客户端里的引擎去解析这个json,然后把页面画出来。这个过程里,python不仅控制了下发的model,还控制了view的层级。

你像vue/nuxt的生态中,已经有了SDUI的成熟方案,它们可以通过schema/json驱动或者服务端组件等不同的路径将ui的控制权交给服务端,这里我举几个:

nuxt-sdui-layer:专为 Nuxt 3+ 设计的开箱即用模块。它通过 Firestore 文档驱动 UI,内置了 SDUIRenderer 等组件,将后端数据直接映射为 Vue 组件进行渲染,非常适合 Nuxt 技术栈。

json-render-vue:一款强大的 Vue 3 JSON 驱动渲染器。它提供了完整的 UI Spec 规范,支持双向数据绑定、插件化组件库、条件可见性以及动作处理,灵活性很高。

@sdui-kit/vue:框架无关的 SDUI Kit 的 Vue 适配器。它允许你注册自有组件,并通过 SDUIProvider 和 SDUIRenderer 来渲染后端下发的、与框架无关的 JSON 节点。

@burgantech/pseudo-ui:一个功能强大的 TypeScript SDUI 渲染引擎。它内置了 30+ Material Design 3 组件,并提供了表达式引擎、条件引擎和验证引擎,特别适合动态表单和复杂工作流。

这些项目我们后期可以自己去了解,这里就先不去谈论这个了,现在的主题是Flet。

关于移动端的实现

嵌入Python运行时 + Dart FFI 的混合策略

现在Flet已经实现了安卓端,所以我其实怀疑Flet是已经解决了aot并消除了sdui的,但是实际上不是这样。flet的sdui核心架构没有去掉,而是通过python运行时嵌入的技术,实现了从远程服务器的驱动到本地驱动。这套技术还是没有变化。

因此,现在还是使用的客户端-服务器的通信模式,python依然在负责业务逻辑和ui控制树。flutter客户端负责将控制器渲染成原生ui,这个模型在android上依然存在。当然aot部分是存在的,但是应用在了flutter部分,flutter的dart代码会被aot编译为arm/x64原生机器码,保证ui渲染和交互的性能。

分离部署、集中管理

WebSocket服务器完全可以独立于客户端运行,并且一个服务端进程能够同时维护成百上千个独立的Python会话,为每个连接的客户端提供服务。

Flet的远程模式中,python应用逻辑运行在性能强大的云服务器上,而ws网关比如nginx、fastapi可以单独部署,甚至可以用cdn加速。

所有客户端的连接请求都由ws服务器接收,然后转发给后端的flet应用进程进行处理。flet是由严格的会话隔离机制来确保多个客户端互不干扰的,每一个客户端通过ws来连接到flet应用的时候,服务端都会给他创建一个session对象。

当需要管理大量客户端时,Flet推荐将应用托管在生产级的ASGI服务器(如Uvicorn、Hypercorn)上,而不是使用其内置的开发服务器。这能带来更好的并发性能和稳定性。

这个使用场景其实比较好想,在工厂车间或者实验室里,往往有多态工控机、手持PDA、大屏看板。这样可以让几个终端连接同一个服务端, 权限不同,工人连上来的 UI 树里可能只有“扫码”按钮;工程师连上来的 UI 树里包含“修改参数”按钮。 全局状态同步,当设备发生紧急故障时,服务端可以瞬间修改所有在线终端的 UI 树——在所有人的屏幕上强制弹出一个红色的报警模态框。如果某个老旧平板性能极差,服务端可以为它下发一个“精简版 UI 树”(去掉复杂图表),只保留关键数据。

那么甚至连请求都可以在SDUI的服务器里执行了,这就是一个完美的BFF聚合层。假设你的上位机需要显示“设备温度”和“订单列表”,传统做法是前端发两个请求。

而在 SDUI 里,服务端收到页面刷新事件后,在 Python 里用 asyncio 并发请求这两个微服务,然后组合成一棵完整的 UI 树(包含数据和结构),一次性推给上位机。客户端减少了网络握手,加载速度极快。