Harmony os 技术实战|拼豆制图41:50 张图纸如何避免首屏一次性物化 24.5 万格
实际项目里图库数据量并不一定大真正拖慢首屏的往往是“每条数据都附带了一份详情”。《拼豆制图》只有 50 张内置图纸但每张详情图按 70×70 计算就是 4900 个BeadCell如果应用启动时全部构造首屏还没展示几张卡片内存里已经出现约 24.5 万个格子对象。这篇文章不讨论分页接口而是解决一个更贴近本地应用的问题怎样把图库卡片需要的轻数据与编号图详情需要的重数据拆开让 Harmony os 应用先完成首屏再按用户选择物化完整施工图。一、先算清“50 张图”背后的对象数量当前Pattern同时承担卡片模型和详情模型exportinterfacePattern{id:string;title:string;width:number;height:number;previewCells:BeadCell[];chartCells:BeadCell[];colorStats:BeadColorStat[];}图库卡片真正需要的是标题、分类、封面预览和少量统计信息chartCells只有进入编号图页面后才会被使用。把 4900 个格子挂在每一张卡片数据上会产生三类额外成本成本首屏表现根因对象创建启动阶段 CPU 峰值双重循环连续创建格子常驻内存列表没打开详情也占用卡片数组长期引用完整图状态比较页面重组时遍历更重大对象参与派生与缓存二、把 Catalog 与 Detail 拆成两个模型更稳的边界是列表只持有PatternCatalogItem详情页才获取PatternDetail。exportinterfacePatternCatalogItem{id:string;title:string;category:string;categoryName:string;width:number;height:number;likes:string;previewCells:BeadCell[];colorCount:number;}exportinterfacePatternDetailextendsPatternCatalogItem{difficulty:string;beadCount:number;chartCells:BeadCell[];colorStats:BeadColorStat[];}这个拆分不是为了多定义两个接口而是为了建立数据所有权图库页无权要求完整格子编号图页也不需要重新推导卡片元数据。三、Repository 先返回轻目录目录构建时只生成 35×35 或更小的预览矩阵不触碰 70×70 的完整数据。exportclassPatternRepository{staticgetCatalog():PatternCatalogItem[]{constseedsPatternRepository.createSeeds();constresult:PatternCatalogItem[][];for(leti0;iseeds.length;i){result.push(PatternRepository.createCatalogItem(seeds[i]));}returnresult;}privatestaticcreateCatalogItem(seed:PatternSeed):PatternCatalogItem{constassetPatternAssetCharts.get(seed.id);constpreviewCellsPatternRepository.createPreview(seed,asset);return{id:seed.id,title:seed.title,category:seed.category,categoryName:seed.categoryName,width:assetnull?32:asset.chartRows[0].length,height:assetnull?32:asset.chartRows.length,likes:seed.likes,previewCells,colorCount:assetnull?seed.palette.length:asset.colors.length};}}这里最重要的约束是createCatalogItem()不得偷偷调用完整图构造函数。方法名和返回类型都应该把这个边界写清楚。四、进入详情时才物化 4900 个格子用户点击卡片后以稳定 ID 向仓库请求详情staticgetDetail(id:string):PatternDetail|null{constseedPatternRepository.findSeed(id);if(seednull){returnnull;}constassetPatternAssetCharts.get(seed.id);returnPatternRepository.createDetail(seed,asset);}页面只保存selectedPatternId而不是提前保存所有详情StateselectedPatternId:string;StateselectedDetail:PatternDetail|nullnull;privateopenPattern(id:string):void{constdetailPatternRepository.getDetail(id);if(detailnull){this.detailStatus图纸数据不存在;return;}this.selectedPatternIdid;this.selectedDetaildetail;this.activeTabnumbered;}这样做还能避免卡片对象和详情对象互相修改。收藏状态应由独立 ID 集合管理而不是写回这两个只读模型。五、详情缓存只保留真正看过的图纸完全不缓存会让用户来回进入同一张图时重复构造。更合适的是一个容量明确的小缓存privatestaticreadonlydetailCachenewMapstring,PatternDetail();privatestaticreadonlydetailOrder:string[][];privatestaticreadonlymaxDetailCache6;privatestaticremember(detail:PatternDetail):void{PatternRepository.detailCache.set(detail.id,detail);PatternRepository.detailOrder.push(detail.id);if(PatternRepository.detailOrder.lengthPatternRepository.maxDetailCache){constoldestPatternRepository.detailOrder.shift();if(oldest!undefined){PatternRepository.detailCache.delete(oldest);}}}容量 6 不是固定答案关键是它可测量、可解释。对于只有单页详情的本地工具缓存最近几张通常比永久缓存 50 张更符合使用路径。六、预览矩阵也要有明确预算轻目录不等于可以无限放大预览。若每张卡片持有 35×35 个对象50 张仍有 61250 个对象。可以继续压缩为字符串行或调色板索引exportinterfaceCompactPreview{width:number;height:number;rows:string[];colors:string[];}渲染前再把当前屏幕附近的预览解码成格子或者直接让卡片组件按字符索引绘制。优化应从 24.5 万格降到可控范围而不是把压力从详情数组搬到预览数组。七、用数据记录优化是否真的有效验证时至少记录三组数据interfaceStartupProbe{catalogCount:number;previewCellCount:number;materializedDetailCount:number;}推荐场景如下冷启动停留首页详情数应为 0。进入图库但不点卡片详情数仍为 0。连续打开 8 张图缓存详情数不超过设定上限。返回图库后卡片搜索和分类结果不因详情释放而变化。八、常见问题与修复问题表现修复目录构造仍调用完整图函数首屏耗时没有下降给目录与详情使用独立构造入口缓存命中后直接修改对象再次打开出现旧状态详情保持只读交互状态单独存放只压缩chartCells预览仍占大量对象统计预览总格数并设预算缓存没有上限使用越久内存越高加容量、淘汰顺序和观测字段排查时要把“目录对象数、预览格数、已物化详情数”分开记录。只看总内存很难判断压力来自哪里而这三个计数能直接对应目录构建、预览解码和详情缓存三个阶段修复后也能用同一路径复测。九、迁移检查清单图库页类型中不再出现chartCells。冷启动不会构造 50 份完整矩阵。详情丢失时显示可理解的空状态。收藏、搜索只依赖稳定 ID 和目录字段。缓存有上限并能通过计数确认淘汰。反复进入同一详情不会重复生成。清单应在冷启动、连续打开多张图和返回首页三条路径各执行一次。尤其要确认离开详情后目录仍可搜索说明轻数据没有意外依赖被淘汰的完整对象而不是只在第一次启动时看起来更快。十、总结本地图纸也需要数据分层。先用轻目录完成首屏再按 ID 物化完整编号图最后用小容量缓存覆盖返回路径就能把 Harmony os 图库从“一启动就准备全部详情”改成“只为当前操作付费”。标签Harmony os、ArkTS、ArkUI、性能优化、本地图库