Kotlin Multiplatform 三方库 connectivity 的 OpenHarmony 鸿蒙化适配指南

库版本:jordond/connectivity 2.2.1 鸿蒙切片 2.2.1-ohos.1|验证环境:HarmonyOS Kotlin 2.2.21-1.0.0|Gradle 8.14.1|JDK 21|DevEco Studio 26.0.0|HarmonyOS 7.0.0(API 26)|模拟器 127.0.0.1:5555(x86_64)

前两篇 KMP 适配分别验证了两件事。qrcode-kotlin 证明 ohos target 能跑纯计算:actual 不链系统 so,自己光栅、自己编 PNG。multiplatform-settings 证明同一套 Gradle / NAPI 骨架能接到设备上的 libohpreferences.so。这篇补第三块:系统网络状态。

我以为把 OH_NetConn_HasDefaultNet 五个函数名抄进 Kotlin,ArkTS 点一下就能出 Connected。然后发生了四件事。

第一,SDK 的 net_connection.h 喂给 cinterop,availability 宏把 clang 前端打爆。第二,HasDefaultNet 的出参是 int32_t*,第一版按 Preferences 那篇的 BooleanVar 去读,编得过,值是错的。第三,llvm-nm -D 里明明有 ConnectivityCall,ArkTS 直接 import Kotlin/Native so,业务函数根本不在导出表里。第四,native 和 ArkTS 都编过了,PackageHap 却申请 16GB 堆;权限漏写 GET_NETWORK_INFO,五个 C 调用一律返回 201,页面会装成「没网」。

网络这种库,编过不算数。rc 必须露在 JSON 里。Start / Stop 必须让 isMonitoring 翻转。有 default net 时 kind 必须是 Connected。本文按这条真实路径写。

OpenHarmony 模拟器实拍:Snapshot / Status / Start / Stop

四张是同一台 x86_64 模拟器、同一条以太网。大字一直是 Connected,变的是 JSON:netId=101、monitoring 从 true 翻到 false。只看大字会以为 Start / Stop 没生效。
在这里插入图片描述

一、环境搭建

本章不展开,直接引用官方入口:KMP&CMP 鸿蒙社区、HarmonyOS 应用开发导读。

本文实际使用:

项值
语言 / 框架Kotlin Multiplatform,HarmonyOS Kotlin 2.2.21-1.0.0
插件仓库https://maven.eazytec-cloud.com/nexus/repository/maven-public/
JDKTemurin 21(写进 gradle.properties 的 org.gradle.java.home)
Gradle Wrapper8.14.1
DevEco Studio26.0.0
HarmonyOS SDK7.0.0(API 26)
真机 ABIohosArm64 → arm64-v8a
模拟器 ABIohosX64 → x86_64
HAP 打包 JDKDevEco 自带 JBR,不要用 JDK 8
系统库libnet_connection.so(设备 sysroot,不打进 HAP)
权限ohos.permission.GET_NETWORK_INFO

ohosArm64() / ohosX64() 只存在于这套定制 Kotlin Gradle Plugin。用 Maven Central 上的官方 2.2.21 写这两行,配置期就会 Unresolved reference。这是判断工具链有没有接对的第一根探针。

二、应用背景

跨端业务里,「现在有没有网」出现的频率不比键值存储低。启动页要不要打登录、同步按钮要不要转圈、弱网要不要换小图,全都先问一句网络状态。Android 走 ConnectivityManager,Apple 走 NWPathMonitor。Jordon de Hoog 的 connectivity 把差异收口成一个很小的接口:

val net = Connectivity()
net.start()
when (val status = net.status()) {
    is Connectivity.Status.Connected -> println("metered=${status.metered}")
    Connectivity.Status.Disconnected -> println("offline")
}
net.stop()

commonMain 里的业务代码不需要知道底下是 ConnectivityManager、NWPathMonitor,还是鸿蒙的 OH_NetConn_*。鸿蒙缺的只是一个 actual。

