ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Lynx Clay NativeView 契约实现详解:C++ 驱动的 Native View 生命周期、布局与子树快照

Lynx Clay NativeView 契约实现详解:C++ 驱动的 Native View 生命周期、布局与子树快照 Lynx Clay NativeView 契约实现详解C 驱动的 Native View 生命周期、布局与子树快照【免费下载链接】lynxEmpower the Web community and invite more to build across platforms.项目地址: https://gitcode.com/GitHub_Trending/lynx10/lynx本文基于 Lynx 仓库中clay/ui/component/spec/native_view_spec.md规格文档展开完整梳理 ClayLynx 的 UI 框架层在 Android 上托管 Native View 的实现契约从 create 到 destroy 的 C 驱动生命周期、internal/external 双通道平台视图体系、布局与 nodeReady 的时序保证以及可选的 Clay 子树光栅快照能力。读完本文你可以理解 C、JNI、Java 平台视图胶水层与LynxBaseUI之间每一层职责边界并能在排查原生组件渲染、时序或生命周期问题时快速定位到对应契约条款与源码位置。1. 规格文档定位与范围规格文档开篇即明确了自身定位这是已实现行为的契约spec for the implemented behavior而非历史设计说明。这意味着它对行为做规范性约束只要 C、JNI、Java holder 或 bridge 层中任何一处的变更影响了生命周期、布局、渲染或快照语义该文档必须在同一次变更中同步更新。文档的范围Scope限定为Clay Android 中通用NativeView的行为C、JNI、Java 平台视图胶水层与被包装LynxBaseUI实例之间的生命周期对齐node-ready 边界上 iOS 侧的视觉变更visual-mutation排序规则渲染模式与 holder 选择可选的 Clay 子树光栅快照支持。文档同时明确划出了范围之外的内容地图专属的 tag 策略与 marker 位图行为、以及通用包装机制之外的产品专属 tag 别名。这些内容归属于 map_view_spec.md通用规格不重复收录避免专项规则污染通用契约。2. 设计目标C 驱动的标准生命周期规格文档给出的目标生命周期链路为create - insert - attach - props/events - layout - nodeReady - detach - destroy核心原则只有一条生命周期由 C 侧驱动Java 侧不得基于无关的 Android 回调自行发明等价状态。这条原则贯穿后文所有契约章节也是第 14 节不变量清单的总纲。从源码看这一设计在 C 组件层有直接体现。native_view.h 中clay::NativeView继承WithTypeInfoNativeView, BaseView与OnPaintingListener持有一个PuppetOwner::kUI, NativeViewPlugin成员native_view_plugin_所有跨越到平台插件的操作都通过该 Puppet 在 actor 线程上执行例如OnAttachToTree 中调用plugin.OnAttach()并把自己注册为OnPaintingListenerOnDetachFromTree 中对称地调用plugin.OnDetach()并注销监听OnNodeReady 中先做布局兜底同步再调用plugin.OnNodeReady()。Java 侧只接收 C 转发的信号不产生状态——这正是规格反复强调的Java must not manufacture equivalent states。2.1 术语表文档定义了后续章节反复使用的关键术语这里完整保留NativeView平台表示由 Android platform-view 管线管理的 Clay 组件节点internal wrapped native viewJava 侧实现为InternalPlatformView、底层包装一个LynxBaseUI的NativeViewexternal native view路由到非 internal 的PlatformViewContext的NativeViewInternalPlatformViewWrapperJava 侧包装器负责创建LynxBaseUI、应用 props/events/layout、代理生命周期并可选择性安装子树快照支持nodeReady patching由 C 经PaintingContextClayRef::UpdateNodeReadyPatching(...)与ViewContext::UpdateNodeReadyPatching(...)发出的 ready-id 冲刷platform-layer rendering经PlatformViewWrapper、FlutterMutatorView、VirtualDisplay 或等价 holder 基础设施执行的 Android 侧渲染platformcanDrawfalse平台视图的生命周期与尺寸依然存在但平台层的挂载与绘制被抑制的模式Clay subtree raster snapshot通过PageView::MakeRasterSnapshot(...)从 Clay 子树生成的光栅图像。3. 四层架构总览规格文档把实现切分为四层每层的职责与关键类如下。3.1 C 组件层clay::NativeViewnative_view.h拥有组件侧生命周期缓存暂存属性staging_attrs_从OnPainting()向平台插件推送布局变更在派发 ready 之前从OnNodeReady()再推送一次最终布局更新。clay::ViewContext拥有视图树通过AddChild(...)建立父子关系把 nodeReady patching 派发到正确的BaseView。仓库中该入口为 ViewContext::UpdateNodeReadyPatchingclay::PageView提供MakeRasterSnapshot(...)子树快照能力LayoutContextClay拥有布局节点创建与可测量策略。3.2 JNI / 平台服务层NativeViewPluginAndroidclay::NativeView与 JavaPlatformViewPlugin之间的 JNI 桥创建 Java 插件对象转发 create/insert/attach/detach/layout/nodeReady/measure/method 调用对外暴露RequestSubtreeRasterSnapshot(...)用于可选的子树捕获。C 侧对应的插件抽象通过服务接口创建NativeViewService 声明了CreateNativeViewPlugin(...)其通用实现在 native_view_service_common.hClay 适配层在 native_view_service_embedder.h 中接入。NativeView构造时即通过CreateObjectInActorThread拿到插件对象见 构造函数。3.3 Java 平台视图层PlatformViewPlugin每个NativeView的 Java 入口选择 internal 或 external 的PlatformViewContext创建 holder 路径对特定 tag 应用canDrawfalse之类的平台绘制策略PlatformViewHolder纹理texture、混合hybrid、虚拟显示virtual-display三种合成方式的 holder 抽象PlatformViewContextcreate/insert/layout/props/nodeReady/destroy 的每上下文接口InternalPlatformViewContext用于复用包装LynxBaseUI的 internal 上下文InternalPlatformViewRegistryinternal 视图工厂注册表预先注册内部包装 tag使 Java 侧能选择 internal 上下文并向 Clay 暴露 bootstrap tags 与合成偏好composition preferenceInternalPlatformViewJava 侧 internal 节点抽象onLayoutFinish()仅作为兼容别名保留规范性生命周期信号是onNodeReady()InternalPlatformViewWrapper通用包装LynxBaseUI的实现拥有 prop/event 派发、layout 信息中继、nodeReady 门控以及可选的 snapshot provider 安装。3.4lynx_claybridge 层LynxUIClayUIBridgeClay Java 代码使用的类型化桥接口LynxUIClayUIBridgeImpl位于platform/android/lynx_clay从 behavior tag 创建LynxBaseUI实例复用现有 Lynx runtime API 处理 props、events、insert/remove、layout-info、生命周期与 UI 方法为受支持的包装 UI 可选安装子树快照 provider。4. Create 契约4.1 C 侧创建NativeView的创建始于构造函数调用链为NativeViewService::CreateNativeViewPlugin(...)plugin.OnCreate(tag)插件上报四项信息共享图像 sink若有、hybrid 合成支持、滚动支持、可用性NativeView据此配置RenderExternalContent当前的重要行为源码可印证见 native_view.cc 构造函数若存在 shared image sinkNativeView注册外部纹理并把渲染模式设为RenderExternalContent::kExternalTexture——BindExternalTexture 中创建SharedImageExternalTextureLinux 下按UseTextureBackend()在外部纹理与外部位图之间选择通过page_view()-RegisterDrawableImage(...)注册并调用MarkDirty()保证首个纹理帧到达前也能在 layer tree 中创建DrawableImageLayer若没有 shared image sink 但支持 hybrid 合成则RenderExternalContent改用平台视图 idcontent-SetViewId(weak_self-id())。4.2 Java 侧创建Java 创建由PlatformViewPlugin.onCreateView(...)驱动步骤为判定 tag 是 internal 还是 external选择PlatformViewContextplatformViewContext.createNode(...)platformViewContext.getViewInNode(...)创建 holder 路径应用 tag 专属的渲染模式策略。对 internal 包装视图创建路径继续延伸为InternalPlatformViewRegistry.createView(...)InternalPlatformViewWrapper.createView(...)LynxUIClayUIBridge.createLynxUI(...)LynxUIClayUIBridge.setSign(...)可选通过tryInstallClaySubtreeRasterSnapshotProvider(...)安装LynxContext.ClaySnapshotProviderLynxUIClayUIBridge.getView(...)创建契约完成的判定标准一个真实的 AndroidView已经存在。在此之前 create 都视为未完成。5. Insert 契约Insert 只在 C 侧 Clay 父子关系已经建立之后触发调用链为BaseView::AddChild(...)NativeView::OnInsert(parentId, index)见 native_view.cc经插件转发plugin.OnInsert(parent_id, index)JNINativeViewPluginAndroid::OnInsert(...)JavaPlatformViewPlugin.onInsertNode(...)PlatformViewContext.insertNode(child, parent, index)对 internal 包装视图InternalPlatformViewWrapper.insertChild(...)LynxUIClayUIBridge.insertChild(parent, child, index)当前 bridge 行为调用parent.insertChild(child, safeIndex)以保持 Lynx 侧逻辑父级归属移植相关的LynxUIOwner绘制列表插入逻辑使包装子节点在适用时参与 Lynx 侧的绘制排序。规范性要求平台渲染策略永远不得破坏逻辑插入关系logical insert relation。这条是第 14 节不变量的组成部分。6. Attach / Detach 契约6.1 AttachAttach 由 C 树挂载驱动NativeView::OnAttachToTree()plugin.OnAttach()JavaPlatformViewPlugin.onViewAttached()holder 按其 holder 策略需要时挂载到FlutterViewPlatformViewContext.onViewAttached(viewId)internal wrapper 转发LynxUIClayUIBridge.onAttach(...)6.2 DetachDetach 与之对称NativeView::OnDetachFromTree()plugin.OnDetach()JavaPlatformViewPlugin.onViewDetached()holder 如已挂载则从FlutterView摘除PlatformViewContext.removeNode(viewId)internal wrapper 转发LynxUIClayUIBridge.onDetach(...)当前 wrapper 契约的三条时序规则attach 在每个 attach 周期内只发生一次而不是每个 create 周期一次detach 在 destroy 之前可以发生多次destroy 时若视图当前仍处于 attach 状态必须仍然补发一次 detach。7. Props / Events 契约Props 与 events 从 C 暂存属性staged attributes批量下发NativeView::DidUpdateAttributes()plugin.UpdatePlatformAttributes(staging_attrs, events)JavaPlatformViewPlugin.onPlatformViewAttributesUpdate(...)PlatformViewContext.updateProps(...)对 internal 包装视图InternalPlatformViewWrapper.setAttribute(...)逐个 propInternalPlatformViewWrapper.setEvents(...)InternalPlatformViewWrapper.onAttributesUpdated()当前 wrapper 语义的关键点onAttributesUpdated()不直接派发 nodeReady它只标记一个完整的 props/events 批次已经完成nodeReady 只有在原生 ready 信号与 props 批次两者齐备后才可能触发。C 侧的暂存机制在源码中清晰可见SetAttribute 对未命中通用处理的属性执行staging_attrs_.emplace(attr, CloneClayValue(value))随后 DidUpdateAttributes 将staging_attrs_整体 move 走并连同事件数组一次性调用plugin.UpdatePlatformAttributes(...)随后还会惰性尝试创建SharedImageSink以绑定外部纹理。这解释了文档所称batched from C staged attributes的准确含义。8. Layout 契约8.1 C 到 Java 的布局流平台布局来源于 Clay bounds而不是AndroidView#onLayout。调用链NativeView::OnPainting()NativeView::ApplyUpdateChanged()ContentBoundsInViewport()转换为平台单位plugin.LayoutChanged(left, top, width, height)JavaPlatformViewPlugin.onLayout(...)holder 更新布局 / 返回 buffer 尺寸PlatformViewContext.onLayout(...)Padding 经由plugin.UpdatePaddings(...)单独中继在 Java 侧合并。源码细节上有两点值得注意见 ApplyUpdateChanged只有当bounds或device_pixel_ratio相对缓存值变化时才向平台推送避免重复下发推送前通过page_view()-ConvertTokPixelTypePlatform(bounds)完成像素类型换算。而 OnPainting 中的注释解释了为何要在全局 Painting 事件前检查参考 AndroidSurfaceView使用ViewTreeObserver.onPreDraw更新 surface 的做法。8.2 internal 包装视图的布局中继对InternalPlatformViewWrapperlayout 与 padding 值独立缓存updateLayoutInfoIfNeeded(...)在必要时把缓存值与当前 Android view 的兜底值合并缺失值默认为0通过对比上一次已派发的 tuple 来抑制重复派发。bridge 侧LynxUIClayUIBridgeImpl.updateLayoutInfo(...)的行为先调用updateLayoutSize(width, height)保留 Clay 未提供的 margin、border、bound 字段以 Clay 提供的位置/尺寸/padding 加上被保留字段调用updateLayout(...)。这与不变量layout relay 必须保留 Clay 当前不拥有的字段直接对应。8.3 nodeReady 之前的布局当前NativeView行为要求在 ready 前推送一次最终更新NativeView::OnNodeReady()先调用ApplyUpdateChanged()然后才调用plugin.OnNodeReady()见 OnNodeReady源码注释说明这是为了防止 node-ready 在没有前置 layout-finish 冲刷的情况下被触发时布局失配。平台投递按实现不同而不同Android队列中的布局被同步应用因此 Java 对同一个 patch 会先观察到onLayout(...)再观察到onNodeReady()iOS在转发onNodeReady()之前会冲刷当前视图队列中的 props、events 与 padding——即便视觉变更投递在布局或滚动爆发期整体是延迟的。注意iOS 布局仍由合成器compositor拥有并排队等待正常 presentation 冲刷该生命周期保证并不意味着onNodeReady()之前 UIKit 布局已经完成。该 iOS 视觉冲刷的作用域严格限定为一个新 ready 的视图它防止那些在 node-ready 时机注册初始 prop 工作的平台组件丢失该工作同时避免把 ready 回调变成一次通用同步布局冲刷。9. NodeReady 契约NodeReady 完全由 C 驱动。被包装 internal native view 的参考调用链为PaintingContextClayRef::UpdateNodeReadyPatching(...)ViewContext::UpdateNodeReadyPatching(...)NativeView::OnNodeReady()JNINativeViewPluginAndroid::OnNodeReady()JavaPlatformViewPlugin.onNodeReady()PlatformViewContext.onNodeReady(viewId)InternalPlatformViewWrapper.onNodeReady()LynxUIClayUIBridge.onNodeReady(lynxUI)LynxBaseUI.onNodeReady()InternalPlatformViewWrapper当前以三个条件门控 ready原生 ready 信号已到达包装视图已 attach至少一个 props/events 批次已完成。附加规则ready 对每个节点实例是一次性的one-shotJava 不得从 attach、props 或 Android layout 回调中合成 nodeReadyonLayoutFinish()仅为旧调用点保留的兼容别名新行为必须以onNodeReady()来描述。仓库中的测试印证了该 patching 机制base_view_unittests.cc 覆盖了一次性 ready 语义重复派发相同 id 不产生二次效果与经 platform ref 派发 ready 的路径native_view_unittests.cc 则针对NativeView组件层有独立用例。10. Destroy 契约销毁流程JavaPlatformViewPlugin.onDestroyView()holderrelease()PlatformViewContext.destroyNode(viewId)internal wrapperLynxUIClayUIBridge.onNodeRemoved(...)LynxUIClayUIBridge.destroy(...)CNativeView::OnDestroy()释放已注册的 drawable image当前 wrapper 行为onNodeRemoved(...)至多发出一次若包装视图仍处于 attach 状态先发出onDetach(...)再执行移除/销毁。C 侧的释放路径见 OnDestroy先以 cancel 动作终结触摸派发状态再调用plugin.OnDestroy()最后UnregisterDrawableImage(*tex_id_)注销外部纹理注册。源码注释还特意提醒Destroy 目前先于析构函数调用后续可考虑统一销毁流程。11. 渲染与合成模式11.1 Holder 路径当前有三条 holder 路径模式关键类外部纹理合成PlatformViewWrapperHolder、PlatformViewWrapper混合合成hybridPlatformViewHybridHolder、PlatformViewsController、FlutterMutatorView虚拟显示virtual-display兜底PlatformViewVDHolderC 创建阶段上报的SupportHybridComposition()即用于决定走纹理还是 hybrid 路径见 构造函数回调有image_sink则外部纹理否则若支持 hybrid 则用平台视图 id 合成。11.2 Logical-only 渲染模式platformcanDrawfalseLogical-only 是平台渲染策略不是生命周期策略。它的含义是Java 插件与 holder 路径依然存在尺寸与 buffer 记账依然进行生命周期与方法路由依然进行逻辑插入依然进行只有平台层绘制被抑制。它不意味着删除 native-view 节点、跳过 holder 创建、跳过LynxBaseUI生命周期、或改变 Clay 子节点的绘制顺序。当前实现细节纹理路径PlatformViewPlugin以canDrawfalse创建PlatformViewWrapperHolderholder 在构造时设置PlatformViewWrapper.canDrawfalsePlatformViewWrapperHolder.onViewAttached(...)跳过把 wrapper 加入FlutterViewhybrid 路径PlatformViewPlugin以canDrawfalse创建PlatformViewHybridHolderholder 调用PlatformViewsController.setPlatformViewCanDraw(viewId, false)PlatformViewsController追踪不可绘制的平台视图 idinitializePlatformViewIfNeeded(...)跳过把不可绘制的 mutator view 加入FlutterViewattachToView(...)在 controller 重挂载时跳过重新加入若canDrawfalse在 mutator view 已挂载后才生效PlatformViewsController会将其从FlutterView移除。文档明确指出的当前限制platformcanDrawfalse抑制的是平台层渲染但不能完全消除 holder 创建以及 create 路径中更早的分配开销。12. 布局与测量职责默认NativeView行为native view 是自定义可测量的叶子节点custom-measurable leaves除非被专门特化Yoga 把这类节点当叶子处理跳过后代布局。C 侧的测量走 Measure把约束换算到平台单位后经插件异步plugin.Measure(...)获取结果再把 width/height/baseline 换算回 Clay 单位。特化规则若某特化需要后代布局必须禁用 measure 函数同时不能把节点变成虚拟节点virtual node。当前示例Android 地图 tag 通过 internal 平台视图 tag 策略实现ViewRegistry::CreateView(...)创建NativeView而ViewRegistry::CreateShadowNode(...)对x-map-ng、x-map-marker-ng这类仅视图view-only的 tag 不返回NativeViewShadowNode。该示例的完整规则见专项文档 map_view_spec.md。13. 可选的 Clay 子树光栅快照扩展被包装的 internal native view 可以向 Java 暴露一个 Clay 子树光栅快照 provider。通用流程Java wrapper 经InternalPlatformView调用requestSubtreeRasterSnapshot(scale, callback)PlatformViewPlugin.requestSubtreeRasterSnapshot(...)转发到 JNInativeRequestSubtreeRasterSnapshot(...)选择快照根PageView::MakeRasterSnapshot(...)把子树渲染为编码字节结果异步送达需要触碰 Android UI 状态的 Java 消费者必须自行切换到 UI 线程。当前 Android 实现的特征成功回调返回编码后的 PNG 字节及输出宽高失败通过onError()异步上报当前 Android 实现快照的是所请求的NativeView根节点自身地图 marker 特化依赖 marker 的NativeView根。快照前置条件三条缺一不可必须存在合法的快照根根的宽高必须非零根在快照前必须是、或能够成为重绘边界repaint boundary。14. 不变量清单规格文档以一节明确的不变量收尾这也是实现层评审与代码变更的验收基准create、insert、attach、layout、nodeReady、destroy必须端到端保持 C 驱动Java 不得从 attach、props 或 Android layout 回调合成 nodeReadyInternalPlatformViewWrapper在 attach 与首个 props/events 批次两者都满足之前不得派发LynxBaseUI.onNodeReady()platformcanDrawfalse必须通过抑制平台层挂载/绘制来实现而不是重新定义 Clay 子节点绘制语义holder 或渲染模式策略不得破坏逻辑父子插入关系layout relay 必须保留 Clay 当前不拥有的字段子树快照回调必须能容忍异步完成与销毁竞态特化专属规则应写入map_view_spec.md等专项规格而不是这个通用文件。15. 结语如何阅读这份契约native_view_spec.md的价值在于把一条跨 C、JNI、Java、bridge 四层栈的调用链变成了可验收的契约每一个动词OnCreate、OnInsert、OnAttach、OnNodeReady……都有明确的发起方与接收方每一次时序ready 前布局、one-shot ready、destroy 前补发 detach都有可测试的判定。对开发者的实际意义是写新包装视图时按第 410 节逐条实现生命周期转发用第 14 节不变量做自查排查渲染问题时先分清是 logical 层Clay 树、insert 关系还是 platform 层holder、canDraw出了问题两者在这份规格中是被刻意隔离的做专项扩展如地图、输入框聚焦时遵循特化规则进专项 spec的边界约定保持通用契约稳定。【免费下载链接】lynxEmpower the Web community and invite more to build across platforms.项目地址: https://gitcode.com/GitHub_Trending/lynx10/lynx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表