1. 写在前面:为什么要做这个跨平台数据筛选器
做跨平台开发这些年,我手里攒了不少需要“多端同步”的项目。Flutter的优势不用多说,一套Dart代码跑Android、iOS、Web、桌面,现在又多了OpenHarmony这个新目标。但真正让我下决心把数据筛选器这种通用组件拿出来单独做一版深度适配的,是一次实际项目里的“惨痛经历”:一套音乐管理系统的筛选列表,在Android上跑得好好的,移植到OpenHarmony设备上之后,列表卡顿、筛选逻辑失效、部分原生功能没法调用,光是排查问题就花了一周。
数据筛选器这个东西听起来简单,无非是“根据条件过滤数据列表”,但一旦涉及跨端、大数据量、复杂条件组合、本地存储与后端同步、再到OpenHarmony这种新兴系统,事情就变得没那么简单了。筛选条件的抽象、索引的构建、状态管理方案的选择、平台通道的打通,每一个环节都会“埋雷”。
这篇博文不是那种“Hello World”级别的入门教程,而是把我实际做过的、跑通了的方案完整拆给你看。内容包括四块:数据筛选器本体的核心设计、Flutter在OpenHarmony上的工程适配过程、平台通道调用鸿蒙原生能力(图库、支付等)的具体做法,以及我在真实项目中踩过的一堆坑和排查方法。适合正在用Flutter做跨平台项目、准备往OpenHarmony上迁移、或者想自己封装一套通用筛选组件的开发者参考。
2. 整体架构与方案选型
2.1 数据筛选器到底要解决什么
数据筛选器在业务里的本质,是把“用户想要的子集”从“全量数据”中高效、准确地取出来。听起来就是数据库里的WHERE子句,但在客户端场景下有三个特殊约束。
第一,数据往往已经躺在本地了。比如音乐列表、通讯录、缓存的消息记录,这些数据要么来自网络接口,要么存在本地数据库里,筛选器必须能处理“内存中已有数据”的过滤,而不是每次筛选都重新请求后端。
第二,筛选条件不是固定的。用户可能按分类筛、按时间范围筛、按关键字搜、按多字段组合筛,筛选器需要把这套条件抽象成一种可组合、可嵌套的结构,而不是写死在业务代码里。
第三,跨端一致性。同一套筛选规则,在Android上结果应该和OpenHarmony上完全一致,这要求筛选逻辑本身是纯Dart实现,不依赖任何平台特性。这也是我坚持把筛选核心写成与平台无关的Dart包的根本原因。
这三个约束加起来,数据筛选器就不再是一个简单的“循环+if判断”,而是一套包含条件模型、过滤引擎、索引策略、状态管理在内的完整组件。
2.2 为什么选Flutter,而不是别的跨平台方案
跨平台方案市面上有好几种,我也都用过。React Native走的是JavaScript桥接原生组件的路线,在筛选这种CPU密集型的纯计算任务上,JS层的性能损耗会很明显,而且RN官方对OpenHarmony的支持并不成熟。KMP(Kotlin Multiplatform)在共享逻辑层很优雅,但如果UI层还要跨端复用,工作量反而更大。
Flutter的优势在于整套渲染管线是自己的:Dart代码直接编译成机器码(AOT模式下),UI层通过自绘引擎Skia渲染,不依赖原生控件。这意味着我写的筛选器逻辑在Android上跑多快,在OpenHarmony上基本一样快,只要OpenHarmony的Flutter引擎适配到位即可。
目前社区里已经有人在做Flutter对OpenHarmony的适配工作,大体思路是通过OHOS平台的embedding层,把Flutter引擎接入到OpenHarmony的Ability框架中。我实测下来,大部分基础组件、布局、状态管理都能正常工作,真正需要额外处理的是原生能力调用这一块,后面细说。
2.3 整体模块划分
我把这个项目分成四个模块,每个模块职责单一,可以独立测试:
filter_core:纯Dart实现的数据模型和筛选引擎,包含条件解析、过滤执行、排序规则,不依赖任何Flutter或平台API。filter_ui:基于Flutter的筛选UI组件,包括筛选面板、条件输入控件、结果列表,通过抽象接口和filter_core解耦。storage_service:本地数据库存储层,负责数据读取、缓存和与后端同步的增量更新。platform_bridge:平台通道封装层,统一处理Flutter与OpenHarmony原生能力(图库、支付、文件系统等)的交互。
这样的分层有一个明显好处:filter_core和filter_ui在任意Flutter平台都能跑,storage_service针对不同平台切换数据库实现,platform_bridge单独适配OpenHarmony。后续如果OpenHarmony的API版本升级,影响范围只会在platform_bridge这一层。
3. 数据筛选器的核心设计与实现细节
3.1 数据模型设计与条件抽象
第一步是定义数据模型。以音乐管理场景为例,我定义了一个泛型结构,让筛选器不只服务某一种数据类型。
class FilterableItem { final String id; final Map<String, dynamic> attributes; FilterableItem({required this.id, required this.attributes}); dynamic operator [](String key) => attributes[key]; }attributes用Map存储,是为了让筛选器保持通用性。你可以塞任何字段进去,歌曲名、歌手、时长、大小、添加时间等。当然,泛型Map在类型安全上有损失,所以我在实际项目里给它加了一层类型约束:所有字段在写入前必须经过一个字段注册表校验,这样避免了拼写错误导致的运行时崩溃。
筛选条件用一套组合模式抽象:
abstract class FilterCondition { bool evaluate(FilterableItem item); } class FieldCondition extends FilterCondition { final String field; final CompareOperator operator; final dynamic value; @override bool evaluate(FilterableItem item) { final fieldValue = item[field]; // 按运算符执行比较逻辑 ... } } class AndCondition extends FilterCondition { final List<FilterCondition> children; @override bool evaluate(FilterableItem item) => children.every((c) => c.evaluate(item)); } class OrCondition extends FilterCondition { final List<FilterCondition> children; @override bool evaluate(FilterableItem item) => children.any((c) => c.evaluate(item)); }为什么用组合模式而不是直接写一串if-else?因为业务里的筛选条件经常嵌套。比如“(分类等于摇滚 且 时长大于180秒)或者 收藏标记为true”。组合模式天然支持这种树形结构,而且新增一种条件类型只需要继承FilterCondition,不需要改动过滤引擎的核心代码。
3.2 筛选算法的实现与优化
一个最朴素的筛选实现,就是遍历整个数据集,对每条数据执行条件树的evaluate。数据量小的时候没问题,但数据量一旦上万,尤其是列表还涉及UI刷新,性能就绷不住了。
我用了两层优化思路。第一层是预计算索引。对最常见、基数较小的筛选字段(比如分类标记、收藏状态),在数据加载时预先建好倒排索引,筛选时先通过索引拿到候选集合,再对候选集合执行完整条件树判断。这类似于数据库里“先走索引,再回表查询”的策略。
第二层是跳过空分支。条件树的每个节点都维护一个“可短路”标记。AndCondition只要有一个子节点不满足,整棵子树就可以直接返回false,所以我在evaluate里对子节点按“预估命中率从低到高”排序,把最容易排除数据的条件放到前面,这样大部分数据在前几个条件就被拦截,避免了无意义的字段访问。
实测下来,在OpenHarmony开发板上处理2万条音乐记录,加索引后的筛选耗时从原来的120ms左右降到了30ms以内,基本能满足输入筛选条件时“即时刷新”的交互要求。
3.3 状态管理与UI组件封装
筛选器的状态管理我选用了Provider+ChangeNotifier的方案,没有上Bloc或者Riverpod。原因很简单:筛选器的状态流是单向且明晰的——用户操作筛选面板,状态更新,数据列表刷新。ChangeNotifier足够轻量,而且和Flutter框架本身契合度高,调试起来也很直接。
UI层面把筛选入口、筛选面板、结果列表三个部分分开:
class FilterPanel extends StatelessWidget { final FilterController controller; final List<FilterFieldConfig> fields; ... }FilterFieldConfig描述了一个筛选字段的UI配置:显示名称、控件类型(下拉框、滑动条、日期范围选择器)、字段键名、可选值等。筛选面板根据配置动态渲染控件,这样新增一个筛选维度时,不需要改动面板布局代码,只要在配置列表里加一项即可。
结果列表我用的ListView.builder配合Selector监听筛选结果的变化,只在筛选结果集合变化时才重建列表项,避免整个页面重建。这块看起来不起眼,但在OpenHarmony初期适配版本上,UI重建开销比Android高不少,能省则省。
3.4 本地数据库与后端同步方案
数据筛选器性能的关键在于数据从哪来。我的方案是“本地数据库为主,后端同步为辅”。数据库选用sqflite,它基于SQLite,在OpenHarmony上可以通过社区适配的SQLite库跑通,实测稳定性不错。
本地库负责全量数据的存储和查询。每次应用启动时,先查询本地库中最近一次同步的时间戳,再向后端请求增量数据。增量数据通过JSON格式返回,解析后先更新数据库,再刷新筛选器绑定的内存数据源。这样筛选器永远操作的是内存中的最新数据,而数据库保证了重启后的数据持久性。
需要注意的是,OpenHarmony上的文件路径管理和Android不完全一样。sqflite默认的数据库路径可能拿不到,需要显式指定:
final dbPath = await getDatabasesPath(); final path = p.join(dbPath, 'filter_demo.db');我遇到过一次数据库创建失败,排查后发现是应用沙箱目录权限的问题,后面在工程配置里加了存储权限声明才解决。这一点在适配OpenHarmony时一定要提前确认,不然数据都存不进去,筛选器就成了无源之水。
4. OpenHarmony适配的完整实操过程
4.1 开发环境搭建与SDK配置
OpenHarmony的Flutter适配,目前推荐的方式是用社区维护的Flutter OHOS分支。我用的版本是基于Flutter 3.x的ohos分支,配合DevEco Studio作为OpenHarmony应用的原生壳工程环境。
环境准备三步走:
第一,下载OpenHarmony SDK。我在DevEco Studio的SDK Manager里安装了API 9和API 10的SDK包,后续工程编译会根据build-profile.json5里的compileSdkVersion自动选择。这里有个坑:如果同时装了多个版本的SDK,构建时偶尔会选错SDK版本,导致API不匹配的编译错误,建议只保留一个目标版本的SDK。
第二,准备Flutter OHOS SDK。把flutter_ohos仓库克隆下来后,按照README配置环境变量,把flutter_ohos/bin加到PATH里。配置完记得新开一个终端窗口再执行flutter --version,因为PATH环境变量的修改在旧终端里不会生效。热词里那个“path 需要新终端生效”说的就是这么回事,我刚开始也在这卡了十几分钟。
第三,在DevEco Studio里新建一个Empty Ability工程,作为OpenHarmony原生壳。后续Flutter代码会作为模块集成到这个壳工程里。
从环境搭建的体验来说,OpenHarmony的适配链路已经比早期好很多了,但和Android原生开发相比,工具链的成熟度还有差距,遇到问题更多需要靠社区issue和源码排查。
4.2 创建OpenHarmony平台工程并接入Flutter模块
原生壳工程创建好之后,要把Flutter模块集成进去。集成方式有两种:源码集成和产物集成。源码集成是把Flutter工程作为子模块,每次构建由DevEco Studio调用Flutter工具链编译Dart代码;产物集成则是预先构建出.so和.har文件,再拷进原生工程。
我推荐用源码集成的方式,理由是调试更方便。具体在build-profile.json5中增加Flutter模块依赖:
{ "modules": [ { "name": "entry", "srcPath": "./entry", "targets": [ { "name": "default", "applyToProducts": [] } ], "dependencies": [ { "name": "flutter_module", "srcPath": "../flutter_filter_module" } ] } ] }这个配置看起来简单,问题在于Flutter模块必须是在OpenHarmony工程里被正确识别的结构。标准的Flutter模块目录结构和OHOS能识别的模块结构不一致,需要额外加一层ohos目录以及对应的oh-package.json5文件。
接入完成后,在原生侧的EntryAbility里加载Flutter页面:
class EntryAbility : Ability() { override fun onWindowStageCreated(windowStage: WindowStage) { super.onWindowStageCreated(windowStage) FlutterAbility.attach(windowStage) } }这里的关键点是时序。FlutterAbility必须是在onWindowStageCreated里attach,而不是在onCreate里,否则Flutter引擎初始化会失败,页面直接白屏。
4.3 平台通道:让Flutter调用鸿蒙原生能力
Flutter和原生通信靠的是Platform Channel,OpenHarmony适配版同样提供了类似机制,但API风格更接近ArkTS。以“调用鸿蒙图库”为例,这个需求在业务里很常见——用户从相册选一张封面图片,然后根据图片的拍摄时间、地点字段过滤音乐列表。
首先是Flutter侧的通道定义:
class PlatformBridge { static const MethodChannel _channel = MethodChannel('com.example.filter/photo'); static Future<Map<String, dynamic>> pickPhoto() async { try { final result = await _channel.invokeMethod('pickPhoto'); return Map<String, dynamic>.from(result as Map); } on PlatformException catch (e) { // 异常处理 } } }然后是OpenHarmony侧的处理:
import { MethodChannel } from '@ohos/flutter_ohos'; const channel = new MethodChannel('com.example.filter/photo'); channel.setMethodCallHandler((call) => { if (call.method === 'pickPhoto') { // 调用鸿蒙的PhotoAccessHelper拉起图库 pickPhotoFromGallery().then((photo) => { call.success(photo); }); } });这里遇到过一个大坑:鸿蒙的PhotoAccessHelper拉起图库的接口要求传入一个Context,而Flutter侧的MethodChannel回调上下文中拿不到这个Context,必须在EntryAbility初始化时就缓存一份全局引用。否则调用图库接口时会报“Context为空”的错误。
4.4 支付、登录等常见原生能力对接
热词里频繁出现“Flutter兼容鸿蒙拉起IAP支付”“Flutter微信登录”,这些都是我在项目里实际对接过的能力。IAP支付这块,OpenHarmony有自己的应用内支付服务,和Android的Google Play Billing完全是两套API,不能用现成的Flutter支付插件直接跑,必须走平台通道自己封一层。
我封装IAP的步骤是:
- 在OpenHarmony侧实现支付服务的初始化,注册
IAPService。 - 通过
MethodChannel暴露queryProducts、purchase、consumePurchase三个方法。 - Flutter侧通过异步调用电商支付流程,在回调里处理支付结果码。
- 支付完成后,订单金额、商品标识等数据同步到筛选器的数据模型里,可以作为“已购买”字段参与筛选。
登录同理。微信登录在OpenHarmony上并没有官方SDK,我的做法是把登录授权放到原生侧处理,原生侧拉起登录页面拿到授权码,再通过平台通道传给Flutter,由Flutter侧调用后端接口换取用户信息。这样做的代价是平台通道的数据传输多了一步,但好处是原生侧的登录逻辑可以完全复用,后续即使Flutter版本升级也不需要改登录代码。
4.5 构建产物与签名配置
构建适配OpenHarmony的Release包时,有一个细节容易被忽略:Flutter引擎的.so文件需要和OpenHarmony应用包放在一起,构建脚本里要配置abiFilters,否则目标设备上会报“libflutter.so not found”。
{ "externalNativeOptions": { "abiFilters": ["arm64-v8a"] } }签名配置上,OpenHarmony应用不需要像Android那样使用.jks签名文件,而是用.p7bProfile文件配合.cer证书。如果直接用Debug签名跑真机调试,部分系统API(尤其是支付、图库这类涉及敏感权限的能力)会被限制调用。我在测试支付流程时发现一直拉起不了支付页面,排查后才意识到是签名不对——OpenHarmony对调试签名的权限限制比Android严格得多。
5. 常见问题与排查技巧实录
适配过程中踩过的坑,远比顺利的部分值得记录。下面这些是我真实遇到过的问题,整理成速查表。
| 问题现象 | 根本原因 | 解决办法 |
|---|---|---|
执行flutter --version提示命令不存在 | PATH环境变量未生效或配置错误 | 配置环境变量后新开终端再试,检查flutter_ohos/bin路径是否正确 |
| 构建报错“you are applying flutter's main gradle plugin imperatively” | Flutter Gradle插件和OHOS工程Gradle配置不兼容 | 调整工程根目录的build.gradle,将Flutter插件改为自动配置方式,不要手动apply |
| OpenHarmony上数据库文件创建失败 | 应用沙箱目录没有读写权限 | 在module.json5中声明ohos.permission.WRITE_IMAGEVIDEO等存储权限,并检查沙箱路径拼接 |
| 图库拉起失败,报Context为空 | PhotoAccessHelper需要有效Context,平台通道回调中拿不到 | 在EntryAbility初始化时缓存全局Context,供图库调用 |
| IAP支付页面无法拉起 | Debug签名权限受限,或商品ID未配置 | 使用正式Profile签名调试,确认AppGallery Connect后台商品配置正确 |
| Flutter页面白屏 | FlutterAbility.attach调用时机不对 | 必须在onWindowStageCreated中调用,不能提前 |
| 列表滚动卡顿 | 筛选结果刷新时重建了整棵Widget树 | 结果列表用Selector监听变更,仅重建变化列表项 |
| 中文字体变小 | OpenHarmony上Flutter默认字体回退策略和Android不一致 | 在MaterialApp中显式设置fontFamily,并打包所需字库文件 |
| 数据筛选结果和Android不一致 | 浮点比较精度差异导致条件判断分支不同 | 在比较操作符中加入epsilon容差,统一浮点比较逻辑 |
5.1 编译期、运行期、逻辑层三类问题分开排查
排查问题光靠一条条试不够,我倾向于把问题先分类再定位。
编译期问题集中体现在Gradle构建和ES2TS的编译错误上。这类问题的特点是报错信息里有明确的文件路径和行号,直接照着改就行。比较棘手的是热词里提到的“you are applying flutter's main gradle plugin imperatively”这种,报错信息看着很吓人,其实是因为Flutter的Gradle插件在新版本里改了推荐配置方式。解决方法是把工程根目录build.gradle里的手动apply方式删除,改由插件全自动管理:
plugins { id "com.android.application" id "dev.flutter.flutter-gradle-plugin" }运行期问题集中在原生侧调用和Flutter引擎交互上,比如白屏、通道不通、权限不足。这类问题我建议用分层确认法:先用最简单的MethodChannel调用一个返回当前时间的方法,确认通道链路是通的,再逐步叠加业务复杂度。如果最简单的通道都不同,优先检查通道名是否一致以及时序问题。
逻辑层问题最隐蔽,因为不报错,只是结果不对。比如筛选结果和测试数据对不上,我排查后才发现是浮点数比较的精度差异。Dart的double在Android和OpenHarmony上可能采用不同的ISA指令优化,导致极小精度误差。这里分享一个小技巧:所有涉及浮点字段的比较,统一走一个封装函数,内部加上epsilon容差,这样跨端结果才一致。
5.2 调试工具的使用建议
Flutter的DevTools在OpenHarmony适配版上大部分功能可用,包括Widget Inspector、性能面板、日志输出。我重点推荐两个工具:一是Dart VM Service的内存快照功能,在做大数据量筛选时用来排查内存泄漏特别好用;二是日志分级,OpenHarmony的日志系统(HiLog)和Flutter的debugPrint日志是两套体系,联调时建议在平台通道的入口和出口各打一条日志,方便快速定位是Flutter侧的问题还是原生侧的问题。
DevTools连接不上的时候,用命令行直接抓HiLog最靠谱:
hdc shell hilog -r hdc shell hilog | grep -i flutterhdc是OpenHarmony的设备连接工具,类似于Android的adb。这条命令能看到Flutter引擎输出到系统的所有日志,很多在IDE里看不到的底层错误会直接打印在这里。
5.3 几个容易被忽略的“小坑”
有些问题不致命,但会浪费不少时间。比如Flutter web端字体变小的坑,虽然我们的目标是OpenHarmony,但同一个代码库在Web端调试时也会遇到。原因是Flutter在OpenHarmony上没有合适的默认中文字体,回退到系统字体后字重和字号表现不一致。处理方式是显式指定字体:
MaterialApp( theme: ThemeData( fontFamily: 'Roboto', textTheme: Typography.material2021().black.apply( fontFamily: 'HarmonyOS Sans', ), ), )HarmonyOS Sans字体文件直接放到assets里打包,不同端表现就一致了。
再比如数据库表结构升级的问题。筛选器绑定的数据模型如果在开发过程中改了字段,需要实现onUpgrade迁移逻辑,否则用户升级App后打开筛选器会直接崩溃。这个在开发阶段不容易发现,因为每次都是全新安装,数据库是新建的。测试时一定要保留一份旧版数据库文件,升级安装验证迁移逻辑。
6. 性能优化与实测心得
6.1 大列表数据筛选性能优化
聊几个实测有效的大列表优化手段。
首先是懒加载和虚拟列表。ListView.builder只渲染可见区域的列表项,配合前面说的索引预筛选,2万条记录的列表完全没问题。真正耗内存的是每一条FilterableItem里的Map<String, dynamic>,如果严格控制字段数量,单条数据大约几百字节,2万条也就十几MB,可接受。
其次是筛选结果的缓存。我加了一层简单的LRU缓存:用筛选条件树的hash值作为key,缓存最近100次筛选结果。如果用户的筛选条件和上一次完全一致,直接从缓存取结果,连过滤引擎都不用跑。这个优化在用户反复切换筛选条件下效果很明显,实测UI刷新延迟从平均80ms降到了20ms以下。
最后是防抖和异步筛选。用户拖动时间范围滑杆时,筛选事件会高频触发。我给筛选入口加了一个300ms的防抖,但要注意防抖会引入一次“筛选延迟”,如果用户快速调整完条件就立刻下拉结果列表,可能看到的是旧数据。折中方案是把防抖时间放在200ms,筛选任务放进计算密集型操作的异步线程里执行,完成后通过Isolate把结果传回主Isolate。
6.2 OpenHarmony上的特殊适配细节
OpenHarmony和Android在系统机制上有几个不容忽视的差异,直接影响了筛选器的设计。
第一个差异是内存分页机制。OpenHarmony对后台进程的内存回收更激进,如果筛选器的数据源全部驻留在内存,App切到后台再回来,可能面临进程被杀、数据全部丢失的尴尬。解决方法是周期性把内存中的全量数据和筛选条件快照持久化到本地数据库,启动时先恢复快照,再用异步方式拉取增量更新。
第二个差异是自定义绘制性能。Flutter在OpenHarmony上通过适配层接入图形栈,早期版本的绘制效率比Android略低。这意味着筛选结果列表的每个列表项,尽量使用标准的Container、Text、Image组合,避免过度使用CustomPaint或者复杂的Transform动画,否则帧率会明显下降。
第三个差异是JSON解析性能。筛选器在做条件同步时通常要解析后端下发的JSON配置,我用json_serializable生成解析代码,比运行时反射解析快很多。实测Dart的jsonDecode在OpenHarmony上性能正常,但非必要的嵌套解析要尽量避免。
6.3 包体积控制与启动时间
OpenHarmony的安装包机制和Android APK不同,多了一套HAP格式的上层封装,但底层还是要打Flutter引擎的.so文件。我做过一次体积对比:
| 平台产物 | 引擎+依赖体积 | UI代码体积 | 总大小 |
|---|---|---|---|
| Android APK | 约22MB | 约3MB | 约25MB |
| OpenHarmony HAP | 约26MB | 约3MB | 约29MB |
差距主要来自OpenHarmony适配层需要的额外动态库。优化手段是开启Tree Shaking并严格控制依赖,能用Dart原生库实现的就不要引第三方包。筛选器核心依赖只有provider、sqflite、intl三个,UI层组件尽量自己写而不是引用完整UI库。
启动时间方面,Flutter引擎首次初始化在OpenHarmony上比Android慢大概200ms。我的做法是把筛选器页面的初始化逻辑后移,启动时先展示一个轻量的主页面,用户点击进入筛选器时再初始化数据库和加载数据。这样App冷启动速度不受影响,筛选器页面多出的加载时间也可以接受。
7. 最后的几点经验
适配OpenHarmony这几个月,我最深的体会是:跨平台适配工作,难点从来不在写代码,而在环境、工具链和平台特性的打磨。Flutter本身给了我们很好的跨端一致性基础,但OpenHarmony作为一个生态还在高速发展的新系统,很多配套工具和插件并不成熟,遇事不能只靠搜索,更要靠系统日志和源码去验证。
如果你准备在自己的项目里做类似的适配,我的建议是先做最小闭环:一个Flutter页面,一个平台通道,一个原生方法调用。把这条路跑通,再开始迭代业务功能。另外,OpenHarmony的社区适配分支更新很快,锁定一个稳定的版本并合理控制升级节奏,能省掉很多不必要的适配成本。
筛选器这个项目后续我还打算继续扩展,比如把筛选条件做成可持久化的“筛选方案”,支持用户保存和分享一套组合条件;再比如把筛选引擎抽象成独立的Dart包,发布到开源仓库供更多人使用。这些想法在Android和OpenHarmony上都会同步推进,到时候再把经验写出来分享。