三条常见路线我都否掉了。

在 ArkTS 里直接 @ohos.net.connection,那是使用文,不是适配。KMP 业务跑在 Kotlin/Native 线程,没有 JS runtime,也不能把 Ability 的 Context 传进 commonMain。

ohos actual 走 NAPI 回调 ArkTS 网络模块,等于每次快照跨一次语言,生命周期更乱。KN sharedLib 里也没有 Ability Context。

把 JVM 的 NetworkInterface 原样当鸿蒙实现,那是 L1 冒烟,不是设备上的默认网。JvmConnectivity 留着只给 jvmTest 对照,metered 在 JVM 上恒为 false。

对位的公开能力是 C API:network/netmanager/net_connection.h,动态库 libnet_connection.so。HasDefaultNet / GetDefaultNet / IsDefaultNetMetered / GetNetCapabilities / GetAllNets 从 API 11 开始。目标设备 API 26,够用。

选题时用 git ls-remote 扫过 CPF-KMP-CMP 和 oh-tpc:已有 Ksoup、kotlin-result、kotlin-multiplatform-diff、Kermit、okio、kotlinx-datetime、SQLiter,以及本系列的 qrcode-kotlin、multiplatform-settings,没有 jordond/connectivity。这是增量题,不是重复领激励。方向锁定 KMP 三方库适配,不是 CMP,不是 Flutter connectivity_plus,也不是「拿 ArkTS connection 写业务」的使用文。

上游完整 API 绑死 kotlinx.coroutines.flow.SharedFlow。本轮 HarmonyOS Kotlin 树没有官方 kotlinx-coroutines ohos 制品。与其交一个「看起来在 collect、回调永远不来」的半残 Flow,不如把 2.2.1-ohos.1 写成无协程切片:Status 模型保留,status() 改成同步快照,Flow 明确划出范围。

三、接口分析

在这里插入图片描述

按 Demo 实际打到的表面列,不把上游 statusUpdates、Compose 扩展件算进「已适配」。

能力上游 API鸿蒙 actualDemo 按钮验收
当前状态suspend status()同步 status(),内部 HasDefaultNetStatuskind=Connected 或 Disconnected
计费Connected.meteredIsDefaultNetMeteredMeteredJSON metered 与 rc 同时给出
开始监听start()进程内 isMonitoring=trueStartJSON monitoring=true
停止监听stop()isMonitoring=falseStopJSON monitoring=false
派生属性isConnected / isDisconnected / isMetered原样Status断开时 isMetered 必须 false
默认网 id(上游无,诊断)GetDefaultNetSnapshotnetId ≥ 0 或失败为 -1
承载 / 能力(上游无,诊断)GetNetCapabilitiesCapabilitiesbearers 含 WIFI / CELLULAR / ETHERNET 等
全部网络(上游无,诊断)GetAllNetsAllNetsallNetIds 非空或 rc 非 0

ArkTS 不直接碰这些 Kotlin API。它只调用一个 NAPI 函数:

import { ConnectivityCall } from 'libconnectivity_napi.so';

const raw: string = ConnectivityCall(JSON.stringify({ op: 'snapshot' })) as string;

op 取 snapshot / status / hasDefault / metered / capabilities / allNets / start / stop。返回 JSON:ok、kind、isConnected、isMetered、monitoring、netId、bearers、capabilities、五个 *Rc。UI 进程不懂 NetConn_NetHandle,Kotlin/Native 进程不懂 ArkUI。这是有意设计的。

