ARTICLE DETAIL

资讯详情

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

Flutter库鸿蒙化实践:从sorted_list迁移到性能优化

Flutter库鸿蒙化实践:从sorted_list迁移到性能优化 1. 为什么要把 sorted_list 搬到鸿蒙背景与选型解析1.1 鸿蒙生态下的 Flutter 现状鸿蒙生态这两年变化很快尤其是 HarmonyOS NEXT 彻底去掉 Android 兼容层之后原本在 Android 上跑得好好的 Flutter 应用突然变得无处安放。很多团队面临同样的困境Flutter 技术栈很成熟团队也很熟悉但鸿蒙设备已经摆在那了总不能放弃。好在 OpenHarmony 社区和华为官方一起推进了 Flutter 的适配目前 Flutter 的 OpenHarmony 分支已经能跑通大部分基础能力。我身边已经有团队把 Flutter 应用完整跑在了鸿蒙平板上性能和稳定性虽然说不上完美但确实已经具备生产条件。不过这里有个坑——生态里大量三方库很多还没有针对鸿蒙适配。有一些纯 Dart 库比如 sorted_list本身不涉及平台通道理论上可以直接用但实际跑起来还是要踩不少坑。这次我做 sorted_list 的鸿蒙化目的其实很朴素把 Flutter 生态里一个高性能自动排序列表库迁移到鸿蒙环境验证它的可用性同时把整个过程中需要注意的技术细节整理出来给后面做鸿蒙化迁移的同行提供一个可参考的样本。1.2 sorted_list 到底解决什么问题先聊聊 sorted_list 本身。这个名字很直白就是一个自动排序的列表。日常开发里我们经常需要维护一个始终有序的数据集合比如排行榜、价格区间、时间线消息。最笨的办法是在每次插入后调sort()但sort()是 O(n log n)数据量一大就会卡顿。sorted_list 的核心价值在于它在插入时就通过二分查找找到正确位置然后只做一次数组移动插入复杂度是 O(log n shift)。这里的 shift 是移动元素的成本在 List 实现里是逃不掉的但至少比每次全量排序快一个数量级。并且它维护了一个内部 List对外提供SortedListBuilder当内部数据变化时只会触发 ListView 中受影响部分的重新 build而不是整个列表重建。这个设计思路对于鸿蒙上的长列表场景尤其重要。鸿蒙设备形态多样有的性能很好有的则相对一般合理控制插入和 build 的开销能让列表交互保持流畅。1.3 为什么选它做鸿蒙化样本很多人问鸿蒙化迁移选哪个库做样本最有代表性我选 sorted_list 出于三个理由。第一它是纯 Dart 实现不依赖 Android 或 iOS 的原生代码这类库是鸿蒙化迁移里最理想的情况因为上层逻辑几乎不用动。第二它涉及了数据结构和 UI 交互的性能处理不好会出现卡顿、重复 build 的问题足够暴露 Flutter 在鸿蒙上的性能短板。第三它足够复杂但又不过度庞大适合作为一次性摸清鸿蒙 Flutter 开发环境和调试工具链的载体。做完之后我的体会是纯 Dart 三方库的鸿蒙化难的从来不是说改几行代码而是整个工具链、调试方式、性能分析手段都要切换一套这些细节我会在后面的章节里全部展开。2. 鸿蒙化前的准备与核心原理拆解2.1 环境搭建DevEco Studio 与 Flutter ohos 分支的版本匹配鸿蒙上的 Flutter 开发和标准 Flutter 最大的区别在于 SDK 来源。目前主流的方案是使用 OpenHarmony SIG 维护的 Flutter 分支仓库地址在 Gitee 上。第一次接触的同事经常会搞错去 flutter 官方仓库下载 SDK然后发现根本没有鸿蒙设备选项这是最常见的新手问题。我的实践步骤是这样# 克隆 OpenHarmony 的 Flutter SDK 分支 git clone -b ohos https://gitee.com/openharmony-sig/flutter_flutter.git克隆完成后需要设置环境变量让 Flutter 命令指向这个 SDK 路径。同时DevEco Studio 的版本要求比较严格我用的版本是 DevEco Studio NEXT 系列配合对应的 HarmonyOS SDK。这里有一个容易踩的坑DevEco Studio 版本和 Flutter ohos 分支之间有一个隐含的版本匹配关系如果版本不匹配编译时会报各种奇奇怪怪的错误比如找不到ohos平台或者构建工具版本不一致。我的建议是先去 OpenHarmony SIG 的 release 页面确认当前 Flutter 分支对应的 DevEco 版本再统一安装。不要在版本匹配上凭感觉否则后面每一环节都会为这个选择买单。2.2 sorted_list 核心原理从二分查找到列表维护要讲清楚鸿蒙化过程中做了什么先得理解 sorted_list 的内部机制。sorted_list 的核心是一个有序插入算法。每次插入元素时它通过二分查找定位到目标位置。这里有一细节要注意当列表存在相同元素时sorted_list 会默认将新元素插入到现有相同元素的后面。这个后插入策略是为了保证稳定性如果你实现的列表需要类似数据库索引的稳定排序语义这个默认行为正好符合需求。代码层面的核心逻辑大致如下int _insertIndex(E element, int lowerBound, int upperBound) { int lower lowerBound; int upper upperBound; while (lower upper) { final int mid lower ((upper - lower) 1); final int compare _compare(element, _list[mid]); if (compare 0) { upper mid; } else { lower mid 1; } } return lower; }这个二分查找写得很干净每次排除一半的搜索范围保证了插入位置查找是 O(log n)。找到位置后内部 List 会进行一次insert这个操作在 Dart 的 List 实现里会触发现有元素的内存移动所以复杂度里那个 shift 项是必然存在的。另一个核心设计是SortedListBuilder。它监听 SortedList 的数据变化当插入、删除、更新元素时只会对受影响的范围触发 builder 重新执行。和直接setState后全量重建 ListView 相比这种局部刷新机制在长列表场景下能省下大量的 UI 构建开销。迁移到鸿蒙后因为鸿蒙设备形态差异大同样逻辑在高分辨率屏幕上渲染的元素更多局部刷新的价值会更加突出。2.3 鸿蒙化改造的核心难点平台差异与依赖处理纯 Dart 库按理说不需要改 Dart 代码但实际迁移中还是会遇到平台差异。最主要的差异是 dart:core 和 dart:collection 在 OpenHarmony 上的 Flutter 实现里虽然尽力做到和官方一致但个别底层方法的行为可能会受 GC垃圾回收策略和内存分配器的影响。sorted_list 依赖的 List 操作在鸿蒙上经过测试重点要关注两点。第一是大量插入时的内存抖动鸿蒙上的 Dart 虚拟机对短生命周期对象的回收策略和标准 Flutter 略有不同表现为偶尔出现轻微的掉帧。第二是comparator的处理sorted_list 允许传入自定义比较器如果你的比较器内部有耗时逻辑比如字符串国际化换算在鸿蒙上的耗时会被放大插入操作会明显变慢。这两点并不是代码不兼容而是运行时行为差异需要我们在使用层面做一些调整。具体的优化方案我会在实操部分详细展开。3. 实操过程从源码到可运行的高性能列表3.1 环境搭建成功后如何验证 Flutter 与鸿蒙的连接在正式开始引入 sorted_list 之前我建议先创建一个空的 Flutter 工程跑通代码到鸿蒙设备这条路。这一步如果没做后面所有问题都会混在一起排查起来非常头疼。创建工程的命令和标准 Flutter 一样flutter create sorted_list_harmony_demo然后查看可用设备flutter devices正常情况下你应该能看到类似HarmonyOS Device的设备列表。如果看不到需要检查hdc工具是否配置好。鸿蒙的调试工具叫 hdcHarmonyOS Device Connector它和 Android 的 adb 类似但设备连接方式略有不同。常见问题是开发者模式下没有打开 USB 调试或者 hdc 工具链版本不匹配。我个人的经验是先用 hdc 单独验证设备能否连接hdc list targets等到 hdc 能看到设备再跑flutter devices和flutter run。很多同事跳过这一步最后发现 Flutter 侧找不到设备绕了一大圈才发现是 hdc 的问题白白浪费半天。3.2 引入 sorted_list 并处理 pubspec 依赖跑通基础工程后就可以引入 sorted_list 了。在 pubspec.yaml 里添加依赖dependencies: flutter: sdk: flutter sorted_list: ^0.0.4执行flutter pub get理论上它会从 pub.dev 拉取包。但这里有个鸿蒙特有的问题部分标准的 Flutter 包在拉取依赖时会默认检查 Flutter SDK 的平台支持情况而鸿蒙分支的 SDK 可能会返回一些让 pub 解析器困惑的信息。如果遇到依赖解析失败可以考虑在 pubspec.yaml 中临时改用 git 依赖指向仓库镜像这是社区常用的变通方案。sorted_list 本身的依赖非常简单就是 Dart 核心库不需要额外插件所以只要 pub get 通过基本就成功了一半。3.3 UI 层实现用 SortedListBuilder 构建自动排序列表接下来是 UI 层的实现。我们做一个简单的例子一个聊天消息列表新消息到达时自动按时间戳排序插入列表始终保持有序。核心代码如下import package:flutter/material.dart; import package:sorted_list/sorted_list.dart; class ChatMessage { final String content; final DateTime timestamp; ChatMessage(this.content, this.timestamp); } class SortedListDemo extends StatefulWidget { override _SortedListDemoState createState() _SortedListDemoState(); } class _SortedListDemoState extends StateSortedListDemo { // 按时间戳升序排列越早的消息越靠前 final SortedListChatMessage _messages SortedListChatMessage( (a, b) a.timestamp.compareTo(b.timestamp), ); final TextEditingController _controller TextEditingController(); void _addMessage(String content) { setState(() { _messages.add(ChatMessage(content, DateTime.now())); }); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(sorted_list 鸿蒙实战)), body: Column( children: [ Expanded( child: SortedListBuilderChatMessage( sortedList: _messages, builder: (context, messages) { return ListView.builder( itemCount: messages.length, itemBuilder: (context, index) { final msg messages[index]; return ListTile( title: Text(msg.content), subtitle: Text(msg.timestamp.toString()), ); }, ); }, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Expanded( child: TextField( controller: _controller, decoration: const InputDecoration(hintText: 输入消息), ), ), IconButton( icon: const Icon(Icons.send), onPressed: () { if (_controller.text.isNotEmpty) { _addMessage(_controller.text); _controller.clear(); } }, ), ], ), ), ], ), ); } }这段代码的运行逻辑很清楚消息通过_addMessage插入到 SortedList 中SortedList 自动按时间戳排序SortedListBuilder 感知数据变化并重建 ListView。由于插入采用二分查找即使消息数量达到上万条每次插入的定位成本也保持在极低水平。3.4 性能验证插入、删除、滑动三项实测代码能跑起来这只是第一层。第二层是要验证它的性能是否真的高性能。我在鸿蒙平板上做了一组实测分别在数据量 1000、5000、10000 条时测试批量插入耗时时长以及列表滑动时的帧率表现。测试结果大致如下数据量直接 List.add sort 耗时SortedList 插入耗时备注1000 条约 18 ms约 7 ms提升约 60%5000 条约 86 ms约 24 ms提升约 72%10000 条约 190 ms约 42 ms提升约 78%这个结果很说明问题。数据量越大sorted_list 的全量排序优势越明显因为它的插入定位是靠二分查找而不是全量遍历。滑动帧率方面在 10000 条数据下普通 ListView 在快速滑动时偶尔掉到 50 帧以下使用 SortedListBuilder 的版本基本稳定在 55 帧以上。这说明局部刷新起了作用没有因为数据变化触发整个列表的重新构建。注意以上数据是在我的测试环境下得出的不同鸿蒙设备的硬件差异会导致绝对值不同但相对趋势是一致的。3.5 内存分配与垃圾回收的鸿蒙特性优化前面提到过鸿蒙上的 Dart 虚拟机在处理短生命周期对象时和标准 Flutter 略有差异。实测下来如果频繁执行插入操作内存占用曲线会出现锯齿状波动这是 GC 回收新对象的典型特征。针对这个问题我做了两个优化。第一是控制比较器里的对象创建。原来的时间戳比较如果直接使用DateTime.now()并封装成对象每次比较都会产生两个新对象。优化方式是提前把时间戳转成int型毫秒值在比较器里只做整型比较避免在比较路径上创建新对象。第二是利用 SortedList 提供的removeWhere接口做批量删除而不是逐条删除。批量操作可以降低 GC 压力因为每次删除都触发内部数组搬移逐条删除会导致搬移次数翻倍。批量删除只搬移一次性能差异明显。这两步做完内存抖动明显缓解帧率曲线也平稳了很多。4. 常见问题与排查技巧实录4.1 典型问题速查表从编译失败到卡顿排查实际操作中总会遇到各种问题我把最典型的几个整理成一张速查表方便大家对照排查。问题现象可能原因解决方案flutter devices 找不到鸿蒙设备hdc 未连接或版本不匹配先跑 hdc list targets确认设备在线编译报错提示找不到 ohos 平台DevEco 版本与 Flutter ohos 分支不匹配严格按官方 release 说明配对版本pub get 解析失败Flutter SDK 平台信息影响 pub 解析改用 git 依赖或镜像仓库SortedList 插入数据后 UI 不刷新忘记用 SortedListBuilder 或 setState检查 UI 是否监听了 SortedList 的变化大量插入时偶发掉帧比较器内部创建过多临时对象将比较维度缓存为 int/string 再比较列表快速滑动时白屏闪烁ListView 没有设置 itemExtent给 ListView 设置固定 itemExtent减少构建成本包体积明显偏大Debug 模式运行使用 release 模式构建并开启 tree-shaking这张表里的每一项都是我在实际项目中踩过的坑不是从文档里抄来的理论。尤其是最后一项Debug 模式的 Flutter 包体积和性能表现是严重失真的鸿蒙真机上做性能验证一定要用 release 包否则你会得出完全错误的结论。4.2 独家避坑心得为什么你的比较器比别人的慢关于比较器我再展开多说几句。sorted_list 是一个非常诚实的库它的性能表现完全取决于你的比较器做得够不够快。如果一个设计不良的比较器在标准 Android 上只会让你感受到微小的性能下降那么在鸿蒙上就可能被放大成肉眼可见的卡顿。我遇到过这样一个场景用 sorted_list 做一个按多字段排序的任务列表比较器里对两个字符串字段做toLowerCase()后再比较。在数据量 2000 条时还能接受涨到 8000 条时插入操作明显变慢。排查后发现toLowerCase()每次都创建新的字符串对象8000 条数据的排序过程产生了海量的临时对象频繁触发 GC。解决方式是预计算排序键class Task { final String title; final String lowerTitle; Task(this.title) : lowerTitle title.toLowerCase(); }在构造对象时就把小写化的结果缓存下来比较器里直接比较lowerTitle彻底消除了重复计算和临时对象。这个优化在鸿蒙上是决定性的尤其是列表数据长时间累积后收益会越来越大。这个思路对任何使用 sorted_list 的项目都适用比较器是高频执行路径任何可以提前计算的量都不要放到比较时再算。4.3 SortedListBuilder 与局部刷新什么时候该用它还有一个常见困惑SortedListBuilder 是不是所有场景都应该用实际操作下来我的答案是视数据量和更新频率而定。如果你的列表数据量只有几十条更新频率也不高直接用setState全量重建完全没问题代码还简单。SortedListBuilder 的真正优势场景是数据量大、更新频繁且更新只影响列表局部的时候。比如聊天室消息、股票行情推送、日志实时展示这些场景下局部刷新的收益非常明显。反过来如果列表每一项的高度都不同且每次插入都可能影响前面的布局比如折叠卡片局部刷新的优势会被布局开销抵消这时候全量 rebuild 反而更省心。sorted_list 提供的是一种能力但用不用、怎么用还是要看具体业务的特点。从鸿蒙化的角度来说OpenHarmony 分支的 Flutter 目前对 widget 复用和增量布局的支持还处于持续优化中能减少 rebuild 范围就尽量减少这是鸿蒙 Flutter 应用保持流畅的一条重要原则。5. 后续还可以这样扩展sorted_list 的鸿蒙化做完之后我顺手验证了几个和它配合使用的场景发现扩展空间还挺大。一个方向是和数据库配合。比如本地数据库查询结果可以直接灌进 SortedList再利用 SortedListBuilder 在 UI 层做实时展示。数据库一次查询也好、多次增量更新也好UI 层都能保证列表始终有序且不会因为数据源是多端同步的就乱序。另一个方向是和拖拽排序交互结合。标准的 reorderable_list 在拖拽结束后需要手动更新数据顺序如果底层数据是 SortedList拖拽结束后的数据修正逻辑会简化很多。当然这里需要考虑拖拽过程中临时打破顺序的问题需要额外的状态管理但整体思路是可行的。如果你们的 Flutter 工程恰好也在做鸿蒙化我建议从 sorted_list 这类纯 Dart 的库入手先跑通一条依赖引入—真机运行—性能验证的完整链路。这条链路熟了之后再去碰那些涉及 MethodChannel 的插件会轻松很多。毕竟鸿蒙化的最大成本从来不在于改代码而在于重新建立一套对运行时行为和性能特征的感知体系。最后分享一个我在调试过程中的小习惯在鸿蒙上做性能分析时不要只看帧率要结合 DevEco Studio 自带的 Profiler 工具观察内存分配曲线和 GC 触发频率。很多时候帧率轻微的下降背后其实是内存抖动在作祟单纯看帧率是看不出根因的。用 sorted_list 这类高频插入的库时尤其要养成本能——先把比较器优化到极致再谈其他。
返回列表