把 Flutter 项目往鸿蒙上搬,最怕的不是 UI 起不来,而是藏在依赖树深处的底层库突然给你撂挑子。我最近在处理的内部项目里,核心业务层依赖了 ServiceStack 这套老牌的 Message-based 服务架构客户端,它的强类型 JSON 序列化模型、跨端请求-响应调用方式都设计得很成熟,可一旦放到鸿蒙的 Flutter 运行环境里,网络栈、证书校验、代码生成结果这些细节全都需要重新过一遍。这篇博文就把 servicestack 鸿蒙化适配的完整过程拆开讲清楚,内容包括难点分析、改造方案、关键代码和排查笔记,适合正在做 Flutter 鸿蒙化迁移、尤其是后端走 DTO 服务化架构的团队参考。
1. 这项目到底难在哪:从 ServiceStack 到鸿蒙的真实距离
1.1 ServiceStack 客户端在企业项目里的位置
很多团队一开始接触 ServiceStack,是因为后端服务定义了一套清晰的消息模型,客户端只要通过代码生成器拿到对应的强类型 DTO,就能直接调用增删改查和业务接口。和裸写 HTTP 请求相比,它的优势非常明显:请求参数和返回结果都被编译期约束住了,字段拼错、类型对不上这类低级错误可以在写代码时就被及时发现。
在企业的实际项目里,ServiceStack 客户端通常承担的是“系统性业务入口”的角色。登录态、鉴权头、公共参数、日志埋点、错误转换,这些横切逻辑往往都封装在同一个客户端工厂里。所以它不像某个工具类那样可以随时替换,而是整个业务通信层的地基。鸿蒙化时如果地基晃一下,上层所有页面和状态管理都会跟着遭殃。
我参与的这个项目原本是 Flutter 一套代码跑 Android 和 iOS,服务调用全部走 ServiceStack。鸿蒙版要求不能另起炉灶,要复用同一套业务代码,只针对鸿蒙环境做最小改造。这个约束听起来顺理成章,但真的执行起来才发现,问题并不在 Dart 层,而在于 ServiceStack 默认依赖的 dart:io 网络模型、JSON 序列化工具链和代码生成器,和鸿蒙的 Flutter 运行时之间存在不少隐含摩擦。
1.2 鸿蒙化适配的四个具体难点
第一个难点是最容易被感知的:鸿蒙的 Flutter 引擎虽然已经能跑大部分纯 Dart 代码,但它不是把所有能力都一比一搬过来了。特别是 dart:io 里的 HttpClient,在鸿蒙环境下能否稳定使用、能否正确读取系统证书、能否走系统代理,这些都需要逐一验证,不能假设“Android 能用鸿蒙就能用”。
第二个难点是证书与安全策略差异。ServiceStack 默认走 HTTPS,在企业内网里经常自建证书或使用私有 CA 签发的证书。Android 端可以通过 custom trust manager 处理,但到了鸿蒙,Flutter 层的证书校验行为和原生层并不完全相同,经常出现“Android 上正常、鸿蒙上报证书错误”的怪问题。
第三个难点是长连接场景。ServiceStack 客户端的部分增强功能会涉及 ServerEvents 或 WebSocket 推送,鸿蒙的 WebSocket 支持和 dart:io 的 WebSocket 实现并不完全一致。偶发断连、心跳失效、重连风暴在适配初期几乎天天见。
第四个难点,也是最容易低估的,是代码生成器产物。ServiceStack 的 Dart 客户端代码是由服务端元数据生成出来的,通常会直接引用 dart:io、package:http 里的若干符号。这些生成代码不是手写的,不能靠 Ctrl+F 改两处就完事,因为服务端模型一更新,重新生成的代码会把你的人工修改直接覆盖掉。所以适配工作必须围绕“如何让生成代码在鸿蒙上可运行且可维护”来做文章,而不是赶工一次就完事。
1.3 一个容易被忽视的工程量来源:代码生成
这里我想展开聊一下代码生成。ServiceStack 生态里,服务端用 C# 定义请求和响应 DTO,客户端通过工具生成对应语言的强类型类。这个流程在 Android/iOS 上跑得很顺,因为目标平台对标准 Dart 几乎没有额外限制。但鸿蒙化之后,生成的代码可能会出现三类问题。
第一类是符号引用问题。部分生成代码会隐式使用HttpHeaders、HttpClient类型,这些在 Dart 侧没问题,但一旦你为了适配鸿蒙网络栈而替换底层实现,这些引用就变成了“脏依赖”——你不能直接删,因为生成器下次还会把它们写回来。
第二类是 JSON 转换的边界断言。生成器会为每个 DTO 生成fromJson/toJson,内部大量使用json['fieldName'] as String这类强转。鸿蒙侧如果因为桥接导致 JSON 里的字段类型被改写,比如服务端返回的是数字字面量、但中间某层把它转成了字符串,就会在强转时报错。
第三类是泛型与继承的处理。ServiceStack 支持继承式 DTO,生成代码里会维护一张类型注册表。鸿蒙环境里如果用到热修复或动态加载机制,这张注册表的初始化时机不对,就会出现“类型未注册、反序列化失败”的诡异异常。
所以我在项目启动时做了一个决定:把代码生成器的产物先完整生成一遍,然后做一次静态扫描,把不兼容符号单独提取到一个兼容层,而不是直接修改生成文件。这样后续服务端模型更新时,可以再次生成并自动合并。
2. 适配策略与整体架构设计
2.1 纯 Dart 优先原则:先别急着动平台层
鸿蒙适配最常犯的错,就是一听说平台不是 Android,就立刻把所有底层换成原生通道,结果工程量爆炸、问题面变大、还引入了新的线程和内存模型。实际上,ServiceStack 客户端 90% 以上的代码是纯 Dart 逻辑,包括 DTO 序列化、消息路由、结果解析、错误处理。这些逻辑本质上跟操作系统无关,理论上在鸿蒙的 Flutter 引擎里可以直接跑。
所以我在设计适配方案时第一条原则就是:先验证纯 Dart 路径能不能走通。做法很简单,先用一个最小的 ServiceStack 客户端连测试环境,调用一个无鉴权的只读接口,跑通之后再逐步加鉴权、加错误分支、加复杂 DTO。只有证明“确实是某种原生能力缺失”之后,才允许把某个环节下沉到平台通道。
这条原则最大的收益是维护成本。纯 Dart 的代码可以在所有 Flutter 平台复用,不需要为鸿蒙单独维护一份;也避免了两端实现不一致导致的隐性 bug。你想想,如果某个请求在 Android 上走 Dart 网络栈、在鸿蒙上走原生网络栈,那以后再排查线上问题就得同时懂两套链路的日志特征,想想都头疼。
2.2 必须过桥的能力:网络栈与原生授权
那是不是所有东西都可以留在 Dart 侧?不是。我在实际验证中发现,有三类能力必须考虑走平台通道。
第一类是系统级网络策略。比如鸿蒙有些机型默认断开应用自建 TLS 通道,或者需要在原生侧获取系统证书列表,这类能力和操作系统绑定得比较死,靠 Dart 层绕过不现实。第二类是原生网络栈特有的能力,比如某些内网场景必须使用鸿蒙原生网络框架才能带上特定鉴权头或走指定网卡。第三类是与系统账号体系关联的能力,比如单点登录票据需要从原生侧获取,再注入到后续请求头里。
为了不让这种“必须过桥”的能力反过来污染整个 ServiceStack 调用链,我把它封装成了可替换的传输层。ServiceStack 客户端允许我们在创建时注入自定义的 HTTP 处理逻辑,也就是说,把“用 Dart HttpClient 发请求”和“把请求转交原生侧发出”这两种行为抽象成同一个接口。业务层仍然只管构造 DTO,完全不感知请求是从哪里发出的。
2.3 整体架构分层与模块边界
最终项目里采用了三明治结构。最上层是业务代码,负责构造 DTO、发起调用、处理响应,这一层完全保持与 Android/iOS 一致,不做任何鸿蒙分支。中间一层是传输适配层,包含一套自行实现的网络覆盖器和可选的平台通道封装,这一层是鸿蒙适配的核心战场。最底层是鸿蒙系统能力,包括 ArkTS 侧的网络模块、证书与账号模块。
打个比方,ServiceStack 客户端就像一栋老房子里预埋的高级管道,出水口和龙头都是标准接口。鸿蒙化适配就是给管道换一段材质不同的接头,需要保证接头两端的接口尺寸不变,只有接头本身是重新加工出来的。这个接头的关键规格有两个:一是要能处理 ServiceStack 定义的消息格式,二是要能对接鸿蒙侧的原生网络能力。
模块边界明确之后,每个人的分工自然就清楚了。有人负责维护生成代码的兼容补丁,有人负责传输适配层的 Dart 封装,有人负责 ArkTS 侧的原生实现。最终联调时,只需要盯着传输适配层的接口协议是否对齐,不用把整个业务逻辑从头再翻一遍。
3. 强类型 JSON 序列化的鸿蒙化处理
3.1 ServiceStack 的强类型模型是怎么生成的
ServiceStack 的客户端强类型依赖一套代码生成工具链。服务端 C# 的请求/响应模型经过元数据导出后,客户端可以生成对应的 Dart 类。每个类里都包含字段声明、fromJson和toJson两个方法,以及类型注册信息。
这套东西的巧妙之处在于,IDE 和编译器看到的是真正的 Dart 类,而不是一堆字符串和 Map。所以你在写业务代码时,可以享受自动补全、类型检查、重构跳转这些静态收益。序列化时,框架通过TypeContext来动态处理泛型参数,知道List<OrderItem>里的元素类型到底是什么,并且在 JSON 字符串和 Dart 对象之间完成双向转换。
这套机制在标准 Flutter 上是成熟的,但鸿蒙化之后出现了一个微妙的问题:鸿蒙的 Flutter 引擎在某些场景下会对 VM 的类型注册做更严格的管理,或者你为了处理网络栈问题引入了平台通道,Java/ObjC/ArkTS 侧解析过的 JSON 再传回 Dart 时,类型信息会被“抹平”一层。最常见的结果就是:服务端返回"count": 5,经过某种桥接后,Dart 侧拿到的 JSON 变成"count": "5",生成代码里的as int直接抛类型转换异常。
3.2 鸿蒙上的兼容问题清单
我把自己踩过的序列化相关问题整理成了一张清单,适配时可以直接照着排查。
第一个是时间格式。ServiceStack 一般使用 ISO 8601 / RFC 3339 格式传输时间,Dart 的DateTime.parse可以处理带时区和毫秒的字符串。但鸿蒙桥接到 ArkTS 后,如果字符串被重新格式化或截断,Dart 侧可能拿不到标准格式,就可能出现FormatException。建议在传输适配层统一加上时间字段的归一化处理,不强依赖服务端原始字符串。
第二个是整数精度。服务端 C# 的long是 64 位,Dart 的 int 在原生 VM 上也是 64 位,理论上没问题。但一旦请求经过了 JS/ArkTS 上下文,比如鸿蒙侧通过JSON.parse解析过响应体,超过安全整数范围的数字就可能丢失精度。如果你的业务里有关键 ID 或金额字段,这一点必须警惕。
第三个是泛型解析。生成代码里大量使用fromJson配合TypeContext来构造具体类型的 List 或 Map 值。在鸿蒙 Flutter 环境里,如果代码压缩或混淆配置不当,类型注册表可能被裁剪,导致运行时无法找到目标类型。解决办法是在主入口显式调用一次类型注册预热函数,把关键 DTO 都注册进去。
第四个是字符编码。默认情况下 Dart 的utf8.decode没有问题,但如果鸿蒙原生侧返回的数据带 BOM 头,或者服务端接口在响应头里给了奇怪的编码,序列化工具可能无法自动纠正。这种问题藏得很深,建议在适配层对响应原始字节做一次编码探测。
3.3 时间、数值与泛型的三个坑
时间格式的坑,最典型的表现是“一个时间字段在 Android 显示正常,鸿蒙上显示 Invalid Date 或相差 8 小时”。为什么相差 8 小时?因为服务端返回的字符串带Z后缀,Dart 把它解析成 UTC 时间,展示层如果直接用本地时区渲染就会出现偏移。其实这不是鸿蒙独有,但在鸿蒙桥接场景下,有人为了迁就 ArkTS 的时间处理,先把字符串改成了不带时区的格式,结果反而破坏了 Dart 侧的正确解析。我的建议是:不要去改服务端格式,也不要试图在 Dart 侧“修复”时间字符串,而是确保所有时间字段都以标准格式原封不动地从网络层传到业务层,展示层的时区转换由业务逻辑自己负责。
数值的坑,往往出现在长整型 ID 上。某些系统里主键用雪花 ID,长度超过 JS 安全整数范围。如果这条链路完全停留在 Dart VM 内,问题不大;但如果中途经过平台通道,ArkTS 侧用了JSON.parse或JSON.stringify处理过消息体,精度就可能丢。解决思路有两个:一是长整型字段在 DTO 里声明为字符串类型,服务端负责转换成字符串;二是在平台通道传输时,禁止对 JSON 做二次解析,把原始字符串直接透传。
泛型的坑,和代码生成器的注册表有关。ServiceStack 生成的每个模型类都有对应的TypeContext注册逻辑,当类被混淆或裁剪时,TypeContext里的字符串引用会失效。适配鸿蒙时不要急着开混淆,先把类型注册表是否完整跑通作为前置条件。上线前如果一定要开混淆,务必对所有 DTO 类加 keep 规则。
4. 消息总线与跨端服务调用同步机制实现
4.1 消息流转模型回顾
ServiceStack 的核心通信模式就是 Message-based:客户端构造一个请求 DTO,通过JsonServiceClient发送到服务端,服务端根据 DTO 类型路由到对应的处理函数,返回一个响应 DTO。整个过程从业务视角看是“类型进、类型出”,但从底层视角看,就是一次 JSON 序列化 + HTTP 请求 + JSON 反序列化。
那“消息总线”体现在哪里?体现在 ServiceStack 的设计里,客户端和服务端并不通过显式的 REST 路径约定来沟通,而是通过 DTO 类型来定义契约。后端的路由注解、客户端的调用方法都围绕消息类型展开。所以只要保持 DTO 类型一致,客户端怎么传、服务端怎么收都是强约束的。
鸿蒙化之后,这个消息模型本身是不需要改的。你要适配的是传输这条消息的管道——确保 JSON 消息从 Dart 侧生成之后,能够穿过鸿蒙环境里可能存在的原生桥接层,原封不动地抵达服务端,并且把响应也原封不动地送回来。
4.2 跨端请求同步的实现思路
“跨端服务调用同步”在标题里是一个关键短语。结合鸿蒙适配的实际情况,它至少有两层含义。
第一层是跨平台端的同步:同一套 ServiceStack 客户端代码在 Android、iOS、鸿蒙上调用同一个后端服务,必须保持行为一致,包括 DTO 序列化格式、Header 拼装逻辑、超时策略、错误码映射。只有做到这一层,业务团队才能用同一套测试用例去验证所有端。
第二层是跨执行环境的同步:当请求必须经过 Dart 与 ArkTS 之间的平台通道时,Dart 侧的业务调用是一个普通的await client.post(dto),但底层可能发生了“Dart 侧组装 JSON → MethodChannel 调用原生 → ArkTS 发起 HTTP → 返回结果给 Dart”这么一条链路。此时必须保证调用方的等待语义不被破坏,请求取消、超时、异常都要能映射回 Dart 的 Future 模型。
第二层实现的关键是使用Completer。每个业务请求在 Dart 侧生成一个唯一的请求序列号,把对应的Completer存入一个 Map,然后通过MethodChannel把序列号和请求体一起发给原生侧。原生侧完成请求后,再通过同一个通道把序列号和响应体返回,Dart 侧从 Map 里取出Completer并 complete。这样就能把原生异步调用无缝包装成 Dart 的同步等待。
4.3 完整调用时序与关键代码
完整时序大概是这样的:业务层调用orderClient.getPendingOrders(),内部构造GetPendingOrders请求 DTO,交给自定义的请求分发器。分发器决定走哪条传输通道。如果走平台通道,它会先注册一个回调 ID,再通过 MethodChannel 把 JSON 消息和回调 ID 一起发给 ArkTS 侧。ArkTS 侧使用原生 HTTP 模块发送请求,收到响应后,把 JSON 响应体和回调 ID 返回给 Dart 侧。Dart 侧根据回调 ID 找到等待中的调用方,完成响应 DTO 的反序列化,最后把业务对象交给上层。
Dart 侧的平台通道封装大概长这样:
class OhosServiceBridge { static const MethodChannel _channel = MethodChannel('servicestack/transport'); final Map<int, Completer<String>> _pending = {}; int _seq = 0; Future<String> post(String url, String jsonBody, Map<String, String> headers) { final seq = _seq++; final completer = Completer<String>(); _pending[seq] = completer; _channel.invokeMethod('post', { 'seq': seq, 'url': url, 'body': jsonBody, 'headers': headers, }).catchError((e) { final cb = _pending.remove(seq); if (cb != null && !cb.isCompleted) { cb.completeError(e); } }); return completer.future; } void onNativeResult(int seq, String response) { final cb = _pending.remove(seq); if (cb != null && !cb.isCompleted) { cb.complete(response); } } }ArkTS 侧只要实现一个对等的平台通道方法即可。注意在原生侧,要避免在主线程执行网络 IO,务必使用异步请求,并且在回调里通过UiContext或者统一的返回通道回传结果。精简代码如下:
let httpRequest = http.createHttp(); let header: Record<string, string> = Object.assign({ 'Content-Type': 'application/json' }, options['headers'] as Record<string, string>); httpRequest.request(urlFromOptions, { method: http.RequestMethod.POST, header: header, extraData: options['body'], connectTimeout: 8000, }).then((resp) => { let result = resp.result as string; channel.callMethod('onNativeResult', [seq, result]); }).catch((err) => { channel.callMethod('onNativeError', [seq, String(err)]); });这里有一个很容易被忽略的点:ServiceStack 客户端本身有自己的请求重试、鉴权头注入和错误分类逻辑。如果你直接把原始请求抛给 ArkTS 原生网络层,这些逻辑就丢了。所以我把 ServiceStack 的 requestFilter 也集成到分发器里,在生成 JSON 消息时就把鉴权头、公共参数全部处理好。原生侧只负责发送,不负责理解业务协议。
需要提醒的是,平台通道方案一定不要做成“所有请求全部走原生”。因为通道调用本身有性能开销和线程切换成本。我在项目里加了一个可配置的开关,默认走 Dart 原生 HttpClient,只有开启了“原生网络栈模式”之后才会切到通道。这样既能满足特殊场景,又不会给常规路径增加额外负担。
5. 实操:环境搭建、验证 Demo 与埋点
5.1 鸿蒙侧 Flutter 工程配置
鸿蒙 Flutter 工程的搭建和标准 Flutter 工程有些差异。首先你得确保本地的 Flutter SDK 是支持鸿蒙目标的那套版本,创建工程时需要选择 ohos 平台。工程结构里会多出 entry 目录,里面是 ArkTS 的 UI 入口和原生逻辑。pubspec.yaml 基本沿用原来的依赖,但要注意部分纯 Dart 包如果有平台相关实现,需要检查是否提供了 ohos 端实现。
我这个项目里,servicestack 这个库本身是纯 Dart 实现的,所以在 pubspec 里不用额外调整。但如果你在适配过程中决定使用自定义的平台通道网络栈,就需要为它写一个插件登记文件,在鸿蒙工程入口调用channel.setMethodCallHandler注册处理方法。
网络权限也要专门检查。鸿蒙应用默认不会给任意域名放行网络请求,需要在 module.json5 或对应的权限配置文件里声明网络权限,并且如果是明文 HTTP 请求,还得额外处理。这个坑很典型:App 在 Android 上调试得好好的,搬到鸿蒙上发现所有请求都失败,日志里只有 SocketException,最后发现是权限没有声明。
5.2 三天内跑通调用链的验证步骤
为了验证适配是否靠谱,我建议用一个三层验证计划来推进。
第一步,验证纯 Dart 网络链路。先不接平台通道,直接用 ServiceStack 默认客户端去调测试环境。如果这一步能通,说明鸿蒙 Flutter 引擎的基本网络能力没问题,后续问题大概率出在证书、代理和环境差异上。
第二步,验证复杂 DTO 的序列化。找两三个覆盖了时间、嵌套对象、枚举、长整型字段的接口,在 Android 和鸿蒙上分别调用,把 JSON 日志导出并做 diff。这一步能发现问题最多,因为很多类型细节只有真实数据才能暴露。
第三步,验证平台通道链路。打开“原生网络栈模式”,让请求改走 ArkTS 网络模块。重点测试返回体的完整性、大报文传输、并发请求数量、请求超时后的取消语义。这一步稳定之后,再回到业务层做回归测试。
每一步跑完都要保留日志快照,尤其是请求体、响应体、时间戳和错误堆栈。这样后续排查问题时,可以快速定位是哪一层出了问题。
5.3 性能与稳定性埋点
ServiceStack 客户端的性能关键点不在序列化本身,而在请求链路的额外开销。鸿蒙化之后尤其要关注四个指标:DNS 解析耗时、TLS 握手耗时、平台通道消息排队耗时、JSON 解析耗时。
我习惯在传输适配层统一埋点。每次请求开始记录时间戳,经过序列化、发请求、收响应、反序列化各个环节分别打点。最后把数据汇总成一张表,线上可以观测到:常规请求在鸿蒙上比 Android 慢了多少,是不是慢在 TLS 握手,还是慢在平台通道。
稳定性方面,需要给平台通道方案加上超时熔断。如果原生侧超过 15 秒没有返回,Dart 侧就要把_pending里对应的Completer标记为超时,避免调用方永久挂起。同时要清理孤儿回调,防止内存持续增长。这属于很容易被忽略、但线上必现的隐患。
6. 常见问题与排查实录
6.1 高频问题速查表
我在适配过程中整理了下面这张高频问题速查表,基本覆盖了大多数团队会遇到的场景。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 鸿蒙上所有请求立即抛 SocketException | 网络权限未声明,或请求走了明文链路被拦截 | 检查 module.json5 权限配置,确认调试地址是否允许明文 |
| Android 正常、鸿蒙报证书校验失败 | 系统根证书列表不一致,或未正确加载私有 CA | 在传输适配层注入统一证书校验策略,确定调试和正式环境证书分支 |
| 返回 JSON 日期字段解析失败 | 时间字符串经过桥接后格式被改写 | 保持标准格式透传,不在通道层截断或本地化时间字段 |
| 长整型 ID 和预期不一致 | 数值经过了 JS 侧 JSON 处理,精度丢失 | 长整型字段改字符串传输,或在通道层禁止 JSON 二次解析 |
| 泛型 List 反序列化报类型错误 | 类型注册表被混淆或初始化不完整 | 主入口显式注册全部 DTO 类型,调整混淆 keep 规则 |
| 平台通道方法调用后无响应 | ArkTS 侧回调没有回到正确线程,或未设置 handler | 在 ArkTS 入口全局注册 MethodChannel,并统一回传线程 |
| 高频请求下 App 卡顿 | 平台通道串行阻塞,或主线程执行了网络 IO | 尽量走 Dart 网络栈,只有特殊能力才走原生通道 |
| 断网重连后长连接失效 | ServiceStack 事件流心跳未恢复 | 在适配层增加连接状态监听,断线后重建事件流并恢复鉴权 |
6.2 独家排查思路
排查这类跨端问题最忌讳的就是拿同一份日志反复猜。我的习惯是先做“两端日志时间戳对齐”。Dart 侧和 ArkTS 侧分别打时间戳,然后放在同一张时间轴上,看延迟到底出现在哪一段。有一次问题表现为“请求发出了但服务端没收到”,两边日志都对得上,最后发现是原生侧把请求体里的引号字符做了转义,服务端解析 JSON 时直接 reject。这类问题如果没有时间戳对齐,几乎不可能靠肉眼看出来。
第二个习惯是保存一份“黄金 JSON 样例”。找一个业务覆盖比较全的接口,把 Dart 侧序列化后的请求体 JSON 和服务端返回的响应体 JSON 各存一份。每当环境变更或依赖升级后,就跑一遍自动化对比,确认生成代码没有产生意外变化。这比在 UI 上点来点去效率高得多。
第三个习惯是区分“真问题”和“差异问题”。有些测试用例在 Android 上能过,在鸿蒙上报错,并不代表鸿蒙实现是错的,可能只是因为鸿蒙对时间时区、数字格式的处理更严格。面对这种差异,不要急着打补丁,先确认标准规范是什么,再决定改 Dart 侧还是改原生侧。很多时候,晚一步动手反而能改对。
第四个习惯是给平台通道加“直通开关”。调试阶段把流量实时打印出来,每一条消息都做请求/响应 ID 配对。上线后关闭详细日志,但保留开关,方便线上事故时临时开启采集。这种设计前期多花半小时,后期排查问题能省一整天。
说实话,ServiceStack 鸿蒙化这件事,八成的精力其实花在“理解边界”上。你需要搞明白哪层是纯 Dart 可以扛住的、哪层必须下沉、哪层只是被错误地当成了鸿蒙特有问题。先把这三类边界摸清,再动手改代码,你会发现真正的代码改动量并不大。
我个人在实际操作中最大的体会是:一定不要把“让它在鸿蒙上能跑”当成目标,而要追求“让同一套逻辑在鸿蒙和其他平台表现一致”。这中间包含了太多与序列化格式、超时语义、错误类型、日志粒度的细节。在这些细节上较真,虽然前期进度会慢一点,但后面的联调和上线会非常顺。
最后再分享一个小技巧:适配阶段建议单独留一个分支,把所有调试日志、平台通道开关、证书策略配置做成可动态切换的调试面板。这样不仅适配完可以顺手验证,后续服务端接口升级、DTO 模型变更时,也能快速判断是否有新的鸿蒙兼容问题冒出来。整套方案落地之后,ServicStack 这条消息链路在鸿蒙上已经稳定跑了小半年,业务侧几乎无感知,这才是鸿蒙化适配该有的状态。