四、六阶段路线图

  1. 工具链。 JDK 21 + HarmonyOS Kotlin 2.2.21-1.0.0 + Gradle 8.14.1。JDK 8/11 会在配置期直接死。
  2. 目标矩阵。 根项目收成 jvm() + ohosArm64() + ohosX64()。保留 JVM 是为了 jvmTest 当对照;砍掉 iOS / Android / JS,是因为这次要在 Windows 上闭环。
  3. cinterop。 手写 netconn_min.h,不要把 SDK 的 net_connection.h 整棵树丢进去。
  4. actual。 OhosConnectivity 实现 Connectivity。status() 先采 NetSnapshot,再投影成 Connected / Disconnected。
  5. C ABI + NAPI。 example/nativeApp 用 sharedLib { baseName = "ohosconnectivity" } 产出 so,@CName("ConnectivityCall") 导出。CAdapter 不挂业务符号,必须再编 libconnectivity_napi.so。
  6. 落盘。 HAP 打进两个 ABI 的 so,模拟器上八个按钮都有 JSON,有 default net 时 kind=Connected,Start/Stop 翻转 monitoring。

六阶段:工具链 → 目标矩阵 → cinterop → actual → NAPI → 读网卡

六步不能并成「把 ohos target 打开」。第 3 步头文件写错,第 4 步的 IntVar 读出来就是垃圾;第 5 步漏了 NAPI,第 6 步的按钮全是 undefined is not a function。每一步都有独立验收口,后文分层验收按这个顺序对。

applyDefaultHierarchyTemplate() 会在两个 ohos 目标之上生成 ohosMain。代码放这里,不要放 nativeMain:以后一旦加 linux,会把鸿蒙 Network Kit 强加给不该用的平台。

上游原工程还有 compose artifact 和 coroutines 实现。原样打开,本轮工具链会在用不到的目标上失败。本仓 settings.gradle.kts 只留 :connectivity 和 :example:nativeApp。版本号写成 2.2.1-ohos.1,不要冒充上游正式版。

五、三个关键决策

5.1 切片,而不是假装完整 fork

完整 fork 要把 statusUpdates: SharedFlow<Status> 和 Compose 一起搬过来。没有官方 coroutines ohos 制品,把 kotlinx-coroutines-core 写进 ohosMain,解析阶段就会缺 ohosArm64 / ohosX64 构件。Flow 只能假活。

所以公共接口收成:

public interface Connectivity {
    public val isMonitoring: Boolean
    public fun status(): Status
    public fun start()
    public fun stop()

    public sealed interface Status {
        public val isConnected: Boolean
        public val isMetered: Boolean
        public val isDisconnected: Boolean
        public data class Connected(public val metered: Boolean) : Status
        public data object Disconnected : Status
    }
}

Status 密封类保持原样,包括三个派生属性。start() / stop() 名字也保持原样。缺的是 Flow 收集器和 suspend。谁在 commonMain 里 collect,编译期失败,好过运行期挂起。

这不是偷工。协程一旦有官方 ohos 构件,可以把 OhosConnectivity 再包一层 callbackFlow,把 OH_NetConn_RegisterDefaultNetConnCallback 接进去,不必改 common 模型。现在硬接,只会让 Demo 永远编不过。

5.2 精简头,出参用 IntVar

第一反应是 headers = net_connection.h。编不过,原因和 Preferences 那篇一样具体。

头文件用了 __availability__,还嵌套 net_connection_type.h。cinterop 自带的 clang 前端认不全。我们这轮真正要用的只有五个查询函数,回调和 DNS / 代理先不做。

所以 src/ohosMain/cinterop/ 下放了两份自己写的文件:netconn_min.h 按官方字段顺序手抄 NetConn_NetHandle、NetConn_NetCapabilities、NetConn_NetHandleList;netconn.def 的 headers 只指向这一份,并写 linkerOpts = -lnet_connection。

结构体字段顺序必须和官方头一致。NetConn_NetCapabilities 先是上下行带宽,再是 netCaps[32] + netCapsSize,再是 bearerTypes[32] + bearerTypesSize。写反了,cinterop 不会报错,运行时会把 capability 读成 bearer。

