## 项目介绍

该项目是一个 Kotlin Multiplatform（KMP）示例工程，目标是在保持一套共享业务逻辑的前提下，分别通过各平台原生方式进行集成与展示，支持 Android / iOS / HarmonyOS 三端。

## 工程结构与关键模块
- `composeApp/`：KMP 共享业务与平台桥接
  - `commonMain/`：平台无关逻辑（如 `appInfo()`）
  - `androidMain/`、`iosMain/`、`ohosArm64Main/`：各平台的 `actual` 实现与桥接代码
  - iOS：
    - `MainViewController.kt` 用于向 Swift 暴露 `UIViewController`
    - `App_ios.kt` 暴露 `iosUiText()`（含 `Arith.add/sub` 结果）
  - Harmony：
    - `OhosEntry.kt` 暴露 `@CName("getKotlinUiText")` 供 NAPI 调用
    - `StaticLibDemo.kt` 演示调用 C 静态库 `libsample.a`（包含算术运算与嵌入图片资源获取）
    - `OhosPlatform.kt` 平台标识实现
- `lib-arith/`：跨平台算术库（add/sub）
  - 仅在 `androidMain` 与 `iosMain`、`ohosArm64Main` 依赖（不放在 `commonMain`）
- `lib-base/`、`lib-analytics/`：独立的 KMP 业务模块
  - 通过 `build.gradle.kts` 独立管理，在 `settings.gradle.kts` 统一注册
- `iosApp/`：SwiftUI App，链接 `ComposeApp.framework`
- `harmonyapp/`：ArkTS App，NAPI 桥接 `libkn.so` 与 UI 展示

## 环境准备
- Android Studio：选择 Gradle JDK 为 JDK 17（IDE 内配置，无需写死路径）
- Kotlin/Java 版本建议：
  - Kotlin `jvmToolchain(17)` 用于编译工具链
  - 应用 `jvmTarget` 与 `compileOptions` 建议统一为 Java 11
- iOS：Xcode 15+，建议使用通用 `FRAMEWORK_SEARCH_PATHS`，避免绑定到具体版本目录
- Harmony：DevEco Studio 6.0，ArkTS + NAPI，需可用的 aarch64 交叉工具链

## 构建与运行（Android）
- 运行（AS 中选择 Android 目标设备，正常运行）

## 构建与运行（iOS）
- 生成模拟器框架（arm64 模拟器）
```bash
./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64
```
- 生成真机框架（arm64 真机）
```bash
./gradlew :composeApp:linkDebugFrameworkIosArm64
```
- Xcode 链接框架
  - Linked Frameworks and Libraries 添加 `ComposeApp.framework`，设置为 “Embed & Sign”
  - 推荐 `FRAMEWORK_SEARCH_PATHS`：
    - `$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(PLATFORM_NAME)`
  - 避免使用带版本号目录（例如 `iphonesimulator18.2`），并在 `project.pbxproj` 或 `Config.xcconfig` 中将 `IPHONEOS_DEPLOYMENT_TARGET` 设为你需要的最低版本（如 `17.0`）
- Swift 调用示例（`ContentView.swift`）
```bash
# Swift 中示例调用：
# import ComposeApp
# Text(ComposeAppApp_iosKt.iosUiText())
```

## 构建与运行（HarmonyOS）

- 命令一：仅构建共享库（不拷贝到 Harmony 工程）
  - 作用：编译 KMP 的 Harmony 目标，生成 `libkn.so` 与 `libkn_api.h`
  - 产物目录：`composeApp/build/bin/ohosArm64/debugShared/`

```bash
./gradlew :composeApp:linkDebugSharedOhosArm64
```

- 命令二：构建并发布到 Harmony 工程（自动拷贝）
  - 作用：先执行上面的构建，再把产物拷贝到 `harmonyapp/entry` 便于 ArkTS/NAPI 直接使用
  - 拷贝目标：
    - 头文件：`harmonyapp/entry/src/main/cpp/include/libkn_api.h`
    - 动态库：`harmonyapp/entry/libs/arm64-v8a/libkn.so`

