尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HarmonyOS 7 ArkWeb:平行视界WebviewController归属与缓存

HarmonyOS 7 ArkWeb:平行视界WebviewController归属与缓存 平行视界里最让我意外的不是双栏布局而是返回键。左侧文档列表选中DOC-042右侧网页点进四层链接此时按返回到底该退网页历史、关闭详情还是把整个页面路由弹掉早期实现把三件事都绑在NavPathStack.pop()上结果网页内部链接根本退不回去切换条目后旧 Controller 还会把标题回调写到新详情。我把这个 Demo 命名为ParallelWebDesk页面是ArticleHubPage任务号WEB-1408。最终验收状态为DUAL_PANE选中DOC-042活动 Controller 2 个、缓存 1 个、池上限 3当前 Web 历史深度 4页面路由深度 2回退决策WEB_BACK丢弃迟到回调 1 次状态CONTROLLER_STABLE。一、一个 Controller 看起来能复用实际会把归属关系弄丢第一版只有一个WebviewController。用户点击左侧条目时右侧 Web 改 src为了加快切换我还把 Controller 放进全局变量。短时间测试没问题可连续打开两个详情后网页标题、进度和历史栈开始串页。Controller 的历史属于具体 Web 实例路由选中项则属于详情会话两者生命周期不一致。后来改成“一个详情会话一个 Controller”。池的 key 不是 URL而是detailSessionId同一详情里的站内跳转继续使用原 Controller切换文档则获取另一个槽位。列表选中DOC-042只说明当前业务项不等于网页历史根节点。把这两层身份拆开后迟到回调才有可以比较的 ownerEpoch。这也不同于此前 ArkWeb WebGL 上下文恢复或 WebMessagePort 销毁。前者处理图形资源重建后者处理 H5 通道代次这次页面本身不崩问题是多个详情的 Controller 归属、系统返回键仲裁和缓存上限。症状都发生在 ArkWeb修复点却不在同一层。二、Controller 池先定义归属再谈复用第一段代码解决 Controller 被两个详情同时借用的问题。acquireController()为 session 创建条目重复获取只更新访问时间缓存达到 3 个时只淘汰非活动且最久未使用的条目。活动详情的 Controller 绝不能被挪给另一个 Web 组件。interfaceControllerSlot{sessionId:stringdocId:stringcontroller:webview.WebviewController ownerEpoch:numberactive:booleanlastUsedAt:number}classWebControllerPool{privatereadonlylimit:number3privateslots:Mapstring,ControllerSlotnewMap()acquireController(sessionId:string,docId:string,epoch:number):ControllerSlot{letslotthis.slots.get(sessionId)if(!slot){this.evictInactiveIfNeeded()slot{sessionId,docId,ownerEpoch:epoch,active:true,lastUsedAt:Date.now(),controller:newwebview.WebviewController()}this.slots.set(sessionId,slot)}slot.activetrueslot.lastUsedAtDate.now()returnslot}}池上限 3 是 Demo 的内存预算不是 ArkWeb 的平台限制。折叠屏内双栏同时保留列表预览与详情所以验收时活动 Controller 为 2、非活动缓存为 1。若页面结构只有一个 Web池完全可以更小。这里最重要的不是数字而是“活动项不淘汰、不同 session 不共享实例”。创建 Controller 不代表 Web 已经可调用。页面通过onControllerAttached标记 attachState只有绑定后才执行加载、回退或脚本调用。组件重建时旧回调可能晚到回调携带 ownerEpoch和当前槽位不一致就丢弃。本轮记录的lateCallbackDropped1正是一次切换后的旧标题回调。三、返回动作必须先问 Web再问详情路由第二段代码是这次改造的核心。decideBackTarget()先查看当前详情是否存在、Controller 是否已绑定再调用accessBackward()判断网页历史。如果 Web 能后退决策为WEB_BACK并调用backward()否则双栏模式清空详情选中单栏模式再交给NavPathStack。typeBackDecisionWEB_BACK|CLEAR_DETAIL|ROUTE_POP|NOOPfunctiondecideBackTarget(mode:SINGLE_PANE|DUAL_PANE,slot:ControllerSlot|undefined,pathStack:NavPathStack):BackDecision{if(slot?.activeslot.controller.accessBackward()){slot.controller.backward()returnWEB_BACK}if(modeDUAL_PANEslot){detailState.clearSelection()returnCLEAR_DETAIL}if(pathStack.size()1){pathStack.pop()returnROUTE_POP}returnNOOP}调用顺序不能反过来。若先 pop 页面Web 的历史还没消费用户会直接离开工作台若无条件 backwardController 尚未绑定或已经换 owner就可能操作错误详情。回退决策也只执行一次手势返回和顶部按钮共用同一个仲裁器通过 backEpoch 防止一次手势触发两条路径。当前数据里 Web 历史深度为 4、路由深度为 2因此回退结果明确是WEB_BACK。等 Web 历史回到根页再按返回双栏才清空DOC-042在单栏设备上则可能 pop 详情路由。这里的“平行视界适配”不是把页面宽度分成两块而是让同一个返回动作在布局形态变化后仍有稳定语义。四、缓存回收要先断回调再移除引用第三段代码处理离场与内存收口。缓存淘汰不能只从 Map 删除先把 slot 设为 inactive、递增 ownerEpoch让迟到回调失效再清理业务监听和页面持有的引用。Controller 的底层资源由 Web 组件生命周期管理池只管理应用层所有权不伪造“已物理释放”的状态。releaseInactiveControllers(keepSessionId:string):void{constcandidates[...this.slots.values()].filter((slot)!slot.activeslot.sessionId!keepSessionId).sort((a,b)a.lastUsedAt-b.lastUsedAt)while(this.slots.sizethis.limit-1candidates.length0){constvictimcandidates.shift()!victim.ownerEpochwebEventHub.offOwner(victim.sessionId)this.slots.delete(victim.sessionId)}}limit - 1为即将加入的新槽位预留位置调用发生在新详情 attach 前。重复调用不会继续删除活动项候选为空时保持现状并记录压力日志而不是错误回收正在显示的详情。页面退后台时暂停进度订阅重新前台后按 sessionId 恢复不新建重复 Controller。如果系统发出内存压力策略会更积极先清空非活动缓存再让看不见的预览 Web 释放。当前文章没有把内存等级 API 混进主线避免把 Controller 归属和系统回收写成同一个问题产品实现可以在池外追加策略仍需遵守 active 不被抢占的约束。五、调试页要让三层状态同时可见工程目录包含WebControllerPool.ets、BackArbiter.ets、DetailSessionStore.ets、ArticleHubPage.ets和ParallelDetailView.ets。HiLog 固定输出taskWEB-1408 modeDUAL_PANE selectedDOC-042、activeControllers2 cachedControllers1 poolLimit3、webHistory4 routeDepth2 backDecisionWEB_BACK、lateCallbackDropped1 stateCONTROLLER_STABLE。IDE 中间停在decideBackTarget()右侧模拟器展示双栏文档工作台底部日志把业务选中、Controller 池和回退仲裁三层状态放在一起。这样排查“返回错了”时不会只看到路由栈也不会只盯 Web 历史。红色标注只指向accessBackward()的优先分支。六、旋转和折叠发生在回退之前怎么办我用一条固定路径回归双栏选中DOC-042详情内连续跳转四层窗口收窄切成单栏再立刻触发返回。形态变化只更新 mode不改 Controller 的 sessionId 和 Web 历史因此第一次返回仍执行WEB_BACK。只有历史回到根页下一次返回才根据当时的 SINGLE_PANE 决定ROUTE_POP。最终运行页时间 14:08、电量 82%显示 modeDUAL_PANE、选中DOC-042、活动 2、缓存 1、上限 3、Web 历史 4、路由深度 2、决策WEB_BACK、迟到回调丢弃 1状态CONTROLLER_STABLE。按钮“返回上一页”走仲裁器“清理非活动页”调用受限缓存回收。七、三个看起来像优化、其实会破坏语义的做法第一种是按 URL 复用 Controller。两个业务文档可能跳到同一登录页或帮助页URL 相同不代表详情会话相同。第二种是切换选中项时直接清空全部 Web 历史这会让用户在当前详情里失去正常回退。第三种是把 Controller 放进全局单例却不记录 owner页面重建后没人能判断回调属于谁。我更愿意接受三个受控槽位而不是一个“万能 Controller”。缓存数量稍多却换来了可解释的归属。真要压内存也应淘汰完整的非活动 session而不是让两个可见组件共享一个历史对象。优化必须服从页面语义否则省下的初始化时间会变成无法复现的串页问题。八、平行视界适配最后落在状态边界上做完以后双栏布局本身反而成了最简单的一部分。真正需要设计的是列表选中属于业务状态NavPathStack 属于页面路由WebviewController 属于 Web 会话三者谁先消费返回、谁在形态切换后保留、谁在缓存淘汰时失效。这套实现没有覆盖网页下载、权限弹窗或多窗口共享 Cookie它只解决当前 Demo 的 Controller 归属、回退仲裁和缓存回收。边界写清楚后状态页上的CONTROLLER_STABLE才不是一句装饰文字而是可以由活动数、缓存数、历史深度和迟到回调共同证明的工程结论。
返回列表