编过之后,Kotlin 侧还有几条从 dump 出来才知道的事实:

  1. HasDefaultNet / IsDefaultNetMetered 的出参是 int32_t *,不是 bool *。Kotlin 侧是 IntVar,不是 Preferences 那篇的 BooleanVar。写成 BooleanVar 能编过,读出来的值是错的。判定写成 value != 0 才算 true。
  2. NetConn_NetHandle 是结构体值类型,字段 netId 直接读。
  3. 定长数组用 caps.bearerTypes[i],不要当成 List。
  4. *Size 字段必须先 coerceIn(0, 32) 再遍历。C 侧如果返回脏值,Kotlin 会越界,模拟器直接 abort。

左:照着 SDK 头写;右:精简头 + dump 对类型

左边六条我都踩过。最隐蔽的是「五个 rc 全是 201」:函数名对、so 也链上了,页面却写成断网。那不是 HasDefaultNet 返回 false,是权限没声明,C API 拒绝回答。右边六条是后来能在模拟器上看到 rc=0 的最低条件。

链接时,nativeApp 的 sharedLib 必须再加 -L$sysroot/usr/lib/<abi>-linux-ohos -lnet_connection。只在 def 文件写 linkerOpts,有的发行包不会把它传到最终 so。这些符号运行时由系统 libnet_connection.so 提供,不要把系统库打进 HAP。

5.3 业务桥走 @CName + NAPI,只信 HasDefaultNet

qrcode 和 settings 已经验证过:HarmonyOS Kotlin 的 CAdapter 只导出 stdlib 的 Checksum / Zip / GZip。@CName("ConnectivityCall") 在 ELF 里看得到,ArkTS import KN so 却调不到。缺的是 NAPI 表面,不是 C 符号。

投影只有一句:有默认网就是 Connected(metered),否则 Disconnected。不要用「bearers 里有 WIFI」当连通条件。不要用 GetDefaultNet 的 netId == 0 当断网——文档没保证 0 是非法值。GetDefaultNet 失败只让 netId=-1,不把 Connected 打成 Disconnected。IsDefaultNetMetered 失败则 metered=false,但 rc 留在 JSON。没有合法 handle 时不要调 GetNetCapabilities,官方文档写的是 401。

库不过问能不能访问公网,那是 VALIDATED 能力位的事。应用层若要「能上网」,自己看 capabilities,不要改 Status 语义。

start() / stop() 在本版只翻 isMonitoring。系统里有 OH_NetConn_RegisterDefaultNetConnCallback,回调在网络服务线程,和无协程切片的边界冲突。下一版若补 Flow,从这条 API 接,而不是在 ArkTS 里 setInterval 假装监听。

六、桥接怎么接

调用链从上到下是五层。中间少一层就会在 dlopen 或 undefined is not a function 上爆。

ArkTS  →  libconnectivity_napi.so (NAPI)  →  ConnectivityCall()  →  libohosconnectivity.so (KN)  →  libnet_connection.so

ArkTS → libconnectivity_napi.so → libohosconnectivity.so → OhosConnectivity → libnet_connection.so

HAP 里真正要带的只有三份:libconnectivity_napi.so、libohosconnectivity.so、libc++_shared.so。libnet_connection.so 在设备 sysroot,打进包反而会和系统库抢加载。C++ 包装层不解析 JSON,也不调 OH_NetConn_*。解析、IntVar 判定、bearer 名字,都停在 Kotlin。

Demo 按官方 native 模块来:entry/src/main/cpp/napi_init.cpp 编成 libconnectivity_napi.so,内部链接 libohosconnectivity.so,调完用 DisposeString 把 KN 分配的 C 字符串释放掉。C++ 里不解析 JSON,也不调 OH_NetConn_*。解析和映射都在 Kotlin。

example/nativeApp 的 Bridge.kt 吃一小段 JSON,在 Kotlin 里走完整 OhosConnectivity:

@CName("ConnectivityCall")
fun connectivityCall(request: String): String = dispatch(request)

private val connectivity = OhosConnectivity()

