本文有实证版。 同一主题已用能跑的代码和真实渲染的画面重做 — 只有画面这一层会化 — 无需安装的应用,以及编译冻住的东西。这篇保持写成时的原样。
在上一篇Vol「无需安装的应用时代」里,我们做了一个大胆的主张。若画面不由设备而由服务器定义,构建、审核、安装、更新这条长长的锁链就会整个消失。反响恰好对半分。「这真做得到吗」与「我们早就在用类似的东西了」。两种反响都指向同一处——那条锁链并非理所当然。
这一篇Vol再往锁链里深掘一环。锁链有许多环,但有一个最初的环,其余每一环都吊在它上面。一个深在底部、平时连看都看不见的环。编译。 这篇文章谈的是:编译对画面做了什么,我们是怎么被困在其中的,以及它从来都不是必然的。这会是一篇长文。毕竟它处理的是锁链的根。

编译不是翻译
按字典式的定义,编译是「把人读的代码翻译成机器执行的形式」。这话没错。计算机课的第一节就是这么教的。可这定义对编译在画面上做了什么,却一个字都没说。
让我们从画面的角度,给编译重新下个定义。
编译是把那一刻的所有决定,冻进唯一一块坚硬的疙瘩里。
按钮摆在画面何处、什么颜色、字号多大、按下会发生什么、接下来跳到哪个画面——这数百个决定,在被编译的那一刻冻结了。原本各自散落的决定凝成一团疙瘩,那团疙瘩被植入设备。这种冻结有明确的好处。冻硬的东西快、可预测、稳定。它在用户的设备上一致地运行。
问题在那之后。冻起来的东西,再要重新决定就难了。
假设你想改一个按钮颜色。这本是只改一个决定的事。可那个决定,早已和另外数百个一起冻成了一团疙瘩。要取出一个决定来改,就得把整团融开(打开源码)、改掉那个决定、再整个冻上(重新编译)。而新冻成的疙瘩还得再植入设备一次——打包、签名、上传商店、等待审核、分发。上一篇Vol里我们长篇描述的那条锁链,换个角度看,便是把冰融开再冻上这一个循环的成本。
这里有这篇文章的第一个结。我们忍受的分发之苦,并非因为懒惰或工具陈旧。它是从「画面这个决定冻在设备里」这一结构中必然流出的。别去咒骂锁链——要去看那块冰。
我们是怎么被困进冰里的
这块冰并非一开始就有。让我们简短地追溯一下谱系。若知道今天的憋闷是在哪一层凝固的,也就清楚了该融化什么。
第一代——打包软件。 把程序装进CD或软盘出售的年代。画面也好逻辑也好,全都被编译后整个安装进用户的电脑。要改,就压制新版本邮寄出去,用户得重新装一遍。更新周期是一季度,运气差则以年计。冰的原型在这里造成了——程序的本体冻在设备里。 当时这是唯一的路。网络很慢,或者干脆没有。
第二代——网页。 浏览器翻转了前提。网页是不编译的。服务器送下一份写着「这里一个标题,下面一个段落,再下面一个链接」的文档,浏览器就在接到它的那个地方把它画出来。画面没有冻在设备上。每次访问都按服务器所定义的样子重新画出。所以修改是即时的——把服务器的文档改一行,下一个访客就看到改后的画面。没有构建、没有安装、没有更新。这是锁链的第一次断裂。我们自由了——只要我们处理的是文档。
第三代——应用商店。 智能手机一登场,潮流又倒了回去。摄像头、GPS、加速度传感器、推送通知、离线运行、贴着手指的六十帧动画——为了网页当时给不了的原生能力,我们又回到了被编译并安装到设备上的应用。 我们把画面又冻上了。而这一次,冰上还多压了一层:商店这道关卡,以及审核。锁链比第一代更长了,就这样再次凝固。
这里要看清最重要的一点。我们回到应用商店模式,是因为原生的能力,而不是因为想要编译、安装、审核这块冰。冰是为了获得能力而不得不附带的成本。然而随着时间流逝,我们竟把这成本和能力当成了一捆。「想要原生能力,就得连编译、连锁链一并承受。」这篇文章所追问的,正是这一捆。能力与冰,真的不能分开吗?
业界已经试图破冰的痕迹
业界并非只是干等着忍受这块冰。破冰的尝试不断出现,而看看这些尝试的成功与局限,真正解法的轮廓便浮现出来。
混合应用(WebView)。 在叫作应用的壳子里塞进一个浏览器,画面用网页来画。因为画面能在服务器端改,冰融了一部分。但代价不小。WebView不如原生流畅,难以触及设备的深层功能,还给用户一种「像网页的应用」的不上不下之感。等于是放弃了一部分能力,买来了即时性——而那正是我们当初离开网页所要的能力。
代码推送(热更新)。 不经审核就把JavaScript包推进设备,更新应用逻辑的方式。对紧急修复有用。但本质仍然是把代码冻进设备。只不过是稍软一点的冰罢了。得和商店政策拉锯,大的改动最终还得回到正式审核。它没有破冰,只是把冰弄软了一点而已。
服务器驱动UI(SDUI)。 从这里起,便接近本质了。大型服务悄悄沿用的方式。画面的构成——放哪些卡片、按什么顺序、用什么数据来显示——由服务器作为规格送下来,应用接到这份规格,用原生控件把它画出来。在信息流、主页这类频繁变动的区域大显身手。它把画面留作液体(服务器定义),又把画的那只手留作原生(应用来画)。三种尝试中,它最接近正确答案。
可是SDUI也有天花板。那就是它大多是各家公司只为自家服务打造的封闭系统。只在那家公司那个应用里才工作的规格,只和那家公司服务器对话的结构。它不是通用标准。所以「我们的信息流画面由服务器定义」做到了,但「向任意服务器、任意设备定义并发送画面与工具」却做不到。它只在自家院子里融了冰,却带不出院子去。
业界走到这里为止。即时性由网页,能力由原生,画面的动态定义由SDUI,各自证明了出来。三者都有了。只是这三者从未在同一个开放标准之上相遇罢了。
把冰取出,但守住能力
服务器驱动UI——这本杂志所讨论的方式——所做的,正是那未竟的合流。只融开画面这个决定,而把画的能力留作原生。并且让这场合流不在一家公司的院子里,而在开放的规约之上发生。
来看结构。服务器送下一份轻巧的定义:「一个标题,下面一个按钮,按下按钮就调用这个工具。」设备里有的,只是一个接到这份定义、用原生控件把它画出来的通用运行时。画面像网页一样留作液体,画的那只手仍是原生。摄像头、传感器、六十帧动画都照样能用——因为画的一方是真正的原生运行时。我们离开网页时放弃的能力,这一次不放弃。
此时设备里不再有「只属于这个应用的、被编译而冻住的那团画面」。只有一个无论来什么都能画的运行时。于是改画面这件事的物性变了。从前那是把冰融开再冻上的事——打开源码、修改、编译、植入。如今则是把手指伸进液体里搅动的事——把服务器的定义改一行,下一次刷新画面便照样变了。同样是「改一个按钮颜色」,一边要三天,一边十秒。两者之差不在努力,而在物性。
一个场景
听上去会很抽象,所以来描一个场景。假设服务器里有这样一份定义——「这个画面有一句欢迎语,下面有一个按钮。按下按钮就调用下单工具。」 用户打开应用,设备的运行时接到这份定义照样画出。欢迎语和按钮出现了。
现在你在服务器上改那份定义。「改成两个按钮。一个下单,一个取消。颜色用蓝色。」 保存。下一刻,店里每一台设备的画面上都变成了两个按钮。店员们什么都没做。没点更新,没重新下载应用,没去商店。你没编译,没等审核,没按发布键。你只是改了定义的一行。十分钟的决定,在十分钟内抵达每一块画面。 这便是锁链整个消失之处。
自然会浮现的反驳
读到这里的技术者,脑中想必已浮现出若干反驳。若不正面回答这些反驳,这篇文章就只是一纸空洞的宣言。让我们一个一个来。
「每个画面都从服务器接收,不会慢吗?」 画面定义很小。不是沉重的代码包,而是「一个按钮、一个文本」那样轻巧的规格——通常几千字节。况且一旦接收过的定义就会被缓存。真正沉重的东西,也就是绘制引擎,早已原生地在设备里了。服务器定的是画什么,而非画这个行为本身。用户体感到的性能就是原生的。
「那离线就什么都做不了吗?」 缓存下来的定义在连接断开时照样工作。最后接到的画面照样挂着,只在定义改变时才去问服务器。「必须时刻连着网」这种误解,来自每时每刻都调用服务器的错误图景。实际上只有变动的部分在往来。这和网页以PWA获得离线是同一个原理,只不过是在原生运行时之上。
「这是要我再学一套新框架吗?」 不是。核心不在新工具,而在顺序变了。在你把画面写成代码冻进设备的那个位置,把画面作为定义放到服务器上。要学的不是一套庞大的新体系,而是一件事:「怎么写定义」。所以入门可以从十行代码开始(这一篇Vol的编程文章会打开那十行)。
「安全呢?服务器既然下发画面和动作,会不会下发恶意的定义?」 所以运行时不信任收到的定义而是验证它。 一份定义能做的事,被框在运行时设定的边界之内——不是任意代码执行,而只能是预先定义好的控件与工具的组合。这和浏览器只在沙箱内执行任意站点的JavaScript是同一个思路。(这条边界怎么画,会在下一篇Vol「每个服务器都是应用」里正面讨论。)
「这不是违反应用商店政策吗?」 这是个微妙的领域,老实说视情况而定。商店一直警惕「绕过审核、用任意原生代码把应用的核心动作整个改掉」。但「服务器定义内容与画面构成」,是每一个信息流、每一个电商应用早就在做的事。界线在于是否植入任意代码,而不在于画面是否由服务器定义。只要这个模型停留在后者一侧,它就如同既有的SDUI那样处在政策之内。(不过这是一种解读,而非法律咨询。实际应用时应查阅各商店的最新指南。)
那么编译还剩下什么
老实地理一理。编译并非消失了。它换了地方。
实时控制、繁重的计算、设备深处的驱动、安全的底层——这些仍须被编译,牢牢立在设备和服务器上。那些以毫秒相争、半寸都不容晃动的东西,就该冻着不动。融化的是人看、人按的那层画面。显示什么、如何布局、以什么流程相连——那层频繁变动、频繁被实验、又须因人而异的画面。
想一想,画面本来就该是频繁变动的东西。价目变,菜单变,按钮位置要实验,对不同用户显示不同的东西。我们却把这种该频繁变动的东西,冻在了最难改的地方(被编译的设备里)。那才是不自然的。我们并不是把编译给丢了。
我们只是把不该被编译的东西,从编译里取了出来。
让该坚硬的更坚硬地冻着,让该流动的流动起来——那分离,才是这场转变的核心。
所以,去怀疑
读到这里仍想「可这真做得到吗」——那份怀疑是对的。用文字写出的愿景,要写得多像样都行。把历史串得像样,把反驳答得像样,都做得到。要紧的是它究竟能不能跑起来。
所以这本杂志不止步于把愿景铺陈一通。在这一篇Vol接下来的文章里,我们会直接展示那液态的画面,在最坚硬的地方——芯片之上——是如何运行的。一位工程师摆弄评估板的一个下午,没有专用GUI,也不烧固件。还有,一个不懂编程的农民,是怎么把三十年的田间记录做成画面的。把冰取出来,能去触碰的人就多了——这就是整篇Vol的故事。
最后留一句。你上次「重新编译」什么东西是什么时候?为改画面的一行,把那整套流程又跑了一遍。那个熟悉的仪式——至少在画面这一层——正在开始消失。
makemind.dev 「探索」— 「告别编译」 Vol 的开篇。画面从编译中解放之后会有什么不同,我们在芯片与田间接着看。