ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Litho Working Ranges 完全指南:用 Sections 预取数据与缓存预热

Litho Working Ranges 完全指南:用 Sections 预取数据与缓存预热 移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载导读本文基于 working-ranges.md 展开系统讲解 Litho 的Working Ranges API如何通过定义WorkingRange、注册范围、接收进入/退出事件在列表滚动过程中实现数据预取prefetch与缓存预热cache warming等复杂操作。读完本文你将掌握范围定义、OnRegisterRanges/OnEnteredRange/OnExitedRange三件套的完整用法并理解其底层分发机制WorkingRangeContainer与官方实现BoundaryWorkingRange的原理能够在自己的 RecyclerView/Sections 列表中落地分页加载与预取逻辑。说明本文面向的是基于 Java codegen 的经典 Sections API。若你使用 Kotlin Lazy Collection API可参阅 Lazy Collection 交互文档 中对应的内容。Working Ranges API 概述Working Ranges API 为 Sections 提供了一组外观与可见性事件appearance visibility events当一个 Section 进入或退出视口viewport内外的某个位置区间时框架会触发对应回调。典型场景是当列表最后一个元素即将进入视口时立刻发起网络请求开始预取下一页数据从而消除滚动到底部的等待时间。API 由两部分组成定义范围Defining a range—— 实现WorkingRange接口描述什么位置算在范围内接收范围事件Receiving range events—— 在组件中注册范围并实现进入/退出回调Sections 与范围交互时触发。从源码结构看该能力贯穿 litho-annotations注解定义、litho-processor代码生成与 litho-core运行时容器三个模块。第一部分定义范围Defining a Range实现 WorkingRange 接口要使用 Working Ranges API首先定义一个实现WorkingRange接口的类public class AtLeastPartiallyVisibleRange implements WorkingRange { ... }该接口只要求实现两个函数shouldEnterRange与shouldExitRange。两者签名一致Kotlin 源接口定义见 WorkingRange.ktfun shouldEnterRange( position: Int, firstVisibleIndex: Int, lastVisibleIndex: Int, firstFullyVisibleIndex: Int, lastFullyVisibleIndex: Int ): Boolean fun shouldExitRange( position: Int, firstVisibleIndex: Int, lastVisibleIndex: Int, firstFullyVisibleIndex: Int, lastFullyVisibleIndex: Int ): Boolean参数含义如下参数含义position当前检查的条目在列表中的位置firstVisibleIndex视口内第一个部分或完全可见条目的位置lastVisibleIndex视口内最后一个部分或完全可见条目的位置firstFullyVisibleIndex视口内第一个完全可见条目的位置lastFullyVisibleIndex视口内最后一个完全可见条目的位置firstVisibleIndex/lastVisibleIndex与firstFullyVisibleIndex/lastFullyVisibleIndex共同刻画了当前视口可见范围你的范围判定逻辑就是基于这些值对position做区间运算。使用 shouldEnterRangeshouldEnterRange用于判断条目是否位于用户自定义的范围内。下面的示例判断position 至少部分可见于屏幕Override public boolean shouldEnterRange( int position, int firstVisibleIndex, int lastVisibleIndex, int firstFullyVisibleIndex, int lastFullyVisibleIndex) { return position firstVisibleIndex position lastVisibleIndex; }即只要position落在[firstVisibleIndex, lastVisibleIndex]闭区间内就认为进入了范围此时框架会触发该组件的OnEnteredRange回调。使用 shouldExitRangeshouldExitRange用于判断条目是否位于用户自定义范围之外。下面的示例判断position 完全不可见连部分可见都不算Override public boolean shouldExitRange( int position, int firstVisibleIndex, int lastVisibleIndex, int firstFullyVisibleIndex, int lastFullyVisibleIndex) { return position firstVisibleIndex || position lastVisibleIndex; }即只要position落在可见闭区间之外就认为退出了范围此时框架会触发该组件的OnExitedRange回调。官方内置实现BoundaryWorkingRange仓库在 BoundaryWorkingRange.kt 中提供了一个开箱即用的实现。它以offset定义范围的上下边界position落在[firstVisiblePosition - offset, lastVisiblePosition offset]之间即视为在范围内否则视为在范围外class BoundaryWorkingRange JvmOverloads constructor(private val offset: Int OFFSET) : WorkingRange { override fun shouldEnterRange(...): Boolean isInRange(position, firstVisibleIndex, lastVisibleIndex, offset) override fun shouldExitRange(...): Boolean !isInRange(position, firstVisibleIndex, lastVisibleIndex, offset) companion object { private const val OFFSET 1 private fun isInRange( position: Int, firstVisiblePosition: Int, lastVisiblePosition: Int, offset: Int ): Boolean { val lowerBound firstVisiblePosition - offset val upperBound lastVisiblePosition offset return position in lowerBound..upperBound } } }默认offset 1即预取范围是视口上下各多一个条目。你可以通过构造函数传入自定义 offset 来扩大预取窗口——例如想做提前 5 个条目预取传入BoundaryWorkingRange(5)即可。这正是列表最后一个元素接近视口时开始预取这一场景的直接实现。位置粒度 vs 像素粒度:::caution 重要提示 Working ranges 在 ComponentKit 中以像素度量粒度而 Litho 以**位置position即条目序号**度量。这意味着 Litho 只能判断组件是否落在某个位置区间内而无法获知区间内具体有多少像素。 :::这带来一个实际约束你的预取/预热判断只能基于第几个条目无法精确到距视口多少像素。在设计预取阈值时建议结合条目高度估算或直接使用位置差作为阈值。第二部分接收范围事件Receiving Range Events在 LayoutSpec 中注册范围定义好范围后需要在包含RecyclerComponent的LayoutSpec中实现回调函数来接收 enter/exit 事件。第一步是通过OnRegisterRanges把范围注册到组件上LayoutSpec class ListContainerComponentSpec { OnRegisterRanges static void registerWorkingRanges( ComponentContext c, Prop AtLeastPartiallyVisibleRange myRange) { ListContainerComponent.registerPrefetchWorkingRange(c, myRange); } }这里的registerPrefetchWorkingRange是由注解处理器litho-processor中的 WorkingRangeGenerator.java根据范围 name 自动生成的注册方法registerNameWorkingRange(c, workingRange)。进入范围事件OnEnteredRange当组件进入范围时触发OnEnteredRange事件OnEnteredRange(name prefetch) static void onEnteredWorkingRange( ComponentContext c, Prop MyService service) { service.startPrefetch(); }典型用法即是在此发起预取网络请求。注解定义见 OnEnteredRange.java其文档明确指出要让该回调生效必须同时在OnRegisterRanges中完成注册。退出范围事件OnExitedRange当组件退出范围时触发OnExitedRange事件OnExitedRange(name prefetch) static void onExitedWorkingRange( ComponentContext c, Prop MyService service) { service.cancelAllPrefetches(); }典型用法是取消已发出的预取请求例如用户快速滑过大量条目时回收资源。注解定义见 OnExitedRange.java。name 字段为同一 Layout 启用多个范围事件OnEnteredRange与OnExitedRange的name字段用于在同一个 Layout上启用多组范围事件。例如 name 为paginate时自动生成的注册方法就是registerPaginateWorkingRangeOnEnteredRange(name paginate) static void onPaginateEntered(...) { ... } OnExitedRange(name paginate) static void onPaginateExited(...) { ... } OnRegisterRanges static void registerWorkingRanges(ComponentContext c, ...) { ListContainerComponent.registerPaginateWorkingRange(c, myRange); }这样同一个列表可以同时拥有prefetch、paginate、warmCache等多套互不干扰的范围事件分别注册不同的WorkingRange实例。底层原理WorkingRangeContainer 的分发机制理解了 API 用法之后我们来看运行时容器 WorkingRangeContainer.kt 如何支撑这套机制。注册name hashCode 作为键registerWorkingRange将范围以${name}_${workingRange.hashCode()}为键存入workingRanges映射多个共享同一 name 与同一范围对象的组件会被聚合进同一个RangeTuple见 WorkingRangeContainer.kt#L40-L53fun registerWorkingRange( name: String, workingRange: WorkingRange, scopedComponentInfo: ScopedComponentInfo, interStageProps: InterStagePropsContainer? ) { val key ${name}_${workingRange.hashCode()} val rangeTuple workingRanges[key] if (rangeTuple null) { workingRanges[key] RangeTuple(name, workingRange, scopedComponentInfo, interStageProps) } else { rangeTuple.addComponent(scopedComponentInfo) } }RangeTuple内部持有一个scopedComponentInfos列表意味着一个范围可以关联列表中的多个组件实例。分发checkWorkingRangeAndDispatch当列表滚动、视口信息更新时框架调用checkWorkingRangeAndDispatchWorkingRangeContainer.kt#L59-L117。它对每个已注册范围执行如下逻辑通过WorkingRangeStatusHandler查询该组件以globalKey标识当前是否已被标记为在范围内isInRange若当前不在范围内且isEnteringRange(...)内部即调用workingRange.shouldEnterRange返回 true则调用component.dispatchOnEnteredRange(...)触发OnEnteredRange随后调用statusHandler.setEnteredRangeStatus(...)更新状态避免重复触发若当前在范围内且isExitingRange(...)内部即调用workingRange.shouldExitRange返回 true则调用component.dispatchOnExitedRange(...)触发OnExitedRange并更新状态。这个状态机设计保证了 enter/exit 事件只在状态翻转时触发一次不会在每次滚动回调中反复触发避免重复预取。释放时的退出分发dispatchOnExitedRangeIfNeededWorkingRangeContainer.kt#L123-L149用于释放 ComponentTree 时兜底只要组件仍处于在范围内状态就补发一次OnExitedRange确保资源如未完成的预取任务能被清理。调用方见 ComponentTree.java 中的clearWorkingRangeStatusHandler()L1348-L1353。状态跟踪WorkingRangeStatusHandler每个ComponentTree持有独立的 WorkingRangeStatusHandler.kt见 ComponentTree.java#L268-L269以name globalKey为维度记录每个组件实例当前是否处于范围内。这是保证事件边沿触发而非电平触发的关键。实战示例在列表末尾预取下一页综合以上内容一个完整的列表滑动到底前预取下一页示例// 1. 定义范围最后一个可见条目之后的一个位置即可触发 public class PrefetchRange implements WorkingRange { Override public boolean shouldEnterRange( int position, int firstVisibleIndex, int lastVisibleIndex, int firstFullyVisibleIndex, int lastFullyVisibleIndex) { // 当 position 越过当前可见区预取下一页时进入范围 return position lastVisibleIndex 1; } Override public boolean shouldExitRange( int position, int firstVisibleIndex, int lastVisibleIndex, int firstFullyVisibleIndex, int lastFullyVisibleIndex) { return position lastVisibleIndex 1; } } // 2. 在包含 Recycler 的 LayoutSpec 中注册并接收事件 LayoutSpec class FeedComponentSpec { OnRegisterRanges static void registerWorkingRanges( ComponentContext c, Prop PrefetchRange prefetchRange) { FeedComponent.registerPaginateWorkingRange(c, prefetchRange); } OnEnteredRange(name paginate) static void onEnteredWorkingRange( ComponentContext c, Prop PageFetcher fetcher) { fetcher.fetchNextPage(); // 滑动接近末尾时触发分页加载 } OnExitedRange(name paginate) static void onExitedWorkingRange( ComponentContext c, Prop PageFetcher fetcher) { fetcher.cancelPendingFetches(); // 远离范围时取消请求 } }测试验证与更多参考仓库中提供了可直接对照的测试实现LayoutSpec 测试组件LayoutSpecWorkingRangeTesterSpec.kt展示了OnRegisterRangesOnEnteredRange(name boundary)BoundaryWorkingRange()的完整注册与触发链路MountSpec 测试组件MountSpecWorkingRangeTesterSpec.kt说明MountSpec同样支持 Working Ranges注册方法变为MountSpecWorkingRangeTester.registerBoundaryWorkingRange运行时行为测试WorkingRangeContainerTest.kt覆盖了进入/退出/释放分发等边界场景注解定义OnRegisterRanges.java、OnEnteredRange.java、OnExitedRange.java。需要注意OnRegisterRanges在注解源码中已被标记为Deprecated见 OnRegisterRanges.java官方建议未来版本迁移到 Kotlin Lazy Collection API 的交互方案lazycollections-interactions.mdx。但作为经典 Sections 的既有机制其概念与设计对理解 Litho 的可见性驱动编程模型依然有重要参考价值。赞分享移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载相关推荐Vue Query 数据预取Prefetching完整指南用 queryClient.query 与 infiniteQuery 预热缓存Vue Query 数据预取Prefetching完整指南用 queryClient.query 与 infiniteQuery 预热缓存 导读 在 T前端缓存状态管理DB-GPT缓存策略Redis缓存与数据预热DB GPT缓存策略Redis缓存与数据预热 引言为什么需要缓存策略 在大模型应用开发中数据库查询、模型推理和数据处理都是计算密集型操作。DB GPT作人工智能AI 应用AI AgentRAG本地部署数据分析Rivet缓存策略多级缓存与数据预热机制Rivet缓存策略多级缓存与数据预热机制 还在为高并发场景下的性能瓶颈而头疼Rivet的智能缓存系统帮你轻松应对百万级并发挑战本文将深入解析Rivet如何后端AI Agent人工智能流程编排WebSocket上一篇One-Core-API三步让Windows XP/2003运行现代软件的终极兼容方案下一篇cAdvisor 官方 Go 客户端使用指南对接 REST API 获取容器与机器监控数据创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表