TurboDisplay 首屏加速机制

大约 10 分钟约 3133 字

TurboDisplay 首屏加速机制

iOS 2.16.0 及以上支持

介绍

TurboDisplay 是一套首屏加速机制,以客户端直出为实现路径,智能采集Kuikly节点作为缓存对象,业务只需打开开关即可享受首屏加速能力。

页面首次渲染时自动采集首屏内容并持久化到本地,后续再次打开时直接从缓存构建首屏 View,实现"秒开"效果。

TurboDisplay 默认加载页面的初始状态,且同时支持恢复页面退出时状态,如列表滚动位置、轮播图当前页等,详情可参考「页面状态恢复」章节。

使用指引

启用 TurboDisplay

TurboDisplay 运行于 Render 侧,仅需在 Native 侧配置启动开关,无需改动 Kotlin 业务代码。 在各页面容器中通过实现 turboDisplayKey 方法开启 TurboDisplay。推荐使用 PageName 作为其值,将作为缓存文件名称直接来源,实现页面间缓存隔离 :

iOS 配置:

// KuiklyRenderViewController.m
- (NSString *)turboDisplayKey {
    return pageName;  // 返回 nil 或空字符串则关闭 TurboDisplay
}

管理 TurboDisplay 缓存(可选)

当业务需要在特定时机主动干预缓存内容时,如页面退出前保存当前状态、异常情况下清除缓存、特定交互后强制刷新缓存等,可通过 TurboDisplayModule 提供的方法进行操作:

  • setCurrentUIAsFirstScreenForNextLaunch(extraCacheContent) - 手动采集当前视图状态保存到缓存,调用后关闭自动采集
  • clearCurrentPageCache() - 清除当前页面缓存
  • clearAllCache() - 清除所有页面缓存
  • isTurboDisplay() - 判断本次打开是否使用了缓存上屏

使用示例

// 手动保存当前视图状态
acquireModule<TurboDisplayModule>(TurboDisplayModule.MODULE_NAME)
    .setCurrentUIAsFirstScreenForNextLaunch(extraContent)

// 判断本次是否使用了缓存上屏
val isFromCache = acquireModule<TurboDisplayModule>(TurboDisplayModule.MODULE_NAME)
    .isTurboDisplay()

TurboDisplay 运行策略配置(可选)

默认情况下,TurboDisplay 自动完成缓存的采集与更新,无需业务干预。当页面存在以下场景时,可通过 TurboDisplayConfig 调整 TurboDisplay 的运行策略:

  • 首屏内容存在结构变化(如列表项增减、条件渲染导致节点出现或消失):开启结构变化采集模式,使缓存能感知节点增删
  • 需要恢复页面退出时的状态(如列表滚动位置、轮播图当前页):开启延迟更新模式,确保恢复逻辑执行完毕后再进行首屏增量更新
  • 性能敏感、首屏内容稳定不变(如纯静态展示页):可关闭实时跟踪和自动采集以减少开销

此外,还可通过 View 粒度的 turboDisplayAutoUpdateEnable 属性控制节点级别的缓存采集动作,精确控制指定节点及其子树是否参与缓存采集,详见下方「View 粒度采集控制」。

全局采集控制:设置 TurboDisplayConfig 配置项

diffDOMMode — 控制缓存采集时的算法

  • Normal(默认):只采集已有节点的属性变化
  • StructureAware:同时采集节点的新增、删除和替换等结构变更

diffViewMode — 控制首屏增量更新的执行策略

  • Normal(默认):同步一次性完成差异对比和更新
  • Delayed:延迟至渲染指令全部执行后再更新,支持页面恢复等交互场景

diffExecuteMode — 控制首屏增量更新(真实视图接管缓存首屏)的执行时机

  • Normal(默认):系统时机,业务首帧渲染完成后立即执行
  • Suspend:挂起时机,Diff 在页面初始化阶段静态挂起,由业务调用 executeTurboDisplayDiff() 触发执行,详见「控制首屏接管时机:挂起 Diff」

