
1. 鸿蒙适配下的新问题一个纯逻辑库为什么也要认真对待1.1 从“代码能不能跑”到“集成链路通不通”前阵子接到一个活儿把公司现有的 Flutter 登录注册模块整体迁到 OpenHarmony 设备上其中注册页那个密码强度校验条在 Android/iOS 上一直用的是password_strength这个 Flutter 三方库。打开需求文档的时候我心里其实在打鼓Flutter for OpenHarmony 本身还在快速迭代第三方库能不能顺利跑起来谁也说不准。等真正动手做完一遍发现这类纯 Dart 逻辑库在鸿蒙侧的适配成本远比我想象的低但因为开发范式不同坑也确实有几个。很多人一听到“适配 OpenHarmony”第一反应就是要不要改写原生代码、要不要接平台通道其实在 Flutter 鸿蒙化的场景下问题要拆成两层看。第一层是这个库本身用到了什么能力第二层才是 Flutter 引擎在 OpenHarmony 上是否完整支持了这些能力。一个 Flutter 三方库在鸿蒙上的工作方式取决于它依赖的是 Dart 标准库还是 Flutter SDK 中的平台能力。如果是前者几乎是无缝迁移如果是后者比如调用了MethodChannel、EventChannel、dart:io的文件和网络、dart:ffi或者打包了原生 aar/so那就需要为 OpenHarmony 单独走一遍适配流程。password_strength正好属于前者。它不碰 UI 组件不依赖 BuildContext甚至不依赖package:flutter/material.dart那一层整个包的根目录看下来就是纯 Dart 文件加一个 README。这种库在鸿蒙上的适配本质上是验证、集成、跑通链路而不是改代码。但“验证”这两个字听起来轻松实际操作的时候要确认的东西可不少SDK 版本对不对、依赖能不能拉下来、创建工程时有没有 ohos 目录、设备调试能不能被 Flutter 工具链识别。每一条都会卡人而且卡得毫无预兆。1.2 密码强度校验的业务定位在正式讲适配细节之前先聊聊为什么要在密码框上花这个力气。很多项目做注册功能密码校验就是一句“长度不少于 8 位”用一个TextFormField的validator就解决了。但真实业务里光靠长度判断等于没有校验。一个 8 位的纯数字密码和 8 位的大小写字母加特殊符号混合密码风险等级完全不一样代码层面却会给出同样的通过结果。如果只是通过与否那确实不需要三方库。但注册页上那种“弱 / 中 / 强”的彩色强度条以及对应的提示文案背后需要一个统一的评估规则。这个规则要是各自写各自的Android 一套正则、iOS 一套正则、Web 再一套很容易出现同一串密码在不同端上强度结论不一致的情况用户换个设备登录会发现提示完全变了个样。password_strength把这类规则集成成了现成的纯 Dart 包无论跑在哪个平台上只要 Dart 运行时一致算出来的结果就是一致的。这一点对鸿蒙适配尤其重要因为鸿蒙端的 Flutter 还没有成熟到可以容忍各个端之间行为漂移的程度能用一套确定性算法就尽量不要搞多套实现。1.3 判断三方库是否需要“真适配”的检查清单动手之前我习惯先把一个库的源码完整看一遍有一套固定的检查清单也推荐给你。不需要多高深的技巧直接在 IDE 里打开包源码目录全局搜索几个关键词就行。第一搜MethodChannel、EventChannel、BasicMessageChannel。出现任何其中一个说明这个包需要原生侧配合鸿蒙上就得确认有没有对应的 HarmonyOS 原生实现。第二搜dart:io。它包含文件、网络、进程等能力OpenHarmony 的 Flutter 引擎虽然移植了 Dart 运行时但这些系统能力不一定跟标准 Flutter 完全一致需要实测。第三搜dart:ffi和.so涉及动态库加载的包往往和 CPU 架构强绑定适配成本最高。第四打开这个包的pubspec.yaml看 dependencies 里有没有其他 Flutter 插件。一个包如果依赖了一堆插件它的适配难度不是自己决定的而是由依赖链里最复杂那个插件决定的。按这个清单过一遍password_strength结果是全绿的搜索不到任何平台通道相关代码也不碰dart:io依赖树干净。所以结论很清楚这个库在鸿蒙上大概率是能直接用的接下来的工作重心应该放在 Flutter for OpenHarmony 的环境和集成链路本身。2. 选型判断为什么 password_strength 是这类场景的合适选择2.1 库体量与依赖树分析说句实话我一开始并没有第一时间选它。当时团队内部有几个候选方案一是自研正则二是用带 UI 组件的密码强度库三是用password_strength。对比下来password_strength的源码路径非常短核心计算文件就一个全局搜索 import 之后依赖的 Dart 标准库少得可怜基本就是dart:math这一层。这意味着它的逻辑可以被完整读懂出了问题也容易定位而不是像某些重量级库一样为了改一个校验规则还要翻几层封装。自研正则的方案我直接排除了。不是说写不出来而是密码强度这类规则看着简单边界情况极多。比如连续重复字符的惩罚、常见键盘序列的处理、不同字符类别的权重分配自己写很容易漏。团队里不同人写出来的效果还不一样评审成本高后面维护的人也难受。既然现成有成熟实现没必要重复造轮子。带 UI 组件的密码强度库我也看了能力确实全但问题是它们往往把样式和逻辑绑在一起。跨端适配的时候UI 组件在鸿蒙上的渲染表现不一定和 Android/iOS 一致排查问题的时候不知道是逻辑问题还是渲染问题。而password_strength只给结果UI 完全掌握在自己手里强度条长什么样、文案怎么写都由业务侧决定反而更适合需要统一设计规范的场景。2.2 API 设计与业务集成成本这个库的调用方式是我喜欢的那种简单到不需要看文档的类型。核心入口就一个PasswordStrength.calculate传一个字符串进去返回一个结果对象里面既包含强度档位也包含统计信息。import package:password_strength/password_strength.dart; final PasswordStrengthResult result PasswordStrength.calculate(YourPassword123!); print(result.strength); // 一个 PasswordStrength 枚举值 print(result.stats.length); // 密码长度 print(result.stats.digits); // 数字数量这个 API 设计的价值在于它把“计算”和“决策”分开了。计算部分库帮你做掉决策部分比如“什么档位算通过”“什么档位提示红色”留给业务侧。对于鸿蒙适配来说这种低耦合设计也意味着不需要关心 UI 层的状态管理直接把结果映射到 ArkUI 或者 Flutter 自绘的组件上就行。我在实际开放过程中经常遇到的一件事是库本身没问题结果因为 UI 组件在不同平台的间距、颜色、字体渲染不一样导致视觉验收不过关。这个库从根源上避开了这类问题。2.3 适配前要做的一次“静态体检”选型确认之后我没有立刻往工程里加依赖而是先把这个包下载到本地做了一次静态体检。做法很简单在 pub.dev 页面下载源码压缩包解压后用 IDE 打开按上一节说的关键词全局搜索一遍同时确认pubspec.yaml里没有平台相关声明。这一步看起来很“形式主义”但很有必要。因为你往 OpenHarmony 工程里加依赖后一旦构建失败报错信息往往是编译期的 Native 错误或者是 Flutter 工具链在解析插件时的报错跟库本身的 Dart 代码关系不大。如果你提前知道这个库不碰任何平台能力那遇到这类报错时就能立刻判断问题在工具链、环境或依赖解析而不是怀疑库本身。这也是我觉得“适配鸿蒙”这件事最核心的思路不是每个库都需要改代码先搞清楚它碰了什么再决定怎么适配。顺便说一下如果你是从网上搜索“Flutter 平台插件适配鸿蒙流程”找灵感你会发现那些文章讲的都是带原生代码的插件改造要写 HarmonyOS 的 PlatformView、Channel 实现等等。这个流程对password_strength完全用不上因为它是纯 Dart 包没有原生代码可改。理解这一点能帮你省掉一半的焦虑。3. 准备 Flutter for OpenHarmony 开发环境先跑通一个空工程3.1 获取 OpenHarmony 定制版 Flutter SDKOpenHarmony 的 Flutter 支持来自 OpenHarmony SIG 维护的 flutter_flutter 仓库这个仓库和官方 Flutter SDK 的代码结构一致但针对 OpenHarmony 的构建链路做了定制。第一步自然是把这个仓库克隆到本地并把它bin目录加到 PATH 里。git clone https://gitee.com/openharmony-sig/flutter_flutter.git export PATH$PWD/flutter_flutter/bin:$PATH flutter doctor -v这里有一个容易踩的坑OpenHarmony 的 flutter_flutter 仓库分支和版本跟官方 Flutter SDK 不是一一对应的一般会有 master、develop以及带版本号的稳定分支。不要想当然地以为拉到 master 就能用一定要看仓库 README 里写的支持矩阵确认你下载的分支和你要用的 DevEco Studio、OHOS SDK 版本匹配。我见过有人直接拉 master结果 flutter doctor 直接报错排查半天才发现分支不对。另外你的机器上如果已经装了官方 Flutter SDK一定要注意 PATH 的优先级。我习惯用flutter --version确认当前生效的是哪一个避免命令行里调了半天还是官方版本。3.2 配置 OHOS SDK、开发工具与真机签名Flutter for OpenHarmony 构建的是 HAP 包需要用到 DevEco Studio 自带的那套 SDK。安装 DevEco Studio 之后要找到 SDK 所在目录并设置环境变量。不同版本的环境变量名不一样常见的有DEVECO_SDK_HOME也有新版本用OHOS_SDK_BASE的情况具体以你安装的 DevEco Studio 版本说明为准。export DEVECO_SDK_HOME/path/to/DevEcoStudio/sdk export PATH$DEVECO_SDK_HOME/ohos-sdk/$VERSION/toolchains:$PATH这里还要提醒一句OpenHarmony 设备的调试命令是hdc不是 Android 的adb两者不通用。配置好 SDK 之后先跑一下hdc list targets确认能识别到设备再往下走。真机调试还需要在 DevEco Studio 里配置签名一般用自动签名就行但这一步不配置的话后面安装 HAP 会失败而且报错信息不太友好只会告诉你“install failed”具体什么原因得自己去翻日志。3.3 创建支持 ohos 平台的最小工程环境变量配好之后新建一个 Flutter 工程。OpenHarmony 定制版 Flutter 在创建工程时支持指定 ohos 平台这一点和标准 Flutter 创建 android、ios 平台的思路是一致的。flutter create --platforms ohos --org com.example passwd_demo创建之后工程目录下会多出一个ohos目录里面是鸿蒙侧的工程结构。先用 DevEco Studio 打开这个ohos目录编译并跑通一个 hello world这个环节能验证整条链路DevEco 工具链、Flutter 引擎、Dart 运行时、设备连接是否都正常。我强烈建议不要跳过这一步直接开始接三方库。因为如果空工程都跑不通后面接任何依赖出了问题都没法判断是库的问题还是基础环境的问题。跑通空工程之后再回过头来集成password_strength心态会稳很多。4. 集成 password_strength依赖引入、调用封装与密码页改造4.1 添加依赖与镜像配置空工程跑通之后集成这个库就成了一个标准操作。在pubspec.yaml的 dependencies 里加上dependencies: flutter: sdk: flutter password_strength: ^0.2.0然后执行flutter pub get。这里要说一个国内 Flutter 开发者都遇到过的问题直接连 pub.dev 官方源经常不稳尤其在大项目里有多个依赖时卡个十几分钟是常有的事。我习惯在做鸿蒙适配之前先把环境变量配好用国内镜像源跑依赖解析export PUB_HOSTED_URLhttps://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URLhttps://storage.flutter-io.cn配置好之后再执行flutter pub get基本都能顺利拉下来。如果还是失败大概率不是网络问题而是依赖版本约束问题这个放到第 6 章的踩坑部分细说。4.2 核心调用逻辑与结果解析依赖拉下来之后在 Dart 代码里调用这个库非常直白。我一般会单独建一个password_policy.dart文件不直接在 UI 层写逻辑方便后面做单元测试。import package:password_strength/password_strength.dart; enum StrengthLevel { weak, fair, good, strong } StrengthLevel evaluatePassword(String input) { final PasswordStrengthResult result PasswordStrength.calculate(input); return _mapToLevel(result.strength); } StrengthLevel _mapToLevel(PasswordStrength strength) { switch (strength) { case PasswordStrength.weak: return StrengthLevel.weak; case PasswordStrength.fair: return StrengthLevel.fair; case PasswordStrength.good: return StrengthLevel.good; case PasswordStrength.strong: return StrengthLevel.strong; } }PasswordStrengthResult里的stats字段是调试利器。比如你想知道用户输入的密码里到底有多少位数字、多少位大写字母直接用result.stats.digits、result.stats.uppercaseLetters就能拿到。我在做 UI 文案提示的时候会用到这个数据档位是 weak 时根据具体短在哪一项给出对应的引导文案而不是只丢一句“密码过弱”体验会好很多。4.3 注册页 UI 联动实时校验条与防抖处理密码强度校验体验最好的方式是在用户输入过程中实时更新强度条。实现上很简单监听TextFormField的onChanged把当前字符串传给evaluatePassword然后根据返回的档位更新颜色和进度条。TextFormField( onChanged: (value) { _debounce?.cancel(); _debounce Timer(const Duration(milliseconds: 150), () { final level evaluatePassword(value); setState(() { _strengthLevel level; }); }); }, )这个 150ms 的防抖不是炫技是真的能提升体验。虽然这个库本身的calculate方法跑一次很快但 UI 刷新如果每次按键都触发在低端鸿蒙设备上还是能感觉到掉帧。防抖之后用户连续输入时只会在停顿的瞬间刷新一次既省资源又不会显得卡。强度条组件可以很朴素一个Container加背景色就够了。weak 对应深红色、fair 对应黄色、good 对应蓝色、strong 对应绿色这是比较通用的一套配色。如果你想做得更好看可以在强度条下面再加一个文案把stats里缺的信息告诉用户比如“建议加入大写字母和特殊符号”。5. 强度计算原理拆解知道每一分是怎么来的才能定制自己的策略5.1 从字符串到四档评估很多人在选型时会问这个库到底靠不靠谱算法会不会很傻我当时也好奇所以直接把源码翻了一遍。它内部的思路其实很直接先把输入的密码按字符类别做统计看它包含数字、小写字母、大写字母、特殊符号各多少再基于这些统计量算一个基础分最后考虑一些会拉低分数的因素比如重复出现的字符、连续上升或下降的字符、键盘上常见的序列等做惩罚性修正。最终得到一个分值再映射到weak、fair、good、strong四档。具体每个版本里各项的权重会有些调整所以我不建议把某个版本的具体权重当成永恒不变的规则接入时直接打开当前版本的源码读一遍心里才有底。但大的方向是稳定的这也是这个库的设计核心不看单一长度而看字符构成的复杂度和可预测性。理解这个原理对你的价值在于当产品经理提出“我们的密码必须达到 good 档才能提交”这类需求时你能准确判断这个库能不能满足以及需要叠加什么自定义规则才能满足。5.2 字符分类的边界问题这个库的字符分类规则是按英文环境设计的对中文、emoji、全角符号这类输入它的处理方式不一定符合直觉。比如全角数字、中文汉字、emoji 表情在这个库的统计视角下可能不会被归入“数字”“字母”“特殊符号”任何一类结果就是这些字符对强度评分的贡献被低估甚至忽略。如果你的产品主要是中文用户这是一个值得注意的边界。我的处理方式是把password_strength当成基础分再叠加一层针对 Unicode 的统计逻辑。比如把中文字符和 emoji 都纳入“特殊字符”的考量或者单独做敏感词过滤。具体怎么权衡取决于你的密码策略是面向国内用户还是全球用户没有标准答案但你需要知道这个库有这样一个行为边界。5.3 在 password_strength 之上叠加自定义策略实际业务中的密码策略往往不止“强度”一个维度。比较常见的要求包括密码不能包含用户名、不能包含连续数字如 123456、不能包含常见弱口令如 qwerty、不能与历史密码相同。这些规则password_strength大部分不管需要业务层自己实现。我的做法是在前面那个password_policy.dart文件里扩展一个统一的入口class PasswordPolicy { final int minLength; final bool forbidUsername; final bool forbidKeyboardSequence; bool validate(String password, {String? username}) { final strengthOk evaluatePassword(password) ! StrengthLevel.weak; if (!strengthOk) return false; if (forbidUsername username ! null password.contains(username)) return false; if (forbidKeyboardSequence _containsKeyboardSequence(password)) return false; return true; } }这样设计之后从 UI 到规则只走一个validate方法后续加规则也只改这一个文件。鸿蒙端和 Android/iOS 端共用的是同一份 Dart 代码不用各自写一套。6. 实测记录适配过程中踩到的坑与完整排查链路6.1 坑一pub get 卡在依赖解析上我实际遇到的一个问题是执行flutter pub get时一直停在那里像卡死了一样等半天也没有结果。第一反应是网络问题但 ping 镜像源是通的用浏览器访问也是通的这就说明问题大概率不在网络。排查链路是这样走的先执行flutter pub get --verbose把详细日志打出来发现在解析某个传递依赖时反复重试。再检查pubspec.yaml确认password_strength: ^0.2.0这个版本约束和当前 Flutter/Dart SDK 的兼容性。问题就在这OpenHarmony 定制版 Flutter 的 Dart SDK 版本不一定比官方最新版新有些新版三方包要求的sdk约束在这个环境下满足不了。解决办法有两个方向。一是把password_strength的版本往下降一个主版本或者精确锁定一个低版本二是升级 flutter_flutter 到支持更高 Dart 版本的稳定分支。我选择的是前者因为升级工具链的风险明显更大。锁定版本之后再删掉项目里的pubspec.lock和.dart_tool目录重新执行flutter pub get这次很快就成功了。6.2 坑二创建工程后没有生成 ohos 目录这个坑看起来特别蠢但真的有代表性。我一开始为了图省事直接用普通 Flutter 命令创建了一个标准工程没有指定--platforms ohos。然后拿着 DevEco Studio 去打开找半天找不到鸿蒙侧的工程目录。排查链路很简单检查创建工程用的 Flutter 工具链是不是 OpenHarmony 定制版。如果你命令行里的flutter还是官方版本那它自然不会知道你有个 ohos 平台。用flutter --version确认切换到了正确的 SDK 之后有两种补救方式。第一种是重新创建工程记得加上flutter create --platforms ohos .注意最后的点号它会在当前目录补齐缺失平台。第二种是在现有工程上手动补但步骤复杂得多不如直接重建干净。所以经验就是在开始一对 Flutter 鸿蒙适配项目之前先确认flutter命令指向的是 flutter_flutter 仓库里的那个版本。检查方法很简单运行flutter --version看版本号附近有没有 ohos 相关标识再通过which flutter确认路径。6.3 坑三flutter run 发现不了鸿蒙设备环境都配好、依赖也拉下来之后我想着直接flutter run把应用装到鸿蒙开发板上跑一下。结果flutter devices列表里空空如也一个设备都看不到。一开始我怀疑是 hdc 服务没起来用hdc list targets看了下设备明明在线。这说明问题出在 Flutter 工具链的设备发现插件上。OpenHarmony 定制版 Flutter 不是所有分支都实现了完整的flutter run设备发现和安装流程有些版本需要依赖 DevEco Studio 完成 HAP 的签名、安装和调试。所以我换了思路不用flutter run直接在 DevEco Studio 里打开ohos目录走鸿蒙原生工程的构建流程。构建完之后HAP 会直接安装到连接的设备上Flutter 侧的逻辑照常跑效果是一样的。如果你坚持用命令行可以再检查一下 flutter_flutter 仓库的 README里面一般会写明当前分支对flutter run支持到什么程度。实在不行就用 DevEco Studio 兜底这才是鸿蒙侧最稳的构建路径。6.4 版本警告看到“not known to be fully supported”怎么办适配过程中日志里会时不时冒出一句类似“The current configured Flutter SDK version is not known to be fully supported”的提示。这句话翻译过来就是当前 Flutter SDK 版本可能不在某个三方包或者工具链的支持范围内。我的排查经验是先区分它是警告还是阻塞。如果只是警告编译和运行都正常那可以先忽略但要把对应的版本号记下来等后面出问题时能有个追溯依据。如果是阻塞也就是flutter pub get或编译直接失败那就要去 flutter_flutter 仓库查支持矩阵看当前分支和 DevEco Studio 版本是否匹配。这个问题的根因往往是分支拉得太新或太旧与工具链不匹配跟代码本身没关系。找到匹配版本切换分支重新跑flutter pub get问题就消失了。7. 多端一致性验证与 HAP 产物检查7.1 同一套用例跑 Android / iOS / OpenHarmony适配完成之后最重要的一件事是验证三端行为一致。因为password_strength是纯 Dart 代码理论上结果应该一致但“理论上”和“实际上”之间需要一组用例来确认。我习惯在工程里写一个独立的 Dart 测试文件把代表性输入和期望档位列清楚然后在三个平台上跑同一套测试。测试用例我一般会覆盖这么几类纯数字短密码、包含大小写字母和特殊符号的长密码、只有小写字母的长密码、重复字符较多的密码、包含中文和 emoji 的密码。重点观察的不是某一种密码是不是 weak 或 strong而是三个平台对同一个输入的输出是否一致。如果 Android 上是 goodOpenHarmony 上变成了 fair那就说明 Dart 运行时的某些行为在鸿蒙侧有差异需要进一步排查。单元测试代码长这样import package:flutter_test/flutter_test.dart; import package:password_strength/password_strength.dart; void main() { test(password_strength consistent across platforms, () { final result1 PasswordStrength.calculate(Abc123!#); final result2 PasswordStrength.calculate(123456); expect(result1.strength, isNotNull); expect(result2.strength, isNotNull); // 具体档位按你们的产品策略断言 }); }在实际测试时我发现三端输出完全一致没有遇到正则或字符分类的差异。这也是纯 Dart 库在跨端适配上的天然优势只要 Dart 运行时是同一套实现结果就不会漂。而那些因为平台正则引擎不同、字符编码不同导致的行为差异在纯 Dart 逻辑里几乎不会发生。7.2 打开 HAP 包检查第三方库依赖跑完了功能验证还会有一个很多人忽略的检查项看一下最终打出来的 HAP 包里到底带了什么东西。打开 HAP 包之后你会发现password_strength的代码是被直接编译进 Flutter 产物的没有额外打包任何原生库、资源文件或 so 文件。这意味着它不会和鸿蒙侧的其他原生库发生符号冲突也不会无端增大包体积。这一点在鸿蒙适配里特别重要因为很多 Flutter 插件适配鸿蒙时最让人头疼的就是 HAP 里同时塞入了 Android 的 so 和鸿蒙的 so导致架构不匹配或者运行时崩溃。password_strength不存在这个问题从根源上就少了一种排查方向。我建议每位做鸿蒙适配的同事都养成检查最终产物的习惯看看包里的 lib 目录、assets 目录到底有什么很多诡异问题都能在这里找到线索。7.3 后续可以继续扩展的方向这个适配做完之后个人体验是Flutter 三方库在鸿蒙侧能不能用关键不在于是否需要改代码而在于选型时有没有搞清楚它依赖了什么。password_strength这类纯 Dart 逻辑库在鸿蒙上几乎是“白捡”的跨端能力。但业务往往不止于此如果你们公司有统一的密码策略中心可以考虑把这个校验逻辑做成可配置的能力让产品通过 JSON 下发“最小长度、必须包含字符类别、是否禁止键盘序列”等规则Dart 侧动态解析。另外密码强度计算结果也可以用来做风控埋点。比如用户注册时输入密码后强度为 weak同时又触发了其他异常行为就可以把强度值上报给后端作为风险判断的一个因子。这些都是在password_strength基础上很容易扩展的方向而且因为是纯 Dart扩展开来也不会增加鸿蒙适配成本。写到最后顺嘴提醒一句如果你们团队后续在鸿蒙侧遇到其他三方库适配问题先别急着改库源码按我说的那个检查清单把依赖关系捋一遍大概率能省掉半天到一天的排查时间。这次适配做完之后我对鸿蒙侧跑 Flutter 三方库的信心涨了不少也希望这篇记录能让你少走点弯路。