// snapshot 一次打五个 C 调用,截图不依赖输入法
val snap = connectivity.snapshot()

有四个地方我写错过。

  1. baseName 是 String,不是 Property<String>。 写成 baseName.set("ohosconnectivity") 会在 configuration 阶段报 Unresolved reference 'set'。正确是 baseName = "ohosconnectivity"。
  2. @CName 参数用 String。 KN 生成 const char* ABI。返回的 C 字符串必须 DisposeString,NAPI 包装层已经做了,ArkTS 不用管。
  3. entry/build-profile.json5 的 abiFilters 必须同时有 arm64-v8a 和 x86_64。 本机 DevEco 模拟器是 x86_64。只编 arm64,安装会报 9568347,看起来像签名问题,其实是 ABI 缺失。
  4. ArkTS 严格模式禁止 Any。 Index.ets 用 CallResult 接口收字段,JSON 用 JSON.parse(raw) as CallResult。不要写成 any。

linkDebugSharedOhosArm64 / OhosX64 已经 finalizedBy 拷贝任务,会把 libohosconnectivity.so 和 Kotlin/Native 依赖的 libc++_shared.so 一起放进 entry/libs/<abi>/。漏掉 libc++,运行时是 Error loading shared library libc++_shared.so。

CMake 强制 -Wl,-rpath,$ORIGIN、BUILD_WITH_INSTALL_RPATH TRUE、IMPORTED_NO_SONAME TRUE。IMPORTED 库会把本机绝对路径写进 so,真机上那条路径毫无意义。nm_modname 必须是 connectivity_napi,和 oh-package.json5 对上。名字写错,ArkTS 会编译过、运行时报模块找不到。

JSON 解析是手写的极简扫描,只认字符串字段。不要在 KN 侧再拉 kotlinx.serialization——ohos 目标的依赖图会被重新打乱。对外截图只点按钮,不碰输入法。

七、踩坑表

现象根因处理
Unresolved reference: ohosArm64用了官方 Kotlin 2.2.21换成 HarmonyOS Kotlin 2.2.21-1.0.0,仓库放到 pluginManagement 第一位
cinterop 喂 SDK 头直接死availability 属性 / 额外头手写 netconn_min.h
BooleanVar 读 HasDefaultNet出参是 int32_t *用 IntVar,value != 0
模拟器 abort数组没看 SizecoerceIn(0, 32)
import KN so 调不到 ConnectivityCallCAdapter 不导出业务符号ArkTS 改 import libconnectivity_napi.so
9568347 安装失败HAP 缺 x86_64abiFilters 加上 x86_64,并链接 ohosX64
Error loading shared library libc++_shared.so只拷了 KN socopy 任务同时带上 Konan 的 libc++_shared.so
PackageHap:Unable to allocate 522624KB bitmaps ... 16723968KB heappacking tool 起 JVM 时堆被要到 16GBDevEco JBR + JAVA_TOOL_OPTIONS=-Xmx2g
五个 rc 全是 201,看起来像没网没申请 GET_NETWORK_INFOmodule.json5 里 requestPermissions
看起来像断网把 netId==0 或带宽 0 当成离线只信 HasDefaultNet
Start 之后大字不变本版无 OS 回调看 JSON monitoring 字段;再点 Snapshot
snapshot_display 失败本模拟器要求后缀 .jpeg不要用 .png
以太网仍报 metered=true系统给的计费标记原样上送,不要按 WIFI 去猜

权限是 system_grant,不用运行时弹窗,但必须声明。漏了它,五个 rc 全是 201,看起来像没网。Demo 把 rc 打在页面上,就是为了这一幕。

PackageHap 这一条值得单独说。native 和 ArkTS 都已经编过了,失败发生在最后把 so 塞进 HAP 的 Java 工具。日志原文是 Unable to allocate 522624KB bitmaps for parallel garbage collection for the requested 16723968KB heap。不是工程写错,是 JVM 参数。换 JBR 并限制 -Xmx2g 之后,PackageHap 几百毫秒就过。