```bash
./gradlew :composeApp:publishDebugBinariesToHarmonyApp
```

### StaticLib 集成与验证（libarith.a）

- 生成与链接
  - `entry/src/main/cpp/CMakeLists.txt` 中定义 `add_library(arith STATIC arith/arith.cpp)`，构建时会生成 `libarith.a` 并链接到 `libentry.so`：
    - `add_library(entry SHARED napi_init.cpp)`
    - `target_link_libraries(entry PRIVATE arith)`
  - `napi_init.cpp` 的 `Add` 方法通过静态库函数 `arith_add` 进行计算，ArkTS 页面以 `testNapi.add(2, 3)` 验证 staticLib 集成。

- 产物位置
  - `libarith.a` 是构建中间产物，不会被拷贝到 `entry/libs/`，常见位置为 `entry` 模块的本地构建目录，例如：
    - `harmonyapp/entry/**/obj/arm64-v8a/libarith.a`（具体路径由 Hvigor/CMake 版本决定）
  - 可用命令定位：
```bash
find harmonyapp/entry -name "libarith.a"
```

### Stdlib 演示（Kotlin 标准库：List 等）

- commonMain 中的演示函数（`composeApp/src/commonMain/kotlin/com/example/testdemo/StdlibDemo.kt`）
  - `stdlibListDemo()`：`List` 过滤、`map`、`joinToString`
  - `stdlibAggregateDemo()`：`groupBy` 统计长度、`map/sorted/joinToString` 聚合展示

## 常见问题与解决方案

### HarmonyOS 静态库链接报错
**现象：**
在执行 `hvigor build` 或链接阶段报错：
```
ld.lld: error: .../libsample.a: archive member '/' is neither ET_REL nor LLVM bitcode
clang++: error: linker command failed with exit code 1
```

**原因：**
macOS 默认的 `ar` 工具生成的静态库格式（BSD format）与 HarmonyOS 工具链（期望 GNU format）不兼容。