autoUpdateTurboDisplay — 控制是否在首屏至完全渲染期间自动采集界面变化

  • true(默认):自动捕捉界面属性变化并刷新缓存
  • false:关闭自动采集,仅保留初始缓存

persistentRealTree — 控制是否持续跟踪业务首屏节点状态

  • true(默认):实时跟踪,确保缓存内容准确
  • false:减少性能开销,但仅保留上一次存储的缓存

配置项彼此关系

  • 持续记录(persistentRealTree) → 自动采集(autoUpdateTurboDisplay) → 结构变化采集(diffDOMMode) 呈链式依赖,前者是后者的前提。持续记录关闭后,纵使自动采集开启,缓存内容也不再变化;自动采集关闭,缓存内容同样不再变化。
  • 首屏增量更新模式(diffViewMode) 独立于以上三项,单独控制首屏增量更新的执行策略。
  • 首屏增量更新时机(diffExecuteMode) 为 Suspend(挂起)时,会自动开启 结构变化采集(diffDOMMode),且首屏增量更新固定按 延迟模式(diffViewMode = Delayed) 的节奏执行。此时这两项无需(也不应)再显式设置,详见「控制首屏接管时机:挂起 Diff」。

配置示例

iOS 配置:

- (KRTurboDisplayConfig *)turboDisplayConfig {
    KRTurboDisplayConfig *config = [[KRTurboDisplayConfig alloc] init];
    
    [config enableStructureAwareDiffDOM];       // 启动结构捕捉
    [config enableDelayedDiff];                 // 启用分阶段执行的差异更新
    [config enableAutoUpdateTurboDisplay];      // 启动自动采集能力
    [config enablePersistentRealTree];          // 启动持续记录
    
    // 关闭以上能力的API
    [config disableStructureAwareDiffDOM];
    [config disableDelayedDiff];
    [config closeAutoUpdateTurboDisplay];
    [config disablePersistentRealTree];
    
    return config;
}

View粒度采集控制

通过 turboDisplayAutoUpdateEnable 属性,可控制指定节点及其子孙节点是否可被采集缓存,默认为 true。设置为 false 后,该节点及其子树的数据变化不会被采集到缓存中。

TurboDisplay autoupdate示意图
TurboDisplay autoupdate示意图

如图所示,在TubroDisplay机制加载页面过程中,节点5' 设置了 turboDisplayAutoUpdateEnable(false),其子节点 节点7' 虽然发生属性变化,但不会被更新到缓存。而 节点2 未设置该属性,其属性变化正常记录。

提示

  • 业务首屏和TurboDisplay缓存首屏进行差异更新时,为了可以将显示当前最新且正确的首屏效果,此时属于首屏阶段的组件所对应的 Native 节点,其结构变化与属性变化不受 turboDisplayAutoUpdateEnable 属性影响。
  • turboDisplayAutoUpdateEnable 属性会控制「首屏-完全渲染」期间的属性变更,关闭后本次写入的缓存首屏与所读取的一致。

控制首屏接管时机:挂起 Diff(可选)

默认时机下,首屏增量更新(真实视图接管缓存首屏)由系统在业务首帧渲染完成后立即执行。若页面真实首屏依赖异步数据,此时接管会把缓存直出的成功态替换成 Loading / 空列表态,产生肉眼可见的跳变。

挂起 Diff 将接管时机交给业务:Diff 在页面初始化阶段静态挂起,且无超时自动执行,业务在数据就绪、页面达到稳定态后主动触发接管。

适用场景: 有缓存二次进入、且首屏内容依赖网络数据的页面 —— 缓存直出上次成功态,数据回来后再执行 Diff,使接管前后的树结构对齐、差异收敛为属性级更新。

配置方式

iOS(Native 侧声明挂起):

