文章导读

  • 首页、赛事页、装备页和球员页都复用了下拉刷新入口,但真正的数据拉取逻辑集中在 LiveFetch
  • 远程站点返回的是 GBK 编码网页,不能按 UTF-8 直接解析。
  • 刷新成功后写入 AppStorage,失败则保留本地数据,页面不会因为网络波动变空。

页面效果

对用户来说,下拉刷新的核心感受只有两件事:一是操作后有没有新内容,二是失败时页面会不会崩。羽球联盟把刷新交互做得比较克制,用户下拉后触发联网抓取,成功时用 Toast 告知新增条数,失败时给出错误提示,但首屏内容依旧可读。

羽球联盟首页截图

这类页面最怕“刷新失败等于空白”。如果联网能力没有兜底,本地资讯、赛事或装备列表都会因为一次请求失败显得像程序出错。把刷新理解为“增强数据”,而不是“唯一数据来源”,是这篇文章最重要的前提。

实战拆解

LiveFetch.refreshFromBadmintonCN() 做了四件事:创建 HTTP 请求、按数组缓冲读取网页、用 GBK 解码 HTML、再从 HTML 中抽出资讯和装备链接。之所以要走 ARRAY_BUFFER + TextDecoder.create('gbk'),是因为中羽在线这类站点不是 UTF-8 页面,直接按默认编码解析会得到乱码。

抓取到链接之后,项目并没有急着替换整站内容,而是先把数据整理成 Article[]Equipment[],再与现有 AppStorage 中的远程列表合并。新抓到的数据排在前面,历史远程数据保留在后面,本地内置数据仍然存在于其他页面的兜底链路里。这个策略让刷新结果既能体现“更新”,又不会破坏既有页面结构。

页面端的职责反而很轻。首页、赛事页、装备页、球员页都只需要在 onRefreshing() 时调用刷新函数,然后根据返回结果给出 Toast。业务复杂度主要沉在数据层,而不是散落到每个页面里,这样一来后续调整抓取策略也更容易集中处理。

关键代码

const res = await req.request(HOME_URL, {
  method: http.RequestMethod.GET,
  header: {
    'User-Agent': UA,
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.9'
  },
  expectDataType: http.HttpDataType.ARRAY_BUFFER,
  connectTimeout: 8000,
  readTimeout: 12000
});

const buf: ArrayBuffer = res.result as ArrayBuffer;
const decoder: util.TextDecoder = util.TextDecoder.create('gbk');
const html: string = decoder.decodeToString(new Uint8Array(buf), { stream: false });
const links: ParsedLink[] = parseHomepage(html);
const articleLinks: ParsedLink[] = links.filter((l: ParsedLink) => categorize(l.title) !== 'equipment');
const equipLinks: ParsedLink[] = links.filter((l: ParsedLink) => categorize(l.title) === 'equipment');
AppStorage.setOrCreate<Article[]>('remote_articles', mergedArticles);
AppStorage.setOrCreate<Equipment[]>('remote_equipment', mergedEquip);

这段逻辑把抓取、解码、分类和状态落盘串成了一条完整链路。真正的页面代码因此可以保持简洁,刷新行为和内容更新逻辑也更容易复用到多个页面。

取舍分析

这里最大的取舍,是抓取能力的复杂度应该放在哪一层。如果把编码处理、HTML 解析和去重逻辑写在页面里,任何一个列表页都会迅速变得难以维护。羽球联盟把这些行为收在 LiveFetch 里,本质上是在承认“联网刷新本身就是业务能力”,它应该拥有自己的边界。

另一个取舍是刷新失败后的体验。对资讯类应用来说,失败并不罕见,真正重要的是失败后用户还能不能继续看内容。项目保留本地数据作为兜底,使得联网失败只是“没有拿到更新”,而不是“页面彻底失效”。这比单纯追求每次刷新都成功更现实。

设计落点

  • 刷新交互由页面触发,抓取和解码逻辑由数据层统一处理。
  • 远程数据是增强项,本地数据是兜底项,两者职责不同。
  • 把编码、解析、分类和状态写入拆清楚,比一开始追求抓取范围更重要。

易踩坑

  • 不要把 GBK 网页按默认 UTF-8 直接解析,否则标题和正文很容易乱码。
  • 不要让刷新失败直接清空页面状态,兜底数据必须保留。
  • 多页面共用刷新能力时,不要在每个页面里复制一份联网实现。

验证方式

  • 在网络正常时下拉刷新,确认页面能提示新增资讯或装备条数。
  • 在网络异常时下拉刷新,确认页面给出失败提示但内容仍可浏览。
  • 检查抓取结果中的中文标题,确认没有出现编码乱码。

参考资料

小结

第 13 篇真正要讲的是,刷新不是一个页面小按钮,而是一整条数据链路。只要编码、抓取、合并和兜底逻辑各归其位,页面上的下拉动作才会显得可靠而自然。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