八、Demo 验收:每个接口一张实拍

包名 org.terminator.ohos.connectivity,Ability EntryAbility。本机模拟器 hdc install 后:

hdc shell aa start -a EntryAbility -b org.terminator.ohos.connectivity

下面每张图都是从模拟器抠出来的,不是合成。状态栏时间 09:22 附近。字幕写的是 KMP connectivity + OH_NetConn C API。不要只贴一张启动图交差。网络库要看连通态、计费、能力位、Start/Stop 翻转。本次模拟器走以太网,大字 Connected,bearers=ETHERNET,五个 rc 全是 0。

8.1 Snapshot:一次打五个 C 调用

启动即 snapshot。状态卡 Connected,netId=101,bearers=ETHERNET,capabilities=INTERNET, NOT_VPN, VALIDATED,五个返回码全是 0。这一步过了,说明 NAPI → KN → OH_NetConn_HasDefaultNet 整条链是通的。如果是 失败: 开头,先查 so 有没有进 HAP,再查权限。

Snapshot Connected netId=101 ETHERNET rc=0

JSON 里 kind=Connected、hasDefaultRc=0。这是 status() 投影和诊断快照一次打通的证据。

8.2 Status:commonMain 表面

点 Status。op=status 的响应更短,只留 kind / isConnected / isMetered / isDisconnected / monitoring / netId。这是给 commonMain 看的表面,不带 bearer 数组。

Status Connected isMetered=true

断开时 isMetered 必须是 false。这是密封类派生属性的契约,不是 UI 自己算的。

8.3 HasDefault:权限探针

点 HasDefault。hasDefaultNet=true,rc=0。缺权限时这里会变成 rc=201,页面会装成没网。把 rc 画出来,就是为了把「没权限」和「没网」分开。

HasDefault true rc=0

8.4 Metered:原样上送系统标记

点 Metered。metered=true,rc=0。模拟器以太网被标成计费网,以 C API 为准,不要在 Kotlin 里按 WIFI 去猜。真机 Wi-Fi 会不会仍是 true,要等签名后再测,现在不能编造。

Metered true rc=0

8.5 Capabilities:bearer 与能力位

点 Capabilities。bearers=["ETHERNET"],capabilities=["INTERNET","NOT_VPN","VALIDATED"]。VALIDATED 表示系统认为这条默认网能上网,但 Status 本身不读这个位。带宽两个字段在本机模拟器上是 0,C 调用返回码是 0,说明函数成功了,只是这条虚拟网卡没填速率。不要写成「API 没接上」。

Capabilities ETHERNET INTERNET NOT_VPN VALIDATED

8.6 AllNets:诊断字段

点 AllNets。allNetIds=[101]。诊断字段,不参与 status() 投影。多网卡真机上这里会超过一个 id,本模拟器只有默认以太网。

AllNets [101]

8.7 Start:monitoring 翻成 true

点 Start。last op: start,JSON "monitoring":true。大字仍是 Connected,这是本版预期:start 还不注册 OS 回调。只看大字会误判没生效。

Start monitoring=true

8.8 Stop:monitoring 翻回 false

点 Stop。last op: stop,JSON "monitoring":false。Start / Stop 必须成对截图。飞行模式能拍到 Disconnected 更好。部分模拟器关 WIFI 仍留虚拟以太网,HasDefaultNet 仍可能是 1。以 C API 为准。

Stop monitoring=false

九、分层验收

不要把「库编过」和「页面上 kind=Connected」混成一次验收。四层口子分开过。

L1 jvmTest / L2 链接 / L3 HAP / L4 运行

L1 只证明密封类派生属性没写反。L2 用 nm 看 ConnectivityCall 在不在 ELF 里,在也不代表 ArkTS 能调。L3 打开 HAP 看三个 so 是不是两个 ABI 都有。L4 才看五个 *Rc 是不是 0。上一层绿、下一层红,不要回头改 commonMain。