- (KRTurboDisplayConfig *)turboDisplayConfig {
    KRTurboDisplayConfig *config = [[KRTurboDisplayConfig alloc] init];

    // 开启挂起 Diff:Diff 在 didInit 阶段静态挂起,由 Kotlin 侧 executeTurboDisplayDiff() 触发执行
    [config enableSuspendDiff];
    
    // 剩余可以配置的API,建议全部打开,保证挂起diff阶段可以顺利的采集到缓存内容
    [config enableAutoUpdateTurboDisplay];      // 启动自动采集能力
    [config enablePersistentRealTree];          // 启动持续记录

    return config;
}

Kotlin(业务侧触发接管):

addTaskWhenPagerUpdateLayoutFinish {
    acquireModule<TurboDisplayModule>(TurboDisplayModule.MODULE_NAME)
        .executeTurboDisplayDiff()
}

完整示例参考: demo/src/commonMain/kotlin/com/tencent/kuikly/demo/pages/demo/TBDemoTest/TBDeferDiffTestPage.kt,配合 iOS 侧 KuiklyRenderViewController 中的 enableSuspendDiff 配置使用。

使用注意

  • 挂起 Diff 无框架层超时兜底:业务需自行保证 executeTurboDisplayDiff() 被调用(建议在超时 / 失败分支同样调用),否则页面将停留在可交互的缓存首屏,不会自动接管。
  • 重复调用安全:非挂起模式、已触发但接管尚未完成(延迟 Diff 异步执行中)、已执行完成 —— 这三种情形下再次通过 Module 显式触发 diff,都会在端侧被幂等拒绝。
  • 当前仅 iOS 端实现,其它端调用为空实现(no-op)。 :::

首屏渲染层生命周期事件(可选)

TurboDisplay 在首屏渲染层的 6 个关键节点向 Kotlin 侧发送生命周期事件,可用于时序观测、性能埋点与状态联动(如"真实视图已接管"指示)。事件由 Native 侧发出,业务通过 IPagerEventObserver 接收。

事件名Pager 常量触发时机data
onInitLayerReadCacheStartPAGER_EVENT_INIT_LAYER_READ_CACHE_START开始读取本地缓存文件无
onInitLayerReadCacheFinishPAGER_EVENT_INIT_LAYER_READ_CACHE_FINISH缓存文件读取完成succ:是否读取成功,命中时附加 bytes、children
onInitLayerRenderCacheStartPAGER_EVENT_INIT_LAYER_RENDER_CACHE_START缓存首屏开始上屏渲染无
onInitLayerRenderCacheFinishPAGER_EVENT_INIT_LAYER_RENDER_CACHE_FINISH缓存首屏上屏完成(此后业务可交互缓存首屏)succ:是否上屏成功
onInitLayerRealViewDiffStartPAGER_EVENT_INIT_LAYER_REAL_VIEW_TAKE_OVER_START真实视图 Diff 开始(缓存视图 → 真实视图的关键更替)无
onInitLayerRealViewDiffFinishPAGER_EVENT_INIT_LAYER_REAL_VIEW_TAKE_OVER_FINISH真实视图 Diff 完成,真实页面生效succ:是否接管成功

接收示例:

class XxxPage : BasePager(), IPagerEventObserver {

    override fun created() {
        super.created()
        addPagerEventObserver(this)      // 订阅 Pager 事件
    }

    override fun onPagerEvent(pagerEvent: String, eventData: JSONObject) {
        // 事件名统一以 onInitLayer 开头,便于与其它 Pager 事件区分
        if (!pagerEvent.startsWith("onInitLayer")) {
            return
        }
        when (pagerEvent) {
            Pager.PAGER_EVENT_INIT_LAYER_READ_CACHE_FINISH ->
                KLog.i(TAG, "read cache: succ=${eventData.optBoolean("succ")}")
            Pager.PAGER_EVENT_INIT_LAYER_REAL_VIEW_TAKE_OVER_FINISH ->
                KLog.i(TAG, "real view take over: succ=${eventData.optBoolean("succ")}")
        }
    }
}

