Commit 554d9283 authored by zhangying's avatar zhangying

修改

parent 0f31a59a
# KMP-CAPI HarmonyOS Interop 示例项目 # KMP–HarmonyOS C-API 互操作示例
本项目是一个 Kotlin Multiplatform (KMP) 工程,展示了如何在 HarmonyOS (OpenHarmony) 平台上,通过 Kotlin/Native 与 HarmonyOS 的 C-API (ArkUI Native Node API & Custom Dialog API) 进行深度交互 本项目以 HarmonyOS C-API 为主体,展示 Kotlin/Native 调用鸿蒙 C-API 的通用方法论与工程实践。Demo 中的 AV 能力查询与 ArkUI 自定义弹窗只是示例,工程结构可扩展到任意 C-API(Ability、ArkUI、AVCodec、Window、Telephony 等)
核心特色:**在 Kotlin/C++ 侧直接构建原生 UI 并控制系统组件,无需完全依赖 ArkTS。** **核心价值**
- 在 Kotlin 侧以类型安全的方式封装鸿蒙 C-API
- 通过 `@CName` 暴露函数给 C++/NAPI,供 ArkTS/Harmony 应用侧调用
- 使用 Gradle 产出 `.so` 与头文件,Harmony 工程通过 CMake IMPORTED 链接
## 1. 项目结构 ## 1. 项目结构
* **`composeApp/`**: KMP 共享代码模块。 - composeApp:KMP 主模块
* `src/commonMain`: 跨平台通用业务逻辑。 - src/commonMain:跨平台通用逻辑
* `src/ohosArm64Main`: **[核心]** HarmonyOS 特有的 Kotlin/Native 实现。包含与 C-API 交互的绑定(`cinterop`)和导出给 C++ 的接口(`OhosExports.kt`)。 - src/ohosArm64Main:鸿蒙专属实现,包含 C-API 绑定与 `@CName` 暴露(示例:AV 能力查询)
* **`harmonyApp/`**: HarmonyOS 原生工程(DevEco Studio 工程)。 - 构建输出:`build/bin/ohosArm64/<type>Shared/libkn.so``libkn_api.h`
* `entry/src/main/cpp`: **[核心]** C++ 桥接层(NAPI)。负责实现具体的 UI 构建逻辑(`entry_module.cpp`),并将 Kotlin 接口暴露给 ArkTS。 - harmonyApp:HarmonyOS 原生工程
* `entry/src/main/ets`: ArkTS UI 界面,作为应用入口。 - entry/src/main/cpp:NAPI 桥接层(示例:AVCodecKit 导出),CMake 以 IMPORTED 链接 `libkn.so`
- entry/src/main/ets:ArkTS UI 与页面逻辑
- iosApp:Xcode 工程(与本文无关)
参考文件
- Kotlin `@CName` 示例:[AVCapability.kt](file:///Users/liluo/mine/projects/kmp/kmptpc_kotlin_capi_sample_ir001_sr002/composeApp/src/ohosArm64Main/kotlin/com/example/test/AVCodecKit/AVCapability.kt)
- NAPI 导出示例:[AVCodecKit.cpp](file:///Users/liluo/mine/projects/kmp/kmptpc_kotlin_capi_sample_ir001_sr002/harmonyApp/entry/src/main/cpp/kits/AVCodecKit.cpp)
- CMake 链接:[CMakeLists.txt](file:///Users/liluo/mine/projects/kmp/kmptpc_kotlin_capi_sample_ir001_sr002/harmonyApp/entry/src/main/cpp/CMakeLists.txt)
- ETS 入口:[ResourceAbility.ets](file:///Users/liluo/mine/projects/kmp/kmptpc_kotlin_capi_sample_ir001_sr002/harmonyApp/entry/src/main/ets/entryability/ResourceAbility.ets)
## 2. 环境要求 ## 2. 环境要求
* **IDE**: - IDE
* IntelliJ IDEA / Android Studio (用于编写 Kotlin 代码) - Android Studio(用于 Gradle 同步与 KMP 构建,推荐 2024.2+)
* DevEco Studio (用于编译和运行 HarmonyOS 应用) - DevEco Studio(用于 Harmony 工程构建与真机调试,推荐最新稳定版)
* **SDK**: - SDK/工具
* HarmonyOS SDK (API 12+ / OpenHarmony 5.0+) - **必须支持 ArkUI Native Node API** - OpenHarmony/HarmonyOS SDK:API 12+(示例工程使用 6.0.1 target 21)
* **语言**: - Gradle Wrapper:项目内置 `./gradlew`
* Kotlin 1.9+ - JDK:17
* Java 17+ - HDC:随 DevEco Studio 安装
* C++ (CMake)
## 3. 启动与运行(以 C-API 能力为对象)
## 3. 如何启动运行
### 步骤 1:在 Android Studio 同步并发布 KMP 产物
### 步骤 1: 编译 Kotlin/Native 产物 - 打开项目根目录,等待 Gradle Sync 完成
在项目根目录下运行以下命令,将 Kotlin 代码编译为动态库(`libkn.so`)并发布到 HarmonyOS 工程中: - 在 Gradle 工具窗口执行:
- `composeApp > Tasks > publishDebugBinariesToHarmonyApp`
-`publishReleaseBinariesToHarmonyApp`
- 期望结果:
- `harmonyApp/entry/libs/arm64-v8a/libkn.so` 存在
- `harmonyApp/entry/src/main/cpp/include/libkn_api.h` 存在
命令行等价:
```bash ```bash
./gradlew publishDebugBinariesToHarmonyApp ./gradlew publishDebugBinariesToHarmonyApp
# 或
./gradlew publishReleaseBinariesToHarmonyApp
``` ```
*此任务会自动将生成的 `.so` 文件和头文件复制到 `harmonyApp/entry/libs` 和 `harmonyApp/entry/src/main/cpp/include` 目录。*
### 步骤 2: 运行 HarmonyOS 应用
1. 使用 **DevEco Studio** 打开 `harmonyApp` 目录。
2. 等待 Sync 完成。
3. 连接 HarmonyOS 真机或启动模拟器(API 12+)。
4. 点击 **Run 'entry'**
---
## 4. 核心功能详解:Kotlin 调用 HarmonyOS Native 弹窗
本项目成功实现了一个复杂的场景:**在 C++ 层构建 UI 节点树,传递给 Kotlin 层,再由 Kotlin 层调用 ArkUI C-API 显示原生自定义弹窗。**
### 实现原理
1. **C++ (ArkUI Node API)**: 动态创建 UI 组件树(Column, Row, Text, Button)。
2. **Kotlin/Native (Interop)**: 接收 C++ 创建的 Node 指针,封装为 `ArkUI_CustomDialogOptions`
3. **ArkUI Dialog API**: 调用 `OH_ArkUI_CustomDialog_OpenDialog` 显示弹窗。
### 详细实现步骤
#### 1. C++ 侧构建复杂 UI (`entry_module.cpp`)
我们在 C++ 中使用 `ArkUI_NativeNodeAPI_1` 手动构建一个包含标题栏、内容区和底部按钮的复杂布局。
```cpp
// 示例:创建复杂的弹窗内容节点
ArkUI_NodeHandle root = api->createNode(ARKUI_NODE_COLUMN);
// 设置 Padding
ArkUI_NumberValue paddingValue[] = {{.f32 = 12.0f}, {.f32 = 12.0f}, ...};
api->setAttribute(root, NODE_PADDING, &paddingItem);
// 创建标题栏 (Row) ### 步骤 2:在 DevEco Studio 构建并运行 Harmony 工程
ArkUI_NodeHandle titleRow = api->createNode(ARKUI_NODE_ROW); - 打开 `harmonyApp` 目录,等待 hvigor/ohpm 同步
ArkUI_NodeHandle titleText = CreateTextNode(api, "标题", 20.0f); - 校验 CMake IMPORTED 链接是否正确指向 `libkn.so`
ArkUI_NodeHandle closeBtn = CreateTextNode(api, "X", 20.0f); - 连接真机或模拟器(API 12+)
- 选择 `entry` 模块,点击 Run,观察日志与 UI 行为
// 注册点击事件 (关闭弹窗) ## 4. 通用互操作模式(如何扩展到更多 C-API)
api->registerNodeEvent(closeBtn, NODE_ON_CLICK, 0, nullptr);
api->addNodeEventReceiver(closeBtn, OnCloseDialog); // OnCloseDialog 调用 closeBasicDialog()
// ... 组装节点树 ... 1.`composeApp/src/ohosArm64Main` 中为目标 C-API 编写 Kotlin 封装
api->addChild(root, titleRow); - 通过 cinterop 绑定原生头文件
api->addChild(root, contentText); - 定义 `@CName("...")` 函数作为对外稳定接口
api->addChild(root, buttonRow); 2.`harmonyApp/entry/src/main/cpp/kits` 中添加或更新 NAPI 导出
-`@CName` 对应的函数创建 JavaScript/ArkTS 可调用的导出
3. 执行发布任务,将 `.so` 与头文件复制到 Harmony 工程
4. 在 ArkTS 页面中通过 NAPI 导出进行调用与展示
// 将生成的根节点指针转换为 long 传递给 Kotlin 说明:当前 demo 仅保留了 AV 能力查询相关的 28 项导出用于演示;工程结构允许你以相同模式扩展到任意 C-API 模块。
openBasicDialog(reinterpret_cast<int64_t>(root));
```
#### 2. Kotlin 侧定义弹窗逻辑 (`OhosExports.kt`)
Kotlin 侧通过 `@CName` 暴露接口给 C++,接收 Node 指针,并配置弹窗样式。
```kotlin
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
@CName("openBasicDialog")
fun openBasicDialog(nodeAddress: Long): Int {
// 1. 将 long 转换回 C 指针
val node = if (nodeAddress == 0L) null else nodeAddress.toCPointer<COpaque>()
if (node == null) return -31
// 2. 创建弹窗选项 (Options)
val options = OH_ArkUI_CustomDialog_CreateOptions(node)
// 3. 配置弹窗样式 (去除默认大圆角,设置白底)
OH_ArkUI_CustomDialog_SetModalMode(options, true)
OH_ArkUI_CustomDialog_SetCornerRadius(options, 10f, 10f, 10f, 10f) // 设置 10vp 圆角
OH_ArkUI_CustomDialog_SetBackgroundColor(options, 0xFFFFFFFF.toUInt()) // 白色背景
// 4. 定义回调 (DialogId 用于关闭)
val cb = staticCFunction { dialogId: Int ->
lastDialogId = dialogId // 保存 ID 用于后续关闭
Unit
}
// 5. 打开弹窗
val code = OH_ArkUI_CustomDialog_OpenDialog(options, cb)
return code
}
```
#### 3. C++ 侧事件处理与关闭
当用户点击 C++ 创建的按钮时,触发回调,调用 Kotlin 暴露的关闭函数。
```cpp
// entry_module.cpp
void OnCloseDialog(ArkUI_NodeEvent* event) {
closeBasicDialog(); // 调用 Kotlin 暴露的关闭函数
}
// OhosExports.kt
@CName("closeBasicDialog")
fun closeBasicDialog(): Int {
return OH_ArkUI_CustomDialog_CloseDialog(lastDialogId)
}
```
#### 4. ArkTS 入口调用
最终在 ArkTS 侧,只需要简单地调用 NAPI 暴露的方法即可触发整个流程。
```typescript
// Index.ets
import { openBasicDialog } from 'libentry.so';
Button("Options样式弹窗")
.onClick(() => {
// 触发 C++ -> Kotlin -> ArkUI Native 的调用链
openBasicDialog();
})
```
## 5. 常见问题排查 ## 5. 常见问题排查
* **Crash (SIGSEGV)**: 通常是因为传递了无效的 Node 指针给 `OH_ArkUI_CustomDialog_CreateOptions`。请确保 Node 已正确创建且未被释放。 - `.so` 或头文件缺失:未执行发布任务或路径不正确
* **弹窗样式异常(大圆角/透明)**: 默认系统样式可能带有较大的圆角。使用 `OH_ArkUI_CustomDialog_SetCornerRadius``OH_ArkUI_CustomDialog_SetBackgroundColor` 进行显式配置。 - NAPI 映射异常:导出函数名与 `@CName` 不一致
* **Error -31**: 自定义错误码,表示传入的 Node 指针为空。 - 设备构建失败:SDK 版本与 `build-profile.json5` 不匹配
--- ---
Happy Coding with KMP & HarmonyOS! 欢迎基于本工程快速集成与验证更多 HarmonyOS C-API 能力。*** End Patch***)}$** End Patch*** }$ ?
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment