页面打开耗时,是移动客户端领域一个老生常谈但始终未能彻底解决的问题。从用户点击到首屏内容呈现,中间要经历容器初始化、运行时与渲染引擎准备、业务代码执行、数据请求与解析、布局测量、节点构建等一整套链路。任何一个环节的延迟,最终都会转化为用户指尖下实实在在的等待。
Kuikly 作为腾讯基于 Kotlin Multiplatform 技术构建的全平台高性能开发框架,覆盖 Android、iOS、HarmonyOS、H5、微信小程序、Mac 六大平台。得益于 Kotlin/Native 编译产物与原生渲染管线,它已经具备了原生级的性能表现。但即便如此,它依然走在这条串行链路上。单纯优化"代码执行更快"这条路,收益已经逼近天花板。要想从根本上解决首屏体验问题,必须跳出执行效率本身,从渲染链路的调度方式上寻找新的突破口。
于是,TurboDisplay 诞生了。这是 Kuikly 自研的首屏加速方案,在页面二次打开场景下实现了真正的"秒开"。下面,我们就来深入拆解它的设计思路与核心机制。

页面打开有多"慢",其实很难全甩给技术栈。即便纯原生开发,也无法回避业务复杂度、网络波动、设备性能差异等客观因素。这个问题的本质是:从用户点击到首屏呈现,所有环节都串行执行,任何一个环节的卡顿都会被直接感知。
围绕首屏加速,业界已经沉淀了不少通用手段,比如图片缓存、数据预取、预渲染等。这些方案在各自适用的场景下都能带来一定收益,但都很难做到真正的"秒开"。原因很简单:它们缓存的是图片或数据,而不是渲染本身的结果。

如果想实现"打开即可见、可交互",那么缓存的对象就不应该只是图像或数据,而应该深入到框架自身的渲染产物——缓存构建好的渲染节点。这样一来,下一次打开时,就能直接跳过中间环节,以可交互的形态秒级呈现。那么,Kuikly 在这条路上具体该怎么走?
一个理想的缓存加速方案,应该同时满足三个条件:业务无侵入、渲染及时且正确。业务无侵入决定了它能否大规模铺开;及时性决定了用户看到的是不是最新页面;正确性决定了它在生产环境能不能被信任。这三者缺一不可,否则方案就只是"个例 trick",而非"普惠加速"。
顺着"缓存渲染节点"的思路,第一个想到的肯定是节点直出。具体做法是:把渲染节点的结构、属性、样式持久化,下次打开时直接读出并重建视图,跳过"执行业务代码 → 布局测量 → 节点构建"这一段。在 Kuikly 中,页面最终上屏时会被抽象为一组低阶渲染节点与基本渲染指令,节点直出天然适配这套管线。

但问题在于,节点直出只覆盖了跨端侧的业务执行与节点构建等阶段,前置的引擎初始化操作仍然无法省略。所以,直接套用节点直出方案,距离"页面打开即可见、可交互"仍有明显差距。必须对节点直出方案本身做根本性的改造。
最终方案的核心思路是:把缓存恢复从主链路中拆出来。换句话说,首屏加速不再只是"把原链路做快一点",而是让缓存恢复形成一条独立的执行路径。要实现这一路径,需要解决三个关键问题:如何让缓存恢复提前发生,缓存内容如何持续更新,以及缓存首屏如何与真实页面实现平滑衔接。

围绕这三个问题,TurboDisplay 的核心设计拆成了三部分:双线并行、节点采集和增量更新。前两者解决"如何更早展示"和"展示什么",后者解决"如何在不中断交互的前提下切回真实页面"。
先看双线并行。Kuikly 的做法是:把"页面的完整执行链路"在端侧提前多跑一遍。具体来说,当端侧的容器以及根 view 构建完成后,立即读取本地缓存并转换为节点树,产生渲染指令让首屏快速在端侧上屏。与此同时,原本的执行链路保持不变,端侧进行线程调度后,在跨端侧仍然按正常流程在后台执行这一轮真实页面的业务逻辑、布局测量与节点构建。真实页面产生批量渲染指令并生成一颗端侧的真实页面节点树,等待后续与缓存节点树比对差异,更新实际的首屏渲染内容。
节点采集方面,TurboDisplay 存储的是端侧节点树上节点的完整内容,包括每个节点的属性、事件、布局 frame、Shadow 信息、节点上的方法调用以及节点树结构的变化。采集时,系统会比较缓存首屏与真实页面节点树在树形和节点内容之间的差异,将差异更新至缓存节点树,然后自动、周期性地把缓存节点树写盘。此外,节点采集还支持业务自定义是否捕捉节点树之间的结构差异——如果未开启,缓存的就始终是业务真实首屏所对应的节点及其最新内容。