层命令我这边的结果
L1gradlew :connectivity:jvmTestconnectedStatusFlags、disconnectedStatusFlags、providerBackedStartStop、jvmFactoryReturnsSnapshot 绿。公共 API 语义在 JVM 上对照过
L2gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64两个 ABI 的 so 都导出 ConnectivityCall,并拷到 entry/libs/<abi>/
L3 编译hvigorw assembleHap -p module=entry@default -p product=default --no-daemonHAP 内 arm64-v8a / x86_64 各含 libconnectivity_napi.so、libohosconnectivity.so、libc++_shared.so
L4 运行hdc install + 真点击Snapshot / Status / HasDefault / Metered / Capabilities / AllNets / Start / Stop 全部有实拍

打包必须用 DevEco JBR。本机默认 JAVA_HOME 若指向 JDK 8,或 packing tool 被要到 16GB 堆,PackageHap 会直接起不来。native 其实已经编过了,只是最后把 so 塞进 HAP 的那一步没起来。命令行里固定:

$env:JAVA_HOME = "C:\Program Files\Huawei\DevEco Studio\jbr"
$env:JAVA_TOOL_OPTIONS = "-Xmx2g"

命令行打出来的是 unsigned HAP。这个模拟器接受了 hdc install。真机和正式签名仍走 DevEco 的 "signingConfig": "default",不要在 build-profile.json5 里把这段改空。

Windows 上链出来的 ohos so 不能在本机 dlopen。对照测试走 jvmTest,设备行为走 HAP。不要在第 L4 失败时回头改 commonMain 模型——先 hilog 搜 ConnectivityNapi,看 JSON error 和五个 *Rc。

十、已知限制

  1. 不是 CMP。 没有 Compose 控件,没有联网动画。KMP 和 CMP 是两条配额线,这个库走 KMP。
  2. 没有协程。 statusUpdates: SharedFlow<Status> 和 suspend fun status() 不在 2.2.1-ohos.1 里。等官方 coroutines ohos 构件。
  3. 没有默认网回调。 OH_NetConn_RegisterDefaultNetConnCallback 没绑。Start / Stop 只切换本地标志。
  4. 没有 HTTP 探测。 上游 connectivity-http 用实际请求判断「真的能上网」。本仓只读系统 default net。门户网络(PORTAL capability)能看见,但不会自动发探测包。
  5. JVM 的 metered 恒为 false。 Java 没有可移植的计费网 API。JvmConnectivity 只给宿主测试对照。
  6. NetSnapshot 只在 ohos 源集。 netId / bearers / capabilities 不是跨平台 API,不要在 commonMain 依赖它们。
  7. 带宽在本模拟器上为 0。 返回码是成功,不要当成没接 API。
  8. 模拟器以太网报 metered=true。 原样上送,不在 actual 里改写。
  9. CAdapter 限制仍在。 即使 ELF 能看到 @CName("ConnectivityCall"),ArkTS 也必须走 NAPI。不要在下一篇里再踩一次还当新发现。
  10. 模拟器 ABI。 本地 DevEco 模拟器是 x86_64。只交 arm64 HAP,安装失败码经常被误判成证书问题。
  11. 真机签名未跑。 模拟器 unsigned 可装。对外演示请在 DevEco 配 signingConfig: default,再用 arm64 真机走一遍 Wi-Fi / 蜂窝 / 飞行模式。
  12. 仓库位置。 适配代码在 oh-tpc/jordond,社区组织仍是 CPF-KMP-CMP。不往 Flutter 组织塞 KMP 库。