**解决：**
在 Gradle 构建脚本中强制使用 HarmonyOS SDK 自带的 `llvm- `composeApp/build.gradle.kts` 配置示例：
```kotlin
val llvmAr = "/Applications/DevEco-Studio.app/Contents/sdk/default/hms/native/BiSheng/bin/llvm-ar"
exec {
    if (file(llvmAr).exists()) {
        commandLine(llvmAr, "rcs", staticLib, objFile)
    } else {
        commandLine("ar", "rcs", staticLib, objFile)
    }
}
```

### StaticLib 集成与验证（klib + .a）
- `@CName("getStdlibListDemo") fun getStdlibListDemo(): String = stdlibListDemo()`
- `@CName("getStdlibAggregateDemo") fun getStdlibAggregateDemo(): String = stdlibAggregateDemo()`

- NAPI 封装（`harmonyapp/entry/src/main/cpp/napi_init.cpp`）
  - 通过 `dlopen("libkn.so")` + `dlsym(...)` 获取上述符号
  - 导出 ArkTS 可调用的 `getStdlibListDemo()`、`getStdlibAggregateDemo()` 方法

- ArkTS 类型声明
  - `harmonyapp/entry/src/main/cpp/types/libentry/Index.d.ts` 必须包含：
    - `export const getStdlibListDemo: () => string;`
    - `export const getStdlibAggregateDemo: () => string;`
  - 页面调用示例（`harmonyapp/entry/src/main/ets/pages/Index.ets`）：
    - `const l: string = testNapi.getStdlibListDemo();`
    - `const a: string = testNapi.getStdlibAggregateDemo();`

### StaticLib 集成与验证（klib + .a）

- 目标  
  通过 Kotlin/Native cinterop 将 `.a` 静态库合入到 `ohosArm64` 目标的 klib 中，并在最终 `libkn.so` 导出 `@CName("getStaticLibSum")`，供鸿蒙端 NAPI/ArkTS 验证。

- 文件与配置
  - C 静态库源码与头文件
    - 头文件：[composeApp/native-libs/sample/include/sample.h](kmptcp_kotlin_sample/composeApp/native-libs/sample/include/sample.h)
    - 源码：[composeApp/native-libs/sample/src/sample.c](kmptcp_kotlin_sample/composeApp/native-libs/sample/src/sample.c)
  - cinterop 定义  
    [composeApp/src/nativeInterop/cinterop/sample.def](kmptcp_kotlin_sample/composeApp/src/nativeInterop/cinterop/sample.def)  
    关键项：`staticLibraries = libsample.a`、`libraryPaths = ${projectDir}/composeApp/native-libs/ohosArm64`、`compilerOpts = -I.../include`
  - Kotlin 调用与导出  
    [composeApp/src/ohosArm64Main/kotlin/com/example/testdemo/StaticLibDemo.kt](kmptcp_kotlin_sample/composeApp/src/ohosArm64Main/kotlin/com/example/testdemo/StaticLibDemo.kt)
    - `@CName("getStaticLibSum") fun getStaticLibSum(a: Int, b: Int): Int`
    - 通过 cinterop 绑定的 `sample_add` 调用 `.a` 中实现
  - Gradle 构建任务  
    [composeApp/build.gradle.kts](kmptcp_kotlin_sample/composeApp/build.gradle.kts)
    - `ohosArm64` 下新增 `cinterops { sample { defFile(".../sample.def") } }`
    - 任务 `buildSampleStaticLib` 使用 `clang -arch arm64 -fPIC` 与 `ar rcs` 生成 `libsample.a`
    - 将 `linkDebugSharedOhosArm64` 等链接任务依赖 `buildSampleStaticLib`
  - NAPI 导出  
    [harmonyapp/entry/src/main/cpp/napi_init.cpp](kmptcp_kotlin_sample/harmonyapp/entry/src/main/cpp/napi_init.cpp)
    - `dlsym(..., "getStaticLibSum")` 并封装为 `NAPI_Global_getStaticLibSum`
    - 在 `Init` 中注册 `{ "getStaticLibSum", ..., NAPI_Global_getStaticLibSum }`
  - ArkTS 类型声明  
    [harmonyapp/entry/src/main/cpp/types/libentry/Index.d.ts](kmptcp_kotlin_sample/harmonyapp/entry/src/main/cpp/types/libentry/Index.d.ts)
    - `export const getStaticLibSum: (a: number, b: number) => number;`
  - 页面调用  
    [harmonyapp/entry/src/main/ets/pages/Index.ets](kmptcp_kotlin_sample/harmonyapp/entry/src/main/ets/pages/Index.ets)
    - 将 `refreshSum()` 改为调用 `testNapi.getStaticLibSum(2, 3)` 并显示结果

- 构建与验证
  - 生成静态库 `.a`
    ```bash
    ./gradlew :composeApp:buildSampleStaticLib -x test
    ```
    成功后产物位于：`composeApp/native-libs/ohosArm64/libsample.a`
  - 链接 ohosArm64 并发布到鸿蒙工程
    ```bash
    ./gradlew :composeApp:linkDebugSharedOhosArm64 -x test
    ./gradlew :composeApp:publishDebugBinariesToHarmonyApp -x test
    ```
  - 运行 HAP，页面“静态库计算”卡片应显示：`add(2, 3) = 5`

### StaticLib 复用到 Harmony CMake（libsample.a）

- 目标  
  复用 `composeApp` 侧构建的 `libsample.a`，在 Harmony 原生 `CMakeLists.txt` 中以 IMPORTED 静态库的方式链接到 `libentry.so`，并通过 NAPI/ArkTS 直接调用 `sample_add`。

- 拷贝与产物位置
  - Gradle 任务 `publishDebugBinariesToHarmonyApp` / `publishReleaseBinariesToHarmonyApp` 在拷贝 `libkn.so` 的同时，额外拷贝：
    - 源：`composeApp/native-libs/ohosArm64/libsample.a`
    - 目标：`harmonyapp/entry/src/main/cpp/libs/libsample.a`
  - 配置位置：[composeApp/build.gradle.kts](kmptcp_kotlin_sample/composeApp/build.gradle.kts#L130-L147)

- CMake 链接
  - 在 [harmonyapp/entry/src/main/cpp/CMakeLists.txt](kmptcp_kotlin_sample/harmonyapp/entry/src/main/cpp/CMakeLists.txt#L11-L20) 中新增 IMPORTED 静态库，并链接到 `entry`：
    - `add_library(sample STATIC IMPORTED)`
    - `set_target_properties(sample PROPERTIES IMPORTED_LOCATION "${NATIVERENDER_ROOT_PATH}/libs/libsample.a")`
    - `target_link_libraries(entry PRIVATE arith sample)`

- NAPI 封装
  - 在 [harmonyapp/entry/src/main/cpp/napi_init.cpp](kmptcp_kotlin_sample/harmonyapp/entry/src/main/cpp/napi_init.cpp) 中：
    - 声明 C 接口：`extern "C" int sample_add(int a, int b);`
    - 新增封装函数 `NAPI_Global_getSampleAdd`，内部调用 `sample_add` 完成加法并返回 `napi_value`
    - 在 `Init` 中注册 `{ "getSampleAdd", ..., NAPI_Global_getSampleAdd }`

- ArkTS 类型与页面调用
  - 类型声明：[harmonyapp/entry/src/main/cpp/types/libentry/Index.d.ts](kmptcp_kotlin_sample/harmonyapp/entry/src/main/cpp/types/libentry/Index.d.ts)：
    - `export const getSampleAdd: (a: number, b: number) => number;`
  - 页面调用：[harmonyapp/entry/src/main/ets/pages/Index.ets](kmptcp_kotlin_sample/harmonyapp/entry/src/main/ets/pages/Index.ets)：
    - 状态字段：`@State sampleMsg: string = '等待 Sample 静态库结果...';`
    - UI 展示：在“静态库计算”卡片中新增 `sample_add(4, 5) = ${this.sampleMsg}`
    - 刷新逻辑：`refreshSampleAdd()` 中调用 `testNapi.getSampleAdd(4, 5)` 并将结果写入 `sampleMsg`

- 注意事项
  - 首次执行 Kotlin/Native 任务若提示 `~/.konan/.../.lock (Operation not permitted)`，需为用户库目录授予写权限或在 IDE 中运行构建。
  - `.a` 必须使用 `-fPIC` 以便安全地合入共享库。
  - 若在模拟器验证，需要提供 x64 架构的 `.a` 与 `libkn.so`。

### ohosArm64 SharedLib / StaticLib 产物（libkn.so / libkn.a）

- 目标  
  在 `ohosArm64` 目标下同时生成共享库 `libkn.so` 和静态库 `libkn.a`，二者都链接 `libsample.a`，便于在不同场景复用。

- Gradle 配置
  - 位置：[composeApp/build.gradle.kts](kmptcp_kotlin_sample/composeApp/build.gradle.kts#L13-L31)
  - 在 `ohosArm64 { binaries { ... } }` 下声明：
    - `sharedLib { baseName = "kn"; linkerOpts("-L.../native-libs/ohosArm64", "--whole-archive", "-lsample", "--no-whole-archive") }`
    - `staticLib { baseName = "kn"; linkerOpts("-L.../native-libs/ohosArm64", "--whole-archive", "-lsample", "--no-whole-archive") }`

- 构建命令与产物位置
  - 调试版共享库：
    ```bash
    ./gradlew :composeApp:linkDebugSharedOhosArm64 -x test
    ```
    - 产物：`composeApp/build/bin/ohosArm64/debugShared/libkn.so`
  - 调试版静态库：
    ```bash
    ./gradlew :composeApp:linkDebugStaticOhosArm64 -x test
    ```
    - 产物：`composeApp/build/bin/ohosArm64/debugStatic/libkn.a`
  - 发布版静态库：
    ```bash
    ./gradlew :composeApp:linkReleaseStaticOhosArm64 -x test
    ```
    - 产物：`composeApp/build/bin/ohosArm64/releaseStatic/libkn.a`

- 使用建议
  - `libkn.so`：通过 Harmony NAPI（`dlopen/dlsym`）供 ArkTS 调用（当前工程已集成）。
  - `libkn.a`：可在其他原生 CMake/NDK 工程中静态链接 KMP 导出的符号（需要自行设置 include 路径与链接选项）。


### 常见问题与排查

- `dlopen/dlsym libkn.so|<symbol> 失败`
  - 确认 `libkn.so` 已拷贝到 `harmonyapp/entry/libs/arm64-v8a/`（真机）或 `x86_64/`（模拟器）。
  - 确认架构匹配（arm64-v8a vs x86_64）。
  - 确认 Kotlin 侧导出使用了 `@CName("...")`，符号名与 NAPI 的 `dlsym` 一致。
  - 构建后重新安装 HAP（避免加载旧版本）。

- ArkTS 类型报错或属性不存在
  - 以 `src/main/cpp/types/libentry/Index.d.ts` 为主，补齐 NAPI 导出函数的声明（`add`、`getKotlinUiText`、`getStdlibListDemo`、`getStdlibAggregateDemo`）。
  - 页面调用时加显式类型（`const v: number = ...`），满足 lint 规则。

- `libarith.a` 未找到
  - 这是构建中间产物，被链接进 `libentry.so`；需要在本地构建目录中查找：
```bash
find harmonyapp/entry -name "libarith.a"
```

- 运行效果验证
  - 执行 `hvigor build` 后安装运行 HAP，页面展示：
    - Kotlin 文案：`getKotlinUiText()` 的返回内容
    - 静态库加法结果：`add(2, 3) = 5`

### 类型声明与编译规则（ArkTS）

- 模块类型文件
  - ArkTS 编译器使用 `harmonyapp/entry/src/main/cpp/types/libentry/Index.d.ts` 作为 NAPI 导出类型源。确保其中包含：
    - `export const getKotlinGreeting: () => string;`
    - `export const getKotlinUiText: () => string;`
    - `export const add: (a: number, b: number) => number;`（用于 staticLib 的加法）

- 显式类型要求
  - 受 `arkts-no-any-unknown` 规则影响，页面中建议使用显式类型注解。例如：
    ```ts
    const v: number = testNapi.add(2, 3);
    ```

## 常见问题与排查
- iOS “import ComposeApp: no such module”
  - 未链接当前架构框架或搜索路径错误。检查 `FRAMEWORK_SEARCH_PATHS` 与 `Embed & Sign`
- iOS 模拟器版本被锁到 18.2
  - 在 `project.pbxproj` 有 `IPHONEOS_DEPLOYMENT_TARGET = 18.2;`；建议迁移到 `Config.xcconfig` 设置通用版本（如 `17.0`）
- KMP iOS C-Interop（UIKit）缺少 Opt-In
  - 在 `MainViewController.kt` 顶部添加 `@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)`
- Harmony NAPI 接口 undefined 或类型不匹配
  - 确认 `napi_init.cpp` 已注册并导出 `add`、`getKotlinUiText`；`CMakeLists.txt` 包含对应源并链接静态库。
  - 确认 `src/main/cpp/types/libentry/Index.d.ts` 已包含 `add` 的类型定义；页面调用使用显式类型（如 `const v: number`）。
- Gradle JDK 路径不应写死
  - 删除 `gradle.properties` 的 `org.gradle.java.home`，在 IDE 中选择 JDK 17；在 Gradle 脚本里使用 `jvmToolchain(17)` 保证一致性
- Failed to transform kotlin-native-prebuilt-2.2.21-ohos-01-macos-x86_64.tar.gz问题是 Gradle/Kotlin 插件错误地检测成了 x86_64
  - 在 `gradle.properties`中添加`systemProp.os.arch=aarch64`，强制使用arm版本。


## 问题总结与修复记录

- ~/.konan 权限导致锁文件不可写
  - 现象：`java.io.FileNotFoundException: ~/.konan/.../.lock (Operation not permitted)`
  - 原因：用户目录下的 Kotlin/Native 缓存锁文件权限异常（可能之前用 sudo 构建）
  - 修复建议：
    - `./gradlew --stop && pkill -f kotlin`
    - `sudo chown -R $(whoami) ~/.konan && sudo chmod -R u+w ~/.konan`
    - 如有隔离属性：`xattr -r -d com.apple.quarantine ~/.konan 2>/dev/null`

- cinterop 头文件找不到（sample.h file not found）
  - 现象：`fatal error: 'sample.h' file not found`
  - 原因：.def 中 headers 写死或相对路径不受 -I 解析；临时编译单元无法定位头文件
  - 修复：
    - 将头文件放置在源码路径：[include/sample.h](kmptcp_kotlin_sample/composeApp/src/nativeInterop/cinterop/include/sample.h)
    - .def 中使用相对声明：[sample.def](kmptcp_kotlin_sample/composeApp/src/nativeInterop/cinterop/sample.def)：
      - `headers = sample.h`
      - `headerFilter = sample.h`
    - Gradle 配置 includeDirs 与 compilerOpts：
      - [build.gradle.kts:ohosArm64.cinterops](kmptcp_kotlin_sample/composeApp/build.gradle.kts#L13-L19)

- 避免写死绝对路径（/Users/...）
  - 措施：
    - .def 使用相对 headers 与 headerFilter
    - Gradle 通过 `${project.projectDir}` 拼接 -I/-L，在构建时解析
    - README 链接使用相对/项目路径引用，避免主机路径写死

- 静态库链接位置与 cinterop 警告
  - 现象：cinterop 警告 `-linker-option(s) not supported by cinterop`
  - 结论：链接选项应在二进制阶段添加，不在 cinterop 阶段
  - 修复：在 sharedLib 二进制配置添加：
    - `linkerOpts("-L${project.projectDir}/native-libs/ohosArm64", "-lsample")`
    - 位置：[build.gradle.kts:binaries.sharedLib](kmptcp_kotlin_sample/composeApp/build.gradle.kts#L16-L19)

- JDK 版本不匹配导致 Gradle 插件解析失败
  - 现象：`Dependency requires at least JVM runtime version 11. This build uses a Java 8 JVM`
  - 修复：确保 Gradle/IDE 使用 JDK 17（或至少 11）
    - 命令行示例：`JAVA_HOME=/path/to/jdk-17 ./gradlew ...`
    - IDE 中设置 Gradle JDK 为 17

- Kotlin/Native API 使用需 Opt-In
  - 现象：编译错误提示需要 `ExperimentalNativeApi` 或 `ExperimentalForeignApi`
  - 修复：在 Kotlin 文件顶端添加：
    - `@file:OptIn(ExperimentalNativeApi::class, ExperimentalForeignApi::class)`
    - 位置：[StaticLibDemo.kt](kmptcp_kotlin_sample/composeApp/src/ohosArm64Main/kotlin/com/example/testdemo/StaticLibDemo.kt#L1-L13)

- Kotlin/Native 生成 C 代码的编译器告警
  - 现象：`non-void function does not return a value`、`C99 designated initializer` 混合等警告
  - 结论：为 Kotlin/Native 自动生成代码的已知告警，不影响当前功能；可忽略

- 构建结果
  - 已成功执行：
    - `:composeApp:cinteropSampleOhosArm64`
    - `:composeApp:linkDebugSharedOhosArm64`
    - `:composeApp:publishDebugBinariesToHarmonyApp`
  - 页面验证：`getStaticLibSum(2, 3)` 正常显示 5

- 运行时崩溃 SIGSEGV (0x00000000000d89e0 Not mapped)
  - 现象：调用静态库方法时 Crash，堆栈显示在 `libkn.so` 内偏移处崩溃，nm 查看 `sample_add` 符号为 `U` (Undefined)
  - 原因：
    1. macOS 上 `clang` 默认生成 Mach-O 格式目标文件，而 HarmonyOS 需要 ELF 格式。链接器忽略了格式错误的 .a 文件。
    2. 静态库符号被链接器视为未使用而剥离（dead stripping）。
  - 修复：
    - 编译静态库时指定 `-target aarch64-linux-gnu` 以生成 ELF。
    - 链接时使用 `--whole-archive` 强制包含静态库所有符号：
      - `linkerOpts(..., "--whole-archive", "-lsample", "--no-whole-archive")`
