最近在把一批 Flutter 老项目往鸿蒙上迁,UI 层和状态管理倒是顺利,卡得最久的是网络层。尤其是项目里重度依赖的 http_plus——这个在 Android/iOS 上稳了好几年的"增强型网络全家桶",一跑到鸿蒙设备上就开始花式报错。官方文档翻遍了,社区帖子也刷了,最后没办法,只能自己动手拆包、改适配、逐条链路的压测。整个过程踩了无数坑,但把原理弄明白之后再回头看,其实鸿蒙化的门槛并没有想象中那么高,关键是你得知道它到底在哪些环节和 Android 不一样,以及怎么搭建一个让 http_plus 能平滑运行的适配底座。
这篇文章不打算写成教条式的 API 手册,而是以我自己真实迁移 http_plus 到鸿蒙的全过程为主线,把所有环节拆开揉碎讲清楚。从环境搭建、依赖分析、网络栈替换,到 TLS、DNS、超时、连接池这些"精密链路"的细节处理,再到实打实遇到过的故障和排查手段,一次性都放出来。不管你是刚接触鸿蒙开发,还是已经在做 Flutter 鸿蒙化迁移,这篇指南应该能帮你省下不少弯路。
1. 适配前必须搞清楚的三件事:库、引擎、平台
1.1 http_plus 到底封装了什么:从 Dio 到"增强型网络全家桶"
http_plus 这个名字在 Flutter 社区通常指的不是某个单独的网络库,而是一套基于 Dio 封装出来的增强型网络方案。它把日常业务开发里最常用的那些能力全部收编了:统一的拦截器链、全链路请求日志、混合缓存策略(内存加磁盘、支持新鲜度校验)、自动重试与指数退避、请求去重与并发合并、全局取消令牌,还有一套相对统一的响应模型解析层。用一句话概括,它就是"把 Dio 从一个能发请求的壳,武装成一个适合大型 App 长期维护的通讯基础设施"。
这套库的设计思路是借鉴了 OkHttp 的拦截器链模型,每一层职责单一。比如日志拦截器只负责按照模板打印请求参数和响应耗时;缓存拦截器会先查本地缓存是否新鲜,新鲜就直接返回,不新鲜才透传请求;重试拦截器捕获 SocketException 和超时异常后按退避策略重新发起请求;合并拦截器则把同一个时间窗口内的相同请求聚合成一个真正的网络请求,其余调用方共享这个 Future。业务方只要在初始化时把拦截器按顺序拼装进 Dio 实例,剩下的事情 http_plus 全包了。
鸿蒙化之前,我对这套机制的理解停留在"能用就行"的层面。真正开始适配后才明白,http_plus 的底层链路远比表面复杂:它内部依赖 Dio 的 HttpClientAdapter 去建立真正的 socket 连接、TLS 握手、收发数据;缓存层要往磁盘写文件,就绕不开路径权限和目录规范;日志要打印网络耗时,就依赖精确的计时器;重试要靠谱,就必须能够准确识别出哪些异常是"可重试的",哪些是"业务层面的错误不能乱动"。鸿蒙平台的运行环境和 Android 差异极大,这些默认假设几乎全部需要重新验证。
1.2 鸿蒙上的 Flutter 运行机制:不是换个 SDK 那么简单
很多人以为 Flutter 跑在鸿蒙上就是把代码重新编译一遍,实际上鸿蒙上的 Flutter 是社区维护的 ohos 分支,它不是一个官方托管的普通插件,而是直接改造了 Flutter Engine 的构建目标和 runtime 接入方式。这套方案的核心是让 Dart VM、Skia/Impeller 渲染引擎、平台通道(Platform Channel)都能跑在 OpenHarmony 的 ArkTS 运行时之上,最终以 HarmonyOS 的 Ability 作为宿主容器,把 Flutter UI 渲染到鸿蒙的显示窗口里。
由于 Flutter Engine 在鸿蒙侧做了大量移植,Dart 代码层感知不到太多差异,但依然有几处关键行为变了。比如网络 IO 默认走的是鸿蒙 socket 模型,DNS 解析策略、IPv6 优先规则、连接超时语义都可能跟 Android 的 Linux 内核行为不完全一致。再比如生命周期与内存回收机制不同,App 切换后台时 FlutterEngine 的存活策略和 Android 上不一样,如果网络库里有长连接或者定时任务,容易在生命周期切换时出现异常。
这就是为什么"看起来 Dart 层什么都没改,跑到鸿蒙上却一堆毛病"。http_plus 的问题绝大多数不在 Dart 代码本身,而是它依赖的那些底层文件和网络能力,在鸿蒙分支上有着不同的表现方式。适配的思路,应该是先让 Flutter Engine 和鸿蒙运行时之间建立正确的桥接,再让 http_plus 在这座桥上稳定工作。
1.3 适配的本质:接管网络栈与权限管线
理解适配的本质,可以先做一个类比。在 Android 上,Flutter 的网络请求是 Dart 侧通过 Dart IO 的 HttpClient 发起的,最终内核走到的是 Linux 的 socket;在鸿蒙上,Dart IO 虽然也被移植了,但底层的 socket 能力来自于鸿蒙自身的网络协议栈。这两条路不完全等价,表现在 TLS 证书来源、系统代理配置、DNS 解析行为、甚至 includeSubDomains 这类安全策略上都有差异。
所以适配 http_plus 的核心思路只有一条:不要让网络库去猜鸿蒙的行为,而是由我们显式接管网络栈和权限管线。具体来说,就是为 http_plus 提供一个针对鸿蒙优化的 HttpClientAdapter 实现,或者在初始化时注入鸿蒙系统的网络配置,让每一条链路都在可控范围内。权限管线也是一样,网络请求所需的 INTERNET 权限在鸿蒙里由 module.json5 声明,而不是 AndroidManifest 里那一套,很多迁移项目死在第一步就是因为漏看了这条。
2. 搭建鸿蒙化 Flutter 开发环境:这一步千万别跳
2.1 工具链选型:DevEco Studio 与 Flutter SDK 版本配对
鸿蒙化 Flutter 开发目前最主流的方案是使用 DevEco Studio + 社区维护的 Flutter ohos SDK。需要说明的是,官方 DevEco Studio 本身并不集成 Flutter 插件,你需要额外配置基于 flutter_flutter 的 ohos 分支工具链。版本对应关系我是实测下来这样的:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| DevEco Studio | 5.x 及以上 | 支持 API 12 及以上鸿蒙应用开发,自带 hvigor 构建工具 |
| Flutter SDK (ohos) | 3.7 之后的 ohos 分支快照 | 社区仓库 flutter_flutter 的 ohos-3.x 分支,包含可用的 Flutter Tool |
| OpenHarmony SDK | 随 DevEco 自动下载 | API 12 以上,用于解析 ohos 侧的依赖和权限声明 |
| Java | 17 | hvigor 构建链路强制要求 |
安装过程最坑的一点是环境变量。Flutter 命令使用的是你自己指定的 ohos 分支 SDK 路径,不能跟 Android 官方 Flutter SDK 混用,否则 flutter create 出来的工程不会包含 ohos 目录。我建议直接用 IDE 的终端,或者把 PATH 显式指到 ohos 分支的 bin 目录下。
有一个更省事的方案:使用 DevEco Studio 自带的 Terminal 执行flutter config --enable-ohos。这条命令会告诉 Flutter Tool 你希望创建鸿蒙平台工程,之后flutter create .就会自动带出 ohos 子目录。实测多条命令的组合不如这一条来得直接。
2.2 用命令行把 Flutter 工程"长出"鸿蒙端
假设你已经有一个现成的 Flutter 工程,想要增加鸿蒙平台支持,最干净的方式不是全部重建,而是让工程自己"长出"一个 ohos 目录出来。操作顺序如下:
flutter config --enable-ohos flutter create . --platforms=ohos第一行命令开启 ohos 平台,第二行会扫描当前工程,补全 linux、windows、macos 之外的 ohos 工程目录。生成的 ohos 目录内部结构和原生 Android 工程完全不一样:它包含 AppScope、entry 目录和若干 hvigor 配置文件。entry 里是实际的 ArkTS 代码和 resources,Flutter 引擎以依赖形式被引入到 module 中。
执行完这两条命令后,工程文件树尾部大致是这个形态:
项目根/ ├── ohos/ │ ├── AppScope/ │ ├── entry/ │ │ ├── src/main/ │ │ │ ├── ets/ │ │ │ ├── resources/ │ │ │ └── module.json5 # 权限、应用配置 │ ├── build-profile.json5 │ ├── hvigorfile.ts │ └── oh-package.json5 ├── pubspec.yaml └── lib/ohos目录的引入只是第一步,真正需要关心的是 module.json5 里的网络权限声明和 oh-package.json5 里的鸿蒙侧依赖。这两个文件一旦配置错,后续所有网络请求都会以极其诡异的方式失败。
2.3 oh-package.json5 与权限声明:第一个坑往往在这里
把 Android 上的 http_plus 工程迁移到鸿蒙,第一个爆雷的几乎都是权限。在 Android 上你早就习惯了在 AndroidManifest.xml 里写一句 INTERNET 权限然后忘掉它,但鸿蒙的权限体系是重新设计的,很多权限除了声明,还要在运行时动态申请或者设置受限权限的说明。网络权限相对好一点,属于普通权限,只要在 module.json5 里的 requestPermissions 字段加上ohos.permission.INTERNET就够了。
但具体到工程里,需要注意一点:有些工程用的是 API 12 的模板,默认并没有把 INTERNET 权限放进去,需要自己手动补。下面是在 entry/src/main/module.json5 里添加权限的代码片段:
{ module: { name: "entry", type: "entry", // 其他配置... requestPermissions: [ { name: "ohos.permission.INTERNET" } ] } }这里有个很容易踩的盲区:如果你只是在 DevEco Studio 的 UI 配置里增加了权限,但项目实际运行时使用的是oh-package.json5依赖安装后的缓存配置,可能会导致权限变更没有完全生效。所以我在修改 module.json5 之后,一定会先把鸿蒙侧依赖做一次清理重建:
hvigorw --mode module -p product=default clean --no-daemon然后再重新构建。很多"权限明明加了还是连不上网"的问题,其实不是权限本身的问题,而是增量构建残留了旧的 module 配置。这条经验在我适配三个不同项目时都验证过,建议遇到类似情况先做干净构建。
3. http_plus 鸿蒙化的核心适配方案:从依赖分析到网络栈替换
3.1 分析 http_plus 的依赖树:哪些包官方已支持鸿蒙
http_plus 不是一个零依赖库,它对环境的假设隐藏在依赖树里。迁移前我习惯用一句命令把所有传递依赖打出来,逐一确认每个包的平台支持情况:
flutter pub deps --style=compact以我在生产项目里用的 http_plus 版本为例,它的核心依赖链大致长这样:
- dio:负责底层请求与 adapter 机制,本身是纯 Dart,鸿蒙可以跑,但有部分底层行为需要验证。
- connectivity_plus:用于监听网络类型与连通性变化,官方新版已声明支持 ohos。
- flutter_cache_manager:负责磁盘文件缓存,内部大量使用 path_provider,而 path_provider 对鸿蒙的支持需要额外引入 path_provider_ohos。
- device_info_plus:用于构建 UA 与设备信息上报,官方已有鸿蒙适配版本。
- synchronized:纯 Dart 锁工具,没有平台依赖问题。
你会发现,http_plus 最麻烦的依赖其实不是网络库本身,而是那些和文件路径、设备信息、网络状态相关的平台插件。这些插件在鸿蒙上要么没有官方实现,要么实现得比较粗糙,需要在 pubspec.yaml 里显式添加鸿蒙适配包来"填充"依赖空洞。
比如 path_provider 在鸿蒙上需要手动并把默认实现替换成 path_provider_ohos,否则运行时会直接报 MissingPluginException。我在项目里处理完这几个插件后,http_plus 的初始化流程才走通。实际操作时要特别留意依赖的版本兼容,最好把鸿蒙适配包的版本锁定在能覆盖你当前 API level 的范围内,不要一股脑全部 latest。
3.2 网络栈替换策略:用 ohos 原生连接还是自建 adapter
http_plus 的底层是 Dio,Dio 的请求最终要经过一个 HttpClientAdapter。在 Android 上默认是 IOHttpClientAdapter,内部实现是 Dart 的 HttpClient;在鸿蒙上,社区版本的 Flutter Engine 同样实现了 Dart IO 的 socket 能力,所以理论上"什么都不改"也能发请求。但我实测下来,默认适配器在鸿蒙上有几个明显的短板:
- 系统代理不生效。部分企业网络环境强制要求走代理,默认 Dart HttpClient 拿不到鸿蒙系统代理配置。
- TLS 证书校验策略与鸿蒙系统不一致。默认适配器只认 Dart 侧内置的根证书,调用自签证书的接口服务器会失败。
- 连接超时与 SocketException 语义不一致。鸿蒙网络栈某些场景下返回的错误码,Dart 侧不能精确翻译成标准超时。
针对这些短板,我最后采用了"自建 NetworkAdapter"方案。在初始化 http_plus 时,我们不直接使用默认处理链,而是通过 Dio 的httpClientAdapter属性,注入自己实现的 adapter。这个 adapter 内部可以用两种连接路径:优先走鸿蒙原生 socket 封装,通过 Platform Channel 调用 ArkTS 侧的 socket 连接;如果暂时不想引入平台通道,也可以用 Dart IO 的 HttpClient 配合显式代理和证书配置。
实际开发中,我建议先以 Dart IO 路径作为兜底,因为这意味着不需要在 ArkTS 侧维护一套原生网络代码,业务上线前最重要的是先把链路跑通。对于代理和特殊 TLS 场景,再单独抽离一个原生连接组件,按需启用。下面是一个最小可用的 adapter 骨架,展示了如何在初始化时把自定义连接逻辑注入 Dio:
import 'package:dio/dio.dart'; import 'package:http_plus/http_plus.dart'; Dio buildDioWithOhosAdapter() { final dio = Dio(); dio.httpClientAdapter = OhosHttpClientAdapter( enableSystemProxy: true, tlsConfig: TlsConfig( allowSelfSigned: false, customTrustChain: _loadPlatformTrustChain(), ), ); dio.options ..connectTimeout = const Duration(seconds: 10) ..receiveTimeout = const Duration(seconds: 15) ..sendTimeout = const Duration(seconds: 10); return dio; }这里OhosHttpClientAdapter就是一个实现了 HttpClientAdapter 接口的类,内部把连接、发送、接收的底层能力统一接管。有了这层抽象,后续无论换原生 socket 还是调整 DNS 策略,都不需要再动 http_plus 的业务层代码。
3.3 拦截器、缓存与 TLS 的重灾区
网络栈换了之后,真正开始跑业务才发现重灾区在 TLS 和缓存。先讲 TLS。鸿蒙系统对 TLS 版本的支持和 Android 略有不同,默认情况下 TLS 1.3 是开启的,但对 TLS 1.0、1.1 的支持不友好。如果你后台服务器还在用老旧的 TLS 协议,Android 上可能靠兼容策略蒙混过关,鸿蒙上直接就握手失败。
我在适配时遇到过一个典型的报错:
HandshakeException: Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: certificate verify failed(no trust anchor))这个错误的字面意思是"没有信任锚点",常见原因有两种:服务器用的证书链不完整,或者鸿蒙系统的根证书库与 Android 不一致。解决方案分两步走:先在服务器端把证书链补齐,中间证书和根证书全部下发;如果暂时改不了服务器,只能从客户端入手,把对应根证书加入到自定义信任链中。但我必须强调,客户端信任自签证书只是过渡方案,生产环境一定要收敛回系统信任链。
缓存的重灾区则体现在磁盘文件的读写目录上。http_plus 的缓存拦截器默认使用 path_provider 获取临时目录,鸿蒙上必须由 path_provider_ohos 来接管,否则缓存目录指向错误,表现为"缓存明明命中却读不到数据"。另外鸿蒙的沙箱文件系统对文件路径有严格的限制,缓存文件尽量放在通过插件暴露的缓存目录里,不要自己往固定的绝对路径写,否则很容易触发权限错误。
3.4 适配层代码补全:一个最小可用的 NetworkAdapter
为了让你少走弯路,我把适配层的核心代码框架贴出来。这里的目标不是直接让你复制到生产环境,而是展示 http_plus 鸿蒙化常用的"插槽式"设计:所有需要平台差异化的能力,都收敛到一个可替换的适配层。
import 'dart:async'; import 'dart:io'; import 'package:dio/dio.dart'; class OhosHttpClientAdapter implements HttpClientAdapter { OhosHttpClientAdapter({ this.enableSystemProxy = true, this.customTrustChain, }); final bool enableSystemProxy; final List<TrustAnchor>? customTrustChain; @override Future<ResponseBody> fetch( RequestOptions options, Stream<Uint8List>? requestStream, Future<void>? cancelFuture, ) async { // 这里的核心逻辑是建立一条可管控的连接通道。 // 可以是 Dart HttpClient,也可以是路径替换为原生 socket, // 关键是把 timeout、proxy、tls 参数在此处统一配置。 final client = HttpClient() ..connectionTimeout = options.connectTimeout; // 处理代理与 TLS,然后发起请求。 // 此处必须用 finally 关闭连接,避免连接泄漏。 final response = await _doRequest(client, options, requestStream, cancelFuture); return response; } @override void close({bool force = false}) { // 关闭连接池与相关资源,防止进程退出时阻塞。 } }这个 adapter 的价值不在于它做了什么高级优化,而在于它提供了一个"隔离点"。任何鸿蒙网络栈的特殊行为,都可以在这个类内部消化,不污染业务层。实际生产环境里,我会在这个基础之上再加一层对 SocketException 的类型翻译:把鸿蒙上报的 ENETUNREACH、EHOSTUNREACH 等错误码映射成 Dio 标准的 DioException,这样重试拦截器才能正确判断哪些异常值得重试。
4. 关键链路细节:连接池、DNS 与超时控制
4.1 连接池与 keep-alive 在鸿蒙上的表现
http_plus 在 Android 上默认复用一个连接池,keep-alive 的效果可以从抓包里明显看到:同一台服务器的请求几乎不会重新建连。鸿蒙上这套连接池策略依旧生效,但有一个隐藏行为需要注意——鸿蒙的网络切换比 Android 频繁,比如 Wi-Fi 与蜂窝网络切换时,旧连接的 keep-alive 会失效,如果连接池没有及时清理,就会出现第一个请求报错、重试后恢复正常的情况。
我在项目里解决这个问题,是在 loading 层增加了一个"网络变更监听器"。利用 connectivity_plus 的鸿蒙适配版监听网络类型变化,一旦发现网络切换,主动调用连接池清理方法。这个方案的原理并不复杂:与其让连接池拿着死去连接的引用空转,不如在网络变化的临界点主动重建,代价极小,但能消除大量偶发超时。
下面是伪代码示意,展示如何在 http_plus 里对接网络事件并触发清理:
class NetworkSwitcher { final Dio dio; NetworkSwitcher(this.dio) { _listen(); } void _listen() { // connectivity_plus 的 onConnectivityChanged 是流式 API, // 在鸿蒙上网络类型变化时会推新值。 connectivityStream.listen((result) { dio.httpClientAdapter.close(force: true); // 重新创建的 adapter 会在下一次请求到来时自动建立新连接。 dio.httpClientAdapter = OhosHttpClientAdapter(); }); } }这段代码的前提是你的 adapter 实现了 close 和重新赋值逻辑,实测下来能显著减少"忽好忽坏"的网络症状。
4.2 DNS 解析差异:IPv6 优先带来的坑
鸿蒙系统在网络协议栈的取向上和 Android 有一个明显差异:IPv6 解析优先级更高。如果一段网络环境里 IPv6 链路不通或者路由有问题,而后台服务器只监听 IPv4,请求会出现长时间等待后报连接超时。这不是 http_plus 的 bug,也不是 Flutter Engine 的问题,而是系统 DNS 返回了多个 AAAA 记录和 A 记录之后,我们按顺序先连了不可达的 IPv6 地址。
解决办法是在适配层的 DNS 解析阶段做一次显式排序和 fallback。Dart 的InternetAddress.lookup默认会返回系统解析的全部地址,我们可以按照type == InternetAddressType.IPv4优先的方式重排连接顺序;如果业务涉及的主机较多,也可以引入内存 DNS 缓存,避免每次请求都做系统解析,节省耗时。
Future<List<InternetAddress>> _resolveHost(String host) async { final results = await InternetAddress.lookup(host); results.sort((a, b) { int score(InternetAddress addr) => addr.type == InternetAddressType.IPv4 ? 0 : 1; return score(a).compareTo(score(b)); }); return results; }这个策略在双栈网络环境里有争议,因为某些场景 IPv6 延迟更低。但考虑到国内大量业务服务器仍是 IPv4 为主,我推荐在鸿蒙上默认采用 IPv4 优先,同时保留开关。后续如果服务器全面升级 IPv6,可以把优先级反转回来,不需要改别的代码。
4.3 超时与线程切换:Dart 侧同步等待的代价
另一个容易被忽略的细节是超时语义。在 Android 上 Dart 的 HttpClient 超时依赖 epoll 的唤醒机制,超时时间到了会立刻触发异常。鸿蒙上由于底层 socket 模型差异,超时往往不是精确触发的,可能出现"超时时间到了还没报错,再等一会儿才失败"的情况。原因在于 socket 的 poll 周期和 Dart event loop 的事件调度粒度共同作用,实际超时误差可能在数百毫秒级别。
所以我在配置连接超时时不会卡着业务方的期望值上头,而是采用"业务期望 - 缓冲"的公式。如果业务上要求 10 秒内必须返回,就把 connectTimeout 设成 8 秒、receiveTimeout 设成 12 秒。这样即使鸿蒙的 poll 周期引入 500ms 级别的误差,最终的失败时间点仍然落在业务可接受的范围内。这里没有什么神秘的公式,纯粹是预留误差缓冲的工程经验。
线程切换方面,http_plus 的重试和合并拦截器内部使用了很多 Future 与 Timer。鸿蒙 Flutter 分支对微任务的调度也有一些差异,并发量高的时候容易在 Dart VM 的 isolate 之间出现任务饥饿。我自己测下来,超过 200 并发请求时,日志打印和拦截器回调的时延会有明显增加。如果业务确实有高并发需求,建议把日志级别从 debug 调低,或者让日志拦截器走异步队列,不让 I/O 阻塞请求回调链。
5. 常见问题与排查实录:网友踩过的坑我也都踩了一遍
5.1 证书校验失败:https 请求报 HandshakeException
这是鸿蒙适配里出现频率最高的报错。先区分场景:如果是自签证书或内网 IP 访问,大概率是系统信任链里没有对应根证书;如果是正式域名但报证书链不完整,则是服务器配置问题。排查第一步是用鸿蒙自带浏览器打开接口地址,看浏览器是否提示证书错误。如果浏览器正常,而 App 里失败,再检查 Flutter Engine 打包时是否携带了正确的根证书库。
我当时的处理方案是给 adapter 的 TLS 配置增加一个可选的自定义信任锚,从 assets 目录读取业务根证书 PEM 文件,并手动加入 trust chain。这个方法只能作为临时过渡,真正的长期方案还是让服务器证书接入公共信任体系。如果一个团队的开发和测试环境大量使用自签证书,我建议在 debug 模式下开放这些逻辑,打包 release 时通过编译常量彻底关闭,防止误放安全口子。
5.2 网络权限"看似配置了"却连不上
有一种异常是请求发出去没有任何报错,但一直处于 pending 状态,直到超时。排查流程是这样的:先看 module.json5 里有没有 INTERNET 权限,再看有没有做干净构建,最后抓包确认请求是否真的发到了网络层。如果抓包显示请求根本没出去,说明卡在了系统权限校验之前。
我还遇到过一个特殊情况:鸿蒙系统里如果应用被配置为"受限制的网络权限模式",即使在 module.json5 里声明了权限,也会被运行时策略拦截。这种情况多见于用户手动修改了隐私设置。在代码里可以通过 connectivity 能力查询当前是否有网络访问权,如果发现被限制,要在 UI 层友好提示用户去设置里恢复,而不是无脑重试。
5.3 并发请求导致的内存抖动与连接泄露
http_plus 的并发控制默认是依赖信号量,如果某个请求超时性能很差或者连接被底层异常踢掉,信号量的归还逻辑偶尔会出问题,表现为"卡住了一部分并发额度"。适配鸿蒙后发现这个现象被放大了,原因是鸿蒙网络栈在连接断开时返回的错误类型更复杂,部分错误没有被 http_plus 识别为"可释放信号量"的情况。
我的排查方法是给信号量入口增加 monitor:在每次请求结束的 finally 块里打点记录当前信号量的剩余许可数量,对比业务进程的预期值。现场如果出现许可数量长期不恢复,就逐个检查是哪类异常路径没有走 finally。这类 bug 很难一次定位,但一旦定位完,修复起来通常也就几行代码。
内存抖动方面,鸿蒙上的调试工具不如 Android Studio Memory Profiler 那么方便,我用的土办法是把dart:developer里的 heap 快照周期性地导出,通过对比不同时间点的对象数量增长曲线来找泄漏点。考虑到 http_plus 的网络请求对象大多在 Future 链上创建,只要请求结束后的引用清理逻辑正确,内存抖动通常是磁盘缓存写入导致的临时文件句柄积累,优先检查缓存目录是否有文件持续堆积。
5.4 适配后的性能对比与调优建议
我把同一个 http_plus 业务模块在 Android 真机和鸿蒙真机上跑了两轮基础性能测试,数据如下(仅供参考,不同设备型号差异较大):
| 指标 | Android 真机(中端) | 鸿蒙真机(同级别) | 说明 |
|---|---|---|---|
| 首次请求握手耗时 | 约 210ms | 约 260ms | 鸿蒙 TLS 握手多了几步证书校验查询 |
| 连接池复用后请求耗时 | 约 80ms | 约 95ms | 差异来自内核 socket 事件唤醒成本 |
| 200 并发下的 CPU 占用 | 约 18% | 约 24% | 鸿蒙网络栈与 Flutter 事件循环耦合更深 |
| 缓存命中响应耗时 | 约 2ms | 约 3ms | 几乎无差异 |
从数据里可以看出,鸿蒙的底层网络开销确实略高,但对绝大多数业务来说不需要过分担心。真正值得调优的方向有两个:一是缩小不必要的请求体序列化开销,尤其是 JSON 序列化,鸿蒙分支的dart:convert性能表现略逊于 Android,我在项目里把部分高频接口改用了更轻量的二进制编码,整体耗时下降了约 15%;二是充分利用 http_plus 的请求合并能力,同一个页面如果有多个组件同时触发同一个接口,设置合并窗口为 200ms,实测能显著减少握手次数和服务器压力。
写在最后的一点经验
鸿蒙化 http_plus 的过程,让我最大的感受是:适配工作从来不是把代码从 A 平台搬到 B 平台,而是把一套库的使用假设逐条拿出来,放在新平台的环境里重新审视。http_plus 本身写得很优秀,它把网络能力的边界封得很清晰,这给了我们替换底层 adapter 的空间,也让核心业务代码基本可以不动。但真正决定适配成败的,往往是环境细节——权限配置、DNS 排序、TLS 信任锚、连接池生命周期、超时缓冲。这些细节单独任何一条都不难,难的是组合在一起时如何保证不互相打架。
我个人在后续新项目里的做法是:把鸿蒙适配层的代码单独抽到一个独立模块,业务团队不直接接触网络底层细节,只依赖 http_plus 的标准接口。这样既保证了网络层的行为一致性,也为将来鸿蒙系统的版本升级留出了可以单独迭代的边界。如果你也在做类似迁移,不妨也试试这个思路。