有人会问:鸿蒙自己就有 netManager,为什么还要 KMP 这一层。答案不是性能,是边界。ArkTS NetConnection 的上下文是 UIAbility,API 是 Promise。KMP 业务拿到的是同步 C ABI,没有 JS runtime。把 KN 的 status() 回调到 ArkTS 再读,等于每次启动都要跨一次 NAPI。本适配站在 OH_NetConn 这一层。Demo 之所以还出现 ArkTS,只是因为要有一个能点的界面,和征文要求的全接口截图。真正给业务用的入口是 OhosConnectivity,不是 Index.ets。

十一、如何提 Issue / PR

上游功能问题优先去 jordond/connectivity。鸿蒙 actual、cinterop 头、NAPI 包装、Demo 安装问题开在适配仓库。不要把 ohosArm64 编译日志丢给上游——那不是他们的 target。上游 GitHub 的 CI 跑不了 ohosArm64(),不要把定制插件的 Gradle 硬推给上游当普通 PR。

请固定带这六项,否则很难判断是权限问题、ABI 问题,还是真的没网:

提 Issue 请带 ABI、操作、JSON、权限、hilog、实拍

  1. ABI:真机 ohosArm64 还是模拟器 ohosX64
  2. 操作:snapshot / status / hasDefault / metered / capabilities / allNets / start / stop
  3. JSON 原文,尤其是五个 *Rc 字段
  4. module.json5 是否声明 GET_NETWORK_INFO
  5. hilog 搜 ConnectivityNapi / ConnectivityDemo
  6. 界面实拍,不要只贴「没网」两个字

PR 建议拆开:src/ohosMain 的实现 / cinterop 是库本体;example/nativeApp 的 @CName 是 C ABI;example/harmonyApp 的 CMake 是 NAPI。不要把三种改动揉进同一个 commit。示例工程保持 "signingConfig": "default"。

克隆适配仓请走 AtomGit 组织页导入后再 git clone,不要写第三方镜像站地址。

十二、小结

这次适配没有发明新的网络协议。commonMain 里的 Status 模型原样工作。鸿蒙侧真正要补的是三块:

  • 一份 cinterop 肯吃的精简头,以及 dump 出来才知道的 IntVar / 定长数组访问方式
  • 一个守住 snapshot 语义的 OhosConnectivity,只信 HasDefaultNet,把 bearer 和 capability 原样上送
  • 一层 CAdapter 不肯给的 NAPI 表面,外加双 ABI so,让 x86_64 模拟器也能装

前两块是常规 KMP。第三块是 HarmonyOS Kotlin 目前的现实,前两篇已经踩过,这篇用系统网络库又确认了一次。谁要是把「so 里有符号」当成「ArkTS 能调用」,或者把「编过」当成「真的读到了网卡」,会在 201 返回码和 9568347 上再浪费整下午。

和 qrcode-kotlin、multiplatform-settings 是同一条工具链上的三个样本。一个纯计算、不链系统 so;一个必须 dlopen 设备上的 libohpreferences.so;一个必须 dlopen 设备上的 libnet_connection.so。三篇一起看,才能判断 ohos target 是不是真的接上了,而不是复制了一份 so。

模拟器已经把 Snapshot / Status / HasDefault / Metered / Capabilities / AllNets / Start / Stop 跑通。仓库在 oh-tpc/jordond。下一步用 DevEco 默认签名在真机上再走一遍 Wi-Fi 和飞行模式。

如果只记住一件事:在 HarmonyOS Kotlin 这条链上,Kotlin/Native so 负责对系统 C API 读网卡,CMake NAPI so 负责被 ArkTS 看见,GET_NETWORK_INFO 负责让五个返回码是 0 而不是 201。三者缺一,截图上要么是断网,要么是装都装不上。
KMP&CMP 社区地址:https://atomgit.com/CPF-KMP-CMP
GitHub 上游:https://github.com/jordond/connectivity
鸿蒙适配版:https://atomgit.com/oh-tpc/jordond
示例工程:example/harmonyApp

欢迎加入 KMP&CMP 鸿蒙社区:https://atomgit.com/CPF-KMP-CMP

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