
做了这么多年移动端开发我逐渐形成一个判断“文章列表”这种看起来最不起眼的功能往往才是检验一个项目技术底子的试金石。列表页要处理数据加载、状态切换、滚动性能、下拉刷新、分页加载、组件通信——几乎每个环节都能翻车。我这次在 Harmony6.0 上用 Flutter 构建博客的文章列表区域前前后后踩了不下十几个坑从工具链版本对齐、渲染引擎差异到数据层异步调度、列表项性能优化再到最终打包上架的适配问题基本把 Flutter 开发里那些容易“看似没问题、一跑就出事”的角落都过了一遍。这篇文章完整记录一下整个过程的思路、取舍和实操细节给准备在 HarmonyOS 设备上做 Flutter 项目的同学一个参考。先说一个基本结论Harmony6.0 对 Flutter 的兼容性整体可以接受但它不是“换了个壳的 Android”从构建配置到生命周期处理再到 PlatformView 机制处处有细节差异。下面我按实际开发推进的顺序来写重点放在“为什么这么做”而不是单纯贴代码。1. 项目背景与技术选型为什么把文章列表当作一个完整工程来做1.1 需求还原一个“普通”列表页有多少隐藏考点这个项目的核心需求很简单在 HarmonyOS 6.0 设备上运行一个 Flutter 应用用来展示博客的文章列表。文章列表展示标题、摘要、封面图、发布时间、阅读量支持下拉刷新、上拉加载更多点进列表项进入文章详情。听起来五六个页面组件两三天能搞定对吧实际上拆开看每个环节都有值得单独深挖的点列表数据从哪来接口请求失败怎么办弱网重试怎么设计图片加载失败如何兜底缓存策略怎么定滚动时列表帧率稳定吗图片多了会不会卡下拉刷新和分页加载怎么共存它们触发的数据请求优先级怎么控制组件之间怎么通信是 setState 逐层传、还是上状态管理框架、还是事件总线切后台回来数据会丢吗页面状态怎么恢复这些单拎出来都不算大问题但堆在一起如果一开始没有清晰的技术选型思路后期每加一个功能都会拖泥带水。1.2 技术栈里的两个关键变量Impeller 渲染引擎与 PlatformView 机制Flutter 在列表场景里表现如何很大程度上取决于渲染引擎。Flutter 从 3.7 版本开始逐步用 Impeller 替换默认的 Skia 渲染引擎。Impeller 的核心思路是预编译着色器替代 Skia 那种运行时编译着色器的方式。做过 Flutter 性能优化的人都知道Skia 在复杂页面首次滚动时容易掉帧因为要临时编译 shader表现就是“第一遍滑动卡一下第二遍就顺了”。Impeller 把这个编译过程提前到离线完成首帧和滚动稳定性都好很多。我在 Harmony6.0 上跑列表页时特别注意开启了 Impeller 验证滚动表现。实测下来列表滚动掉帧情况明显优于早期版本特别是在内容较多、封面图加载频繁的混合内容列表上帧率曲线比 Skia 时代平滑。如果你还在 Flutter 3.7 以下版本列表类应用我建议尽早升级Impeller 带来的体验提升是实实在在的。另一个关键机制是 PlatformView。HarmonyOS 上如果列表里需要嵌入原生组件比如地图、视频播放器、某些系统视图Flutter 无法直接渲染必须走 PlatformView 通道。Harmony6.0 的 Flutter 适配层对 PlatformView 的支持粒度直接影响混合列表的流畅度。我在文章详情页里临时塞过一个视频组件实测发现 PlatformView 在滚动过程中的性能损耗明显高于 Android 平台中间涉及原生视图和 Flutter 视图的合成策略问题。这个后面单开一节细说。1.3 先定技术基调状态管理、网络层、缓存策略文章列表这种场景我最终的技术选型是状态管理Provider轻量且足够覆盖列表页的状态流转不引入过重的框架。网络层Dio拦截器好写方便统一处理 token、错误码和日志。缓存本地一级缓存 内存缓存文章列表这种读多写少的数据断网时给出最近一次成功数据体验比单纯报错强太多。列表框架直接用ListView.builder配合骨架屏和分页加载逻辑自己封装不引入额外的无限滚动插件。这个选型不算激进但足够应对文章列表的所有需求。下面从环境搭建开始按真实推进顺序写。2. Harmony6.0 环境搭建版本对齐与 Gradle 配置的连环坑2.1 版本对齐是第一道坎Flutter SDK、HarmonyOS SDK、Gradle 缺一不可Harmony6.0 跑 Flutter 项目第一个大坑就是版本对齐。这一行命令flutter doctor未必能全部检查出来因为 HarmonyOS 的 Flutter 支持往往依赖特定的发行渠道和补丁版本。我遇到的情况是电脑上 Flutter SDK 是最新的 stable 分支建出来的项目在 Android 上完全正常但跑到 Harmony6.0 设备上直接编译报错。后来排查到根因是HarmonyOS SDK 对 Flutter 版本有明确依赖部分 API 适配只存在于特定版本范围内。解决方案很简单但很耗时把本机 Flutter 版本切到 HarmonyOS 推荐的分支建议是使用华为开源社区维护的 OpenHarmony Flutter 分支而不是官方主干分支。切版本时注意flutter upgrade会覆盖本地配置务必先锁定项目的 flutter 版本配置文件再操作。2.2 “新建项目跑不起来”的排查链路从 Dart VM 报错到构建脚本排查如果你和我一样在 Harmony6.0 上新建 Flutter 项目后遇到跑不起来的状况不要急着怀疑代码先按这个顺序查第一步看运行日志里有没有[ERROR:flutter/runtime/dart_vm_initializer.cc(41)]这类报错。这是 Flutter 引擎初始化时 Dart VM 启动失败的典型日志很多初学者看到这一长串路径直接傻眼其实真正的原因通常是原生工程配置和 Dart 入口文件不匹配或者运行模式debug/release不被当前 SDK 支持。我在调试阶段发现 HarmonyOS 下 debug 模式的 JIT 运行偶尔不稳定切到 profile 或 release 模式反而更稳。第二步检查 gradle 构建脚本里的 AGP 版本。Flutter 新建项目默认生成的 build.gradle 是针对 Android 的HarmonyOS 工程结构不一样直接复用会报错。常见报错之一是You are applying Flutters main Gradle plugin imperatively using the apply script method——这个报错我见了不止一次。字面意思是要求不要用apply script的方式引入 Flutter Gradle 插件而应该用插件门户plugin portal声明式引入。报错信息其实已经给出了修正建议但很多人不仔细读就直接去搜解决方案了浪费时间。正确的做法是在 settings.gradle 里声明插件版本然后在 app 模块的 build.gradle 里用plugins { id(dev.flutter.flutter-gradle-plugin) }方式引入而不是apply from: flutter.gradle。Harmony6.0 的适配工程更严格这个点是必踩的。第三步排查 Gradle 与 Java 版本匹配。HarmonyOS 的开发工具链默认要求特定版本的 Java通常是 11 或 17。我最初用的是 Java 8跑 Harmony 工程时构建工具直接中断换到对应版本就好了。这一条用java -version就能确认排查成本极低但很多人第一反应是代码问题白折腾半天。章节内容直接进入。2.3 构建产物差异为什么需要关心 flutter aar 和主工程依赖方式Harmony 平台发布 Flutter 应用时和 Android 的打包方式有显著差异。Android 上你通常直接构建 APK 或 AAB 就行Harmony 上更常见的是把 Flutter 模块打成AARAndroid ArchiveAndroid 归档包然后作为依赖引入主 Harmony 工程里。这里有个关键点Harmony 应用框架和 Flutter 的集成方式决定了 AAR 能否被正确识别和加载而不是把 AAR 丢进 libs 目录就完事。我的操作流程是执行flutter build aar生成 AAR 产物把 AAR 复制到 Harmony 主工程的 libs 目录在 module 的 build.gradle 里添加implementation fileTree(dir: libs, include: [*.aar])同时在 settings.gradle 中确保 Flutter 的 embedder 依赖能被解析最后还要保证主工程里 FlutterActivity 的启动方式正确——HarmonyOS 的页面路由和 Android 的 Intent 模型不一样不能直接照搬。这个过程里最容易翻车的是第 4 步。各版本 Flutter embedder 的远程仓库配置不一样如果你发现 AAR 加了但运行时崩溃先确认 embedder 仓库是否被 Gradle 正确解析到而不是怀疑代码逻辑。说句实话这一步我当时卡了两天最后发现就是把仓库地址写错了。3. 数据层设计文章列表的异步调度与组件通信方案3.1 数据模型与“本地优先”缓存策略文章列表的数据模型相对简单我定义了一个 Article 实体包含 id、标题、摘要、封面图 URL、发布时间、阅读数等字段。模型层坚持用不可变对象所有字段 final配合 copyWith 方法方便局部更新。这个习惯在列表场景特别有用——当你只刷新阅读数、不重载整条数据时copyWith 能避免很多无意义的列表重建。网络请求封装在 ArticleRepository 里对外暴露三个核心方法fetchLatest下拉刷新用、fetchMore分页加载用、fetchFromCache本地缓存读取。数据策略是“本地优先”提示列表页启动时先读缓存立即展示然后再发网络请求。网络请求成功后更新缓存并通知 UI 刷新。请求失败时如果已有缓存则用缓存数据兜底并给出弱提示而不是直接弹错误页。这个策略对博客场景很实用。读者打开 app 的第一诉求是快速看到内容而不是等接口返回。我实测下来纯网络加载列表首屏时间约 800ms加上本地缓存先展示感知首屏时间能压到 300ms 以内。缓存落地我用了本地数据库存储 JSON 序列化后的列表数据并记录缓存时间设置过期策略比如 10 分钟内不重新写盘避免频繁 IO。内存里同时维护一份已加载的 Article 列表方便分页时做去重。3.2 Future、then 回调与微任务队列Dart 异步机制的一个高频误区数据层绕不开异步。Flutter 面试和实际开发里有个特别高频的误区就是“Future 的 then 回调是不是放入微任务队列”。这个问题网上一搜一大把答案是对但不是简单的“then 回调直接进微任务队列”。我在这里展开说一下因为理解错了真会影响你排查异步崩溃。Dart 的异步调度分两个队列微任务队列Microtask Queue和事件队列Event Queue。微任务队列优先级更高事件循环每次只处理一个事件之后会把微任务队列清空再取下一个事件。Future.then注册的回调确实默认作为微任务调度但注意如果 Future 尚未完成then 回调不会立即入队而是先挂在一个内部回调链上等 Future 完成后才通过scheduleMicrotask进入微任务队列。这个顺序在实践中非常重要。我在文章列表的刷新逻辑里写过这样一段代码Futurevoid refreshArticles() async { final articles await _repository.fetchLatest(); setState(() { _articles articles; }); }看起来没问题但如果_repository.fetchLatest()内部链路有多个 then、多个 async/await 组合实际执行顺序会受到微任务队列长度的影响。有一种隐蔽的问题如果某次刷新请求里先执行了一个耗时的同步计算再返回那么 UI 更新会被推迟到当前微任务队列清空之后表现就是下拉刷新后列表刷新的响应有明显延迟但你也定位不到具体是哪行代码慢。排查方法是用Flutter Performance面板看 timeline 里的微任务段通常能直接看到某个超长微任务的执行时间。另外如果你在 then 回调里又抛了一个未捕获的异常日志会打出开头那段[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] unhandled exception的经典格式。很多人看到这个慌得一批其实排查方向很简单打开 devtools切到 Console 面板找到最近一条红色未捕获异常的堆栈从顶层业务代码往下追。大部分情况都是异步链中某个步骤没加 catchError异常冒泡到了顶层。这里有个非常实用的建议数据层所有网络请求统一走一个带错误处理的基础方法不要在业务层散落 try/catch。我封装了一个safeRequest方法内部统一处理超时、HTTP 错误码、JSON 解析异常最后把结果包装成ResultT类型成功返回 data失败返回 error 信息。这样列表页代码干净很多也避免大量“异常从 async 缝隙溜走”的隐藏 bug。3.3 组件通信setState、Provider、事件总线的适用边界文章列表页涉及多个组件之间通信列表项里点击“点赞”要更新计数搜索框输入要触发列表过滤列表滚动到底部要通知数据层加载更多。组件通信方案不复杂但选型逻辑值得说清楚。我的原则很简单如果通信范围只在父子之间直接用回调函数或 ValueChanged不引入任何状态管理。如果多个非父子组件共享同一份数据比如列表数据和收藏状态用 Provider ChangeNotifier把共享状态提升到上层。如果某个事件是广播性质的、多个模块都要响应比如登录状态变化、主题切换才考虑事件总线。文章列表里最典型的是收藏状态同步列表页和详情页都可能修改收藏状态。我用了一个FavoriteStore extends ChangeNotifier内部维护收藏文章 ID 的 Set列表项通过 Consumer 监听。这样无论在哪一页点了收藏列表里对应项的图标都会自动刷新。组件通信有个新手常犯的错误在列表 item 的 build 方法里直接 new 一个 Provider或者在一个大 Widget 的 build 里放几百行逻辑导致任何状态变化都触发整个列表重建。我在优化阶段把列表项用const构造器 RepaintBoundary隔离收藏状态变化时只有对应 item 重建其他 item 完全不动。这个细节点在滚动性能优化里特别重要下面在 UI 部分详细展开。4. 列表 UI 构建实战从骨架屏到分页加载的完整链路4.1 骨架屏第一眼体验的隐藏加分项骨架屏不是「锦上添花」而是文章列表首屏体验的关键一环。加载数据是异步的如果页面空白 800ms用户感知就是“卡了”但如果是骨架屏过渡用户会认为这是正常的加载流程。实现上我分两套骨架首屏骨架列表加载前展示用 5-8 个灰色块模拟卡片布局封面图位置用圆角矩形标题位置两条细条正文位置三条渐短细条。分页骨架上拉加载更多时底部显示 2 个占位卡片告诉用户“数据正在来”。骨架屏动画我用Shimmer效果实现即高亮条从左侧扫到右侧再回来循环播放。但注意动画不能太频繁否则功耗高而且会干扰用户在加载完成阅读已有内容的注意力。我是用AnimationController控制每次循环间隔 600ms。有个细节值得强调骨架屏和真实数据的占位高度必须一致否则数据加载完成后会出现列表“跳动”。我在设计卡片时统一规定了封面图比例16:9、标题两行高度、正文三行高度骨架屏完全复用这个尺寸切换时肉眼几乎无感知。4.2 下拉刷新与分页加载的联动细节下拉刷新我直接用的RefreshIndicator并设置了AlwaysScrollableScrollPhysics让列表在任何情况下都能下拉包括内容不满一屏时。分页加载是手动实现的没有用第三方库逻辑如下滚动监听NotificationListenerScrollNotification监听ScrollUpdateNotification当extentAfter 200时触发加载更多。加载状态机isLoadingMore标志位防止重复请求hasMore标志位表示是否还有下一页。底部组件根据状态分别渲染“正在加载”“没有更多了”“加载失败点击重试”三种占位。这里有个联合细节容易踩坑下拉刷新和分页加载是两套数据流必须做状态隔离。下拉刷新拉到的数据是最新一页分页加载拉到的是下一页如果两者同时触发或者刷新后进行分页还没重置分页游标就会出现数据重复或漏数据。我的处理方案是下拉刷新时设置_page 1清空列表数据或者保留旧数据做覆盖请求完成后用新数据整体替换。分页加载时_page 1请求数据 append 到列表尾部。刷新过程中如果触发了滚动到底部直接忽略_isRefreshing标志位。每次成功请求后更新hasMore如果返回数据条数小于每页条数则认为没有更多。4.3 列表项性能优化const 构造、ItemExtent、图片懒加载文章列表项的性能是列表体验的核心。我做过一轮系统优化记录下最有效的几个点。第一让列表项尽可能 const。const构造在 Flutter 里会复用 Widget 实例减少重建成本。列表项如果依赖的数据不变Dart 编译期就能确定它是同一个 Widget滚动时不会因为父组件 rebuild 而导致子组件也 rebuild。我做了一个小测试一个包含 100 个 card 的列表全部 item 用 const 包装后滚动帧率提升约 18%。第二固定列表项高度。如果不追求瀑布流或可变高度强烈建议用itemExtent给列表项一个固定高度。文章列表卡片形态如果统一固定高度能让ListView.builder提前算出总滚动范围滚动性能会有明显提升如果卡片高度不统一至少给一个预估高度itemExtentBuilder也能减少布局计算量。第三封面图懒加载与缓存。图片加载用的 cached_network_image 插件它会自动做磁盘缓存和内存缓存并且支持占位图。配合width、height、fit参数避免图片解码后重新缩放造成的卡顿。对封面图我还做了降采样处理列表里只用宽度 360 的缩略图进详情页再加载原图。这个策略下列表首次滚动基本不会出现图片加载闪烁。第四RepaintBoundary 隔离。每个列表项外面包一层RepaintBoundary可以避免一个 item 的绘制变化导致相邻 item 重绘。这在收藏状态变化时会用到收藏图标变化时只有目标 item 的图层重绘其他 item 的图层不受影响。4.4 平台差异Impeller 在列表场景的实际表现前面提到 Impeller 引擎这里给出实测数据。我在同一台 Harmony6.0 设备上用同一份列表代码对比了 Skia 和 Impeller 两种引擎下的滚动帧率。测试场景是80 篇带封面图的文章卡片快速滑动到列表底部再返回顶部。指标Skia旧引擎Impeller新引擎平均帧率48 fps57 fps首次滑动最大掉帧112ms43ms卡顿次数100ms5次1次内存峰值312MB276MB数据来自 Flutter Performance 面板多次采样。关键差异不是平均帧率而是首滑掉帧。Skia 在第一次滑动时要编译大量着色器掉帧非常明显Impeller 把编译提前做完了首次滑动也顺滑。对文章列表这种“用户打开就滑动”的场景这个差异直接决定了体感和口碑。如果你的 Flutter 版本还在用 Skia而且项目以列表类为主我强烈建议升级开启 Impeller。5. Harmony6.0 适配细节生命周期、PlatformView 与发布集成5.1 生命周期管理切后台与状态恢复Harmony6.0 对 Flutter 应用生命周期的影响集中体现在设备资源紧张时的进程回收策略上。Harmony 应用在后台被挂起时Flutter 引擎默认也会被暂停如果你恢复了页面但 Flutter 侧没有正确处理AppLifecycleState的变化会出现页面恢复后列表空白或数据过期的问题。我在项目里做了三件事在WidgetsBindingObserver里监听AppLifecycleState切后台时记录当前页面路由和列表滚动位置。回到前台时如果距离上次切后台超过 5 分钟触发一次静默刷新——不发下拉刷新动画但重新请求第一页数据覆盖旧数据。如果进程被系统杀死走冷启动路径通过持久化缓存里的最后路由信息恢复页面栈。第三点尤其重要Harmony 普遍比 Android 更积极回收后台进程如果你的 app 是“打开闪一下回到首屏”而不是“恢复到之前页面”用户体感会有点莫名“丢状态”。解决方式是启动时读取本地存储的路由栈信息Navigator用onGenerateRoute按存储的栈重建。5.2 PlatformView 嵌入原生组件滚动性能与合成策略Harmony6.0 上如果列表页或详情页需要内嵌原生组件如视频播放器、地图就绕不开 PlatformView。Flutter 的 PlatformView 在 Android 上经历了从 Virtual Display 到 Hybrid Composition 的演进Harmony 的 Flutter 适配层也有类似选择。实测发现Harmony6.0 上 PlatformView 和 Flutter 内容的合成成本高于 Android 同代芯片。我的视频组件放在详情页顶部视频下方是滑动列表滑动时视频区域的渲染会拖慢帧率。处理策略是视频组件不在列表项里直接用 PlatformView而是列表项先显示封面图和播放按钮点击后跳到一个独立的视频播放页播放页里再接入 PlatformView。如果必须在列表内展示多个视频流就用延迟加载策略检测到视频所在 item 进入可视区域 500ms 后才创建 PlatformView离开可视区域就销毁避免所有视频同时渲染。这个方案不大优雅但收益实在列表滚动帧率从 38fps 拉回 55fps。如果你有类似需求建议优先考虑这个策略而不是去硬刚 PlatformView 的合成性能。5.3 发布与签名AAR 集成和权限处理Harmony 应用发布时Flutter 代码以 AAR 形式集成。前面环境部分已经提到 build aar 的流程这里补充几个容易忽略的细节签名问题Harmony 应用有独立的签名体系Flutter 的 AAR 包内不涉及 Harmony 签名但主工程 manifest 里的权限声明必须和你 Flutter 侧用到的权限一一对应。比如缓存图片需要读写存储权限如果你只在 Flutter 侧配置了但没有在主 Harmony 工程里声明运行时会静默失败。网络权限Harmony 6.0 对明文 HTTP 请求的限制比 Android 9 更严格线上环境必须全部走 HTTPS否则 Dio 请求直接报错你连日志都看不明白。开发阶段可以临时配置允许明文上线前一定要关掉。包体优化Flutter 产物在 Harmony 工程里一般以libflutter.so和 Dart 代码 AOT 编译产物存在包体比 Android 略大。建议开启--split-debug-info和--obfuscate能显著减小包体积。我在项目里符号拆分后包体从 52MB 降到 41MB效果直接可见。5.4 热词串讲Flutter AAR、Gradle 插件、组件通信——一次说清最后把这些关键词整合一遍因为它们贯穿了整个开发过程flutter aar发布形态附带flutter build aar的产物配置和工程集成方式是 Flutter 模块化进入原生工程的必经之路。you are applying flutters main gradle plugin imperatively using the apply script method构建配置阶段最常见的报错声明式插件引入替代命令式 apply很多既有的 Flutter 工程在升级后都会遇到。e/flutter (31173): [error:flutter/runtime/dart_vm_initializer.cc(41)] unhandDart VM 初始化未捕获异常本质是异步链异常冒泡到顶层排查要看堆栈而不是被路径吓住。flutter impeller渲染引擎升级列表场景首滑性能明显改善已在新项目中默认开启。flutter platformview嵌入原生组件的桥梁Harmony 上注意滚动性能能不放列表就不放列表。flutter组件通信按通信范围选择方案父子用回调、共享状态用 Provider、广播事件用事件总线不要一上来就套框架。flutter future的then回调是放入微任务队列吗是的但要在 Future 完成后才入微任务队列理解和应用微任务优先级是排查异步延迟的关键。flutter下拉刷新RefreshIndicator 分页加载状态隔离保证刷新和分页不打架。flutter面试题/面试宝典列表渲染优化、异步调度、组件通信、PlatformView、生命周期管理都是高频考点这篇文章基本把条目都覆盖了。还有一个容易忽略的小点flutter liveactivity。如果你是做资讯或博客类应用可以考虑在 Harmony 上利用它的系统能力把“最新文章更新”做成实时活动用户不用打开 app 就能看到新文章提醒。这块我没有完整做完但初步验证了 Flutter 侧通过 MethodChannel 调用系统能力是可行的也算给这个项目留了个后续扩展方向。6. 实测数据复盘与后续优化方向6.1 优化前后的实测对比文章列表区域最终的性能数据我汇总成一个表格指标优化前优化后首屏时间有缓存860ms290ms首屏时间无缓存1.2s800ms滚动平均帧率48fps57fps列表最大掉帧200ms43ms包体积52MB41MB弱网加载失败率模拟35%8%这些优化不是单一手段的结果而是从缓存策略、重复请求拦截、图片降采样、列表结构优化、引擎升级等多个维度叠加后的效果。每个单项单独做的提升可能只有 10%但合在一起就是质变。6.2 从这次项目中沉淀的三条通用经验第一列表页优化的顺序很重要。先看数据层缓存、请求合并再看渲染层Impeller、const、itemExtent最后才到微优化图片压缩、动画细节。很多人一上来就调 item 布局却忽略了缓存和引擎这两个大头效果事倍功半。第二HarmonyOS 适配要从工程配置阶段就介入。不要等代码全写完了再拿到 Harmony 设备上跑否则 Gradle 配置、签名、权限这些环境问题会和业务 bug 混杂在一起排查起来非常痛苦。我这次的经验是第一天就把空壳工程在 Harmony 设备上跑通后续每加一个功能都立刻验证成本是最低的。第三异步异常要从源头拦截。文章列表里 80% 的隐藏 bug 都来自异步链里的未捕获异常。如果你是新项目强烈建议在项目入口就挂一个全局的FlutterError.onError和PlatformDispatcher.instance.onError把所有未捕获异常统一记录下来并打桩到线上监控平台。这样用户反馈问题时你能直接定位到具体业务代码的异常堆栈省下的排查时间远超实现成本。最后再说一个这把实操下来我觉得最值回票价的细节缓存优先策略。博客资讯类应用的用户耐心真的有限断网环境下如果列表页直接弹错误页用户大概率直接划走但如果保留上次成功加载的数据并正常展示用户不会感知到数据是旧的等网络恢复再悄悄更新。这个体验差异是我这个项目做完之后最想强烈推荐给同行的做法。