增量更新是 TurboDisplay 实现平滑切换的关键。在快速直出首屏内容之后,如何让真实页面的逻辑平稳地在缓存首屏上展现?增量更新是其中最稳定的解决方式。跨端侧在调度主线程后,批量执行渲染指令建立业务真实首屏对应的节点树,然后与缓存节点树进行属性、布局 frame、调用方法参数等内容的差异比较,全量回放首屏过程中记录的交互事件,并直接新建此前不存在的节点。整个过程不会造成屏幕抖动,实现了缓存首屏与业务真实首屏之间的平滑切换。
三项核心机制支撑了节点直出快速显示 Kuikly 页面首屏。但真实页面并不会总停留在初始位置。用户可能在缓存首屏展示后已经发生交互,列表页也可能需要恢复到上一次访问的位置。这时候,端侧已展示的内容与跨端侧后台构建出的默认首屏之间就会产生错位。
问题的根源在于 diff 的时机。TurboDisplay 采用"端侧直出 + 跨端侧后台构建"的并行设计,缓存首屏比真实业务首屏出现得更早——这个"更早"恰恰是问题的根源。当真实页面通过 diff 在端侧准备展示时,对比的是"端侧已恢复/交互后的状态"与"真实页面默认状态",差异会被全量更新,导致端侧已呈现的内容被覆盖回默认效果,表现为位置跳变或交互结果丢失。

最终解决办法是:让跨端侧来调度 diff。具体做法是,将 diff 的执行时机从"真实页面到达端侧即执行"延后为"恢复内容生效后再执行"。跨端侧在构建真实节点树时精确感知自身进度,在生成渲染指令前先执行恢复逻辑:将滚动偏移量应用到真实页面节点上,回放缓存首屏期间暂存的交互事件,让跨端侧先响应。这样一来,交付给端侧的真实首屏本身就处于恢复后的状态,diff 对比的两端基础一致,非默认内容不再是差异项,自然不会被覆盖。

到这里,无论是业务逻辑默认首屏内容,还是交互之后的动态内容,TurboDisplay 都能实现稳定且正确的展示。
实验数据最有说服力。我们在 iPhone 13 Pro Max 和 iPhone 7 Plus 两种设备上进行了测试,场景为页面二次打开。为了便于观察是否实现首帧即现,测试中的第 1 帧做了 100 毫秒的延迟处理。
iPhone 7 Plus 对比
iPhone 13 Pro Max 对比

线上数据同样亮眼。TurboDisplay 已经在 QQ 游戏、输入法以及腾讯地图等业务场景上线,取得了 65% 到 80% 的耗时优化幅度。需要说明的是,优化前页面的代码与数据均已本地缓存,启动过程无网络耗时,均为本地逻辑耗时。

TurboDisplay 在设计之初就考虑了业务接入的轻量化。实际使用中,业务侧只需要实现一个开关接口 TurboDisplayKey,即可为 Kuikly 页面开启首屏加速能力。以下是典型的接入代码:
// Native端侧页面容器 KuiklyRenderViewController.m
// 实现TurboDisplay开关接口
- (NSString *)turboDisplayKey {
return pageName;
}
其他业务控制项的使用方式及相关细节,可参考官方文档进一步了解。
当前 TurboDisplay 主要聚焦于页面二次打开场景的优化。针对首次打开场景,团队内部也在进行方案设计和开发。核心思路包括:在编译期对首屏逻辑进行静态分析与提取,在运行时通过微指令引擎快速展示首屏,再与原产物执行链路平滑衔接。
当前 Kuikly 已经开源。感兴趣的读者可以通过 GitHub 仓库和官方文档获取更多信息。Kuikly 框架也是腾讯端服务联盟(tds.qq.com)的重要成员。