提示

  • 未命中缓存时不会发送 RenderCache / RealViewDiff 系列事件,ReadCacheFinish 的 succ 为 false。
  • 挂起 Diff 场景下,RealViewDiffStart / RealViewDiffFinish 的触发时刻即为业务调用 executeTurboDisplayDiff() 的时刻,可据此观测接管耗时。
  • 当前仅 iOS 端发送,其它端不会收到上述事件。 :::

页面状态恢复(可选)

默认情况下,TurboDisplay 加载的是页面的初始状态。当需要恢复用户上次的浏览状态时(如列表浏览位置、轮播图当前页、Tab 选中状态等),可通过「页面状态恢复」机制实现,适用于长列表浏览位置、轮播图页面、Tab 选中状态等场景。

TurboDisplay 框架提供 customFirstScreenTag 字段用于传递自定义状态信息,业务可按需实现各类状态恢复。目前已验证支持 List、LazyList、PageList、Banner 等组件的位置恢复。

实现步骤

以 List 滚动位置恢复 为例:

第一步:滚动时保存位置到缓存

List {
    event {
        scrollEnd {
            val (index, offset) = listViewRef.view.getFirstVisiblePosition()
            val extraContent = """{"${listViewRef.nativeRef}":{"viewName":"KRScrollView","contentOffsetX":${it.offsetX},"contentOffsetY":${it.offsetY},"firstVisibleIndex":$index,"firstVisibleOffset":$offset}}"""

            acquireModule<TurboDisplayModule>(TurboDisplayModule.MODULE_NAME)
                .setCurrentUIAsFirstScreenForNextLaunch(extraContent)
        }
    }
}

第二步:页面启动时恢复位置

override fun created() {
    val extra = getPager().pageData.customFirstScreenTag ?: return

    val listProps = JSONObject(JSONObject(extra).toMap().values.first().toString()).toMap()
    val offsetX = (listProps["contentOffsetX"] as? Number)?.toFloat() ?: 0f
    val offsetY = (listProps["contentOffsetY"] as? Number)?.toFloat() ?: 0f

    addTaskWhenPagerUpdateLayoutFinish {
        // lazyList 是调用ScrollToPosition(index,offset)方法实现恢复,详细的存储与恢复细节可参考 TBLazyListTestPage
        listViewRef.view.setContentOffset(offsetX = offsetX, offsetY = offsetY)
    }
}

注意事项

  • 复杂页面恢复时,尽量避免每次启动页面时都执行异步拉取数据,造成首屏跳变;
  • 执行页面时,增量更新策略需调整为延迟模式,确保业务实际首屏的内容与缓存首屏内容保持一致,避免发生跳变现象。

完整示例参考: demo/src/commonMain/kotlin/com/tencent/kuikly/demo/pages/demo/TBDemoTest/ 此目录提供有 List, LazyList, PageList, Scroller, WaterFallList, Banner 组件在首屏实现恢复的实例


工作流程

  1. 首次打开(生成缓存)

页面正常渲染,首屏完成后 TurboDisplay 自动将当前界面内容保存到本地缓存。若开启了自动采集,页面的首屏 - 完全渲染期间的首屏组件发生的属性变更会被持续记录,确保缓存内容始终接近业务实际效果。若打开支持结构变化的采集模式,则完全渲染时机前所有组件的变更皆会被记录;

  1. 后续打开(缓存上屏 → 增量更新)

再次打开页面时,TurboDisplay 直接从缓存构建首屏 View 并显示,实现"秒开"。与此同时,业务逻辑在后台并行执行,构建真实的页面内容。待真实内容就绪后,TurboDisplay 自动对比缓存首屏与真实首屏的差异,仅更新变化的部分,最终展示真实的业务页面。若页面需要恢复上次的状态(如列表滚动位置),可开启延迟更新模式,确保恢复完成后再进行差异对比,避免页面跳变。

  1. 智能采集(刷新缓存)

增量更新完成后,TurboDisplay 会继续跟踪页面内容的变化并刷新缓存。业务也可在特定交互时机(如用户滚动结束)主动保存当前视图状态。最终缓存中保存的始终是最新的页面效果,确保下次打开时展示的内容符合预期。