从 0 开始:用 KMP&CMP 开发鸿蒙应用|从 Kotlin 到 HAP,中间发生了什么
从 0 开始 #02
Kotlin 到 HAP,一份代码怎样进入鸿蒙
目录职责、构建命令与产物说明
第 1 篇里,我们通过 Android Studio 插件创建工程,让 Hello HarmonyOS!跑到了鸿蒙真机上。
应用能启动之后,项目里会留下几个容易让人困惑的目录:composeApp、harmonyApp。代码明明写在 Kotlin 文件里,最后为什么会变成鸿蒙设备上的 HAP?这一篇就沿着直播里的示例工程,把这条路径拆开看。
下文会先分清 composeApp(以及 commonMain/ ohosMain) / harmonyApp各自负责什么,再说明 Kotlin / Compose 怎样编成 libkn.so、怎样被鸿蒙壳加载进 HAP,以及日常「改了哪个目录的文件,要跑什么命令才生效」。
01 先分清四块职责
仓库里通常有两个「工程」:Android Studio 打开的跨端工程(含 composeApp模块),以及 DevEco Studio 打开的鸿蒙壳工程(harmonyApp)。
职责不同,工具也不同,不要混着改。
工程根目录下 composeApp与 harmonyApp并列,如下图所示:

〔项目根目录,composeApp 与 harmonyApp 并列〕
1.composeApp:跨端业务与 UI(Kotlin / Compose)
composeApp是 KMP 模块,负责「页面长什么样、业务逻辑是什么」。真机上看到的 Compose 界面,主体代码都在这里;由 Gradle 编成鸿蒙用的 libkn.so,不是 DevEco 直接编 Kotlin。
· 面向多端:同一套 common 代码可被 Android / iOS / 鸿蒙复用。
· 鸿蒙相关源码主要在 commonMain+ ohosMain。
· 构建产物:libkn.so+ libkn_api.h(再经 publish 拷进 harmonyApp)。
2.commonMain:真正跨端,日常优先只改这里
路径:composeApp/src/commonMain/
· 放可复用的 Compose UI、业务、状态。demo 里如 App.kt、Greeting.kt。
· 放跨端资源:composeResources(图片等)。
· 可声明 expect(如 Platform/ getPlatform),具体实现放到各端 Main。
· 不要写鸿蒙 NAPI、不要写 ArkTS;不要依赖只能在某一端用的 API。
Hello HarmonyOS!例子:Greeting.greet()在 commonMain里拼出 Hello, ${platform.name}!;点按钮后显示的文案骨架就在这里。
Android Studio:commonMain下的 App.kt/ Greeting.kt如下图所示:

〔Android Studio 中 commonMain 下的 App.kt 与 Greeting.kt〕
3.ohosMain:鸿蒙专用「入口与平台胶水」
路径:composeApp/src/ohosMain/
· 把 commonMain的 UI「挂」到鸿蒙视图体系上。
· 核心入口 MainArkUIViewController:@CName导出 C 符号,内部 ComposeArkUIViewController { App() }。
· 放鸿蒙 actual(demo 里 Platform.ohos.kt把 name 写成 HarmonyOS)。
· 还可放仅鸿蒙需要的 cinterop / 平台 API 封装。
Hello HarmonyOS!里的「HarmonyOS」三个字,就来自 ohosMain的 actual;commonMain只负责模板,ohosMain负责填平台名。
kotlin
// ohosMain/.../Platform.ohos.kt
override val name: String = "HarmonyOS"
// ohosMain/.../MainArkUIViewController.kt
@CName("MainArkUIViewController")
fun MainArkUIViewController(env: napi_env): napi_value {
initMainHandler(env)
return ComposeArkUIViewController(env) { App() }
}
一般很少改;只有换入口名、改初始化、加鸿蒙专属逻辑或改 actual 时才动。
Android Studio:ohosMain下的 MainArkUIViewController.kt、Platform.ohos.kt如下图所示:

〔Android Studio 中 ohosMain 下的 MainArkUIViewController.kt 与 Platform.ohos.kt〕
4.harmonyApp:鸿蒙壳工程(ArkTS + Native 桥)
路径:仓库根目录 harmonyApp/,用 DevEco Studio 打开。
· 负责应用壳:Ability、页面路由、权限、打包 HAP、安装运行。
· 负责把 libkn.so链进 entry,并通过 NAPI 把 MainArkUIViewController暴露给 ArkTS。
· 页面里用 Compose { controller }把跨端 UI 嵌进 ArkUI;转发生命周期与返回键。
这里一般不写业务 UI(业务在 commonMain)。Index.ets只是「创建 controller → 挂 Compose」,不会自己画 Hello HarmonyOS!那套按钮界面。
DevEco:harmonyApp工程与 Index.ets如下图所示:

〔DevEco 中打开 harmonyApp 工程,并展示 Index.ets〕
对照一览:
|
模块 / 目录 |
负责什么 |
用什么工具 |
|---|---|---|
|
composeApp / commonMain |
跨端 Compose UI、业务、共享资源、expect |
Android Studio |
|
composeApp / ohosMain |
鸿蒙入口导出、actual、平台胶水 |
Android Studio |
|
composeApp(Gradle) |
编译 ohosArm64,产出并拷贝 so / h / 资源 |
执行 link / publish 任务 |
|
harmonyApp |
ArkTS 壳、NAPI / CMake、HAP 安装运行 |
DevEco Studio |
02 代码怎样变成 libkn.so,又怎样被鸿蒙加载
以 Hello HarmonyOS!为例,记成一条单向链路:
kotlin → hap
commonMain:App() + Greeting(Hello, …!)
ohosMain:Platform.name = HarmonyOS
+ MainArkUIViewController @CName
↓ Gradle :composeApp:link*SharedOhosArm64
libkn.so + libkn_api.h
↓ publish*BinariesToHarmonyApp 拷贝进 harmonyApp
entry/libs/arm64-v8a/libkn.so
entry/src/main/cpp/include/arm64-v8a/libkn_api.h
(顺带同步 composeResources → rawfile)
↓ CMake 链接 libkn.so;napi_init 注册导出符号
ArkTS Index.ets:nativeApi.MainArkUIViewController()
↓
Compose { controller } → 真机显示 Click me! / Hello, HarmonyOS!
三点务必记住:
· 业务 UI 编进 so;鸿蒙壳只负责创建 controller 并挂到页面上。
· libkn.so与 libkn_api.h必须来自 同一次 link,不能混批次。
· Kotlin @CName、napi 注册名、Index.d.ts声明,三处名字必须一致(demo 均为 MainArkUIViewController)。
03 核心 Gradle 命令
1.只编译,生成 so / h
在跨端工程根目录执行:
shell
./gradlew :composeApp:linkDebugSharedOhosArm64
Release:
shell
./gradlew :composeApp:linkReleaseSharedOhosArm64
产物位置:
build output
composeApp/build/bin/ohosArm64/debugShared/
├── libkn.so # 共享库(业务 + Compose 运行时 + 导出入口)
└── libkn_api.h # C 头文件(给 napi_init.cpp 用)
composeApp/build/bin/ohosArm64/releaseShared/
├── libkn.so # 共享库(业务 + Compose 运行时 + 导出入口)
└── libkn_api.h # C 头文件(给 napi_init.cpp 用)
库名 kn 来自 composeApp/build.gradle.kts中 binaries.sharedLib { baseName = "kn" }。改 baseName,文件名会跟着变。
终端 / Gradle 执行 linkDebugSharedOhosArm64成功日志如下图所示:

〔执行 linkDebugSharedOhosArm64 的成功日志〕
产物目录中的 libkn.so与 libkn_api.h如下图所示:

〔build/bin/ohosArm64/debugShared 下的 libkn.so 与 libkn_api.h〕
2.编译并拷贝到鸿蒙工程(日常推荐)
shell
./gradlew :composeApp:publishDebugBinariesToHarmonyApp
Release:
shell
./gradlew :composeApp:publishReleaseBinariesToHarmonyApp
该任务会:
· 先执行对应的 link*SharedOhosArm64。
· 复制 libkn.so→ harmonyApp/entry/libs/arm64-v8a/
· 复制 libkn_api.h→ harmonyApp/entry/src/main/cpp/include/arm64-v8a/
· 同步 composeResources→ 鸿蒙 rawfile(图片等资源)。
终端 / Gradle 执行 publishDebugBinariesToHarmonyApp成功日志如下图所示:

〔执行 publishDebugBinariesToHarmonyApp 的成功日志〕
拷贝后的 libs/arm64-v8a与 include/arm64-v8a如下图所示:

〔harmonyApp 中拷贝后的 libs/arm64-v8a 与 include/arm64-v8a〕
04 libkn.so 和 libkn_api.h 分别是什么
1.libkn.so
Kotlin/Native 为 ohosArm64 链出的共享库,CMake 把它链进 entry。大致包含:
· commonMain的 Compose UI 与业务(含 Hello HarmonyOS!文案逻辑)。
· ohosMain的入口与 actual,以及 Compose 运行时。
· 渲染相关 Native 依赖(demo 默认 rendererBackend=fusion-renderer)。
Debug so 体积达数十 MB 属正常;发版用 Release。
2.libkn_api.h
C/C++ 声明头。napi_init.cpp通过它调用 so 里的符号,例如:
c
extern void* MainArkUIViewController(void* env);
extern void androidx_compose_ui_arkui_init(void* env, void* exports);
05 鸿蒙壳如何接上 so
依赖(ohpm,版本与 KMP Compose 对齐,如 1.9.2-0.3.0):
json5
// entry/oh-package.json5
"@cpf-kmp-cmp/compose": "1.9.2-0.3.0"
CMake 链接 libkn.so;napi_init 里 init + 注册 MainArkUIViewController;ArkTS 页面创建 controller 并挂 Compose。
ets
// Index.ets(示意)
this.controller = nativeApi.MainArkUIViewController()
Compose({ controller: this.controller, libraryName: "entry", ... })
页面需转发 onPageShow/ onPageHide/ onBackPress给 controller。
模拟器运行:Click me!后显示 Hello, HarmonyOS!如下图所示:

〔模拟器运行,点击 Click me! 后显示 Hello, HarmonyOS!〕
06 改了哪个目录,要怎样才生效
这是日常最实用的一节:先看文件落在哪,再决定要不要重新 publish、要不要在 DevEco 重跑。
原则只有一句——编进 libkn.so的东西变了,就必须重新 publish;只有鸿蒙壳变了,直接 DevEco Run。
1.按目录对照(保存文件后再操作)
|
你改了什么 |
需要做什么 |
说明 |
|---|---|---|
| commonMain
下 Kotlin / Compose(如 …/App.kt、Greeting.kt) |
① publishDebugBinariesToHarmonyApp ② DevEco 重新 Run |
UI / 逻辑在 so 里,必须重新 link 并拷贝 so;只在 AS 里编译不够 |
| commonMain/composeResources
(如 drawable/…) |
① publishDebugBinariesToHarmonyApp ② DevEco 重新 Run |
publish 会同步到 entry/.../rawfile/composeResources/;漏跑 publish 真机仍是旧图 |
| ohosMain
(如 MainArkUIViewController.kt、Platform.ohos.kt) |
① publishDebugBinariesToHarmonyApp ② DevEco 重新 Run |
改 Platform.name 也会进 so;若改了 @CName 符号名,还要同步改 napi_init.cpp 与 Index.d.ts |
| composeApp
构建配置(如 build.gradle.kts、libs.versions.toml、gradle.properties 等,含 rendererBackend) |
① publishDebugBinariesToHarmonyApp ② DevEco 重新 Run |
链接选项、依赖、渲染后端变更都会影响 so |
| harmonyApp
下 ArkTS(如 Index.ets、EntryAbility.ets) |
只需 DevEco 重新 Run(不必重新 publish so) |
没动 Kotlin / 资源就不用重编 so,可省几分钟 |
| harmonyApp
下 C++ / CMake(如 napi_init.cpp、CMakeLists.txt) |
DevEco 重新编译运行(未改 Kotlin 则不必 publish) |
若要换用新 so / 新头文件,仍须先 publish 再编 |
| harmonyApp
的 oh-package / 依赖版本 |
ohpm install 后 DevEco 重新 Run |
与 so 无关;别漏装 @cpf-kmp-cmp/compose |
|
只改了注释,或文件未保存 |
无 |
以磁盘已保存且参与编译的文件为准 |
2.推荐默认操作顺序
场景 A:
改了 commonMain/ ohosMain/ composeResources/ composeApp构建配置
shell
# 1)跨端工程根目录:重新出 so/h(及资源)并拷进鸿蒙工程
./gradlew :composeApp:publishDebugBinariesToHarmonyApp
# 2)用 DevEco Studio 打开 harmonyApp,Run 到设备 / 模拟器
# (让新的 libkn.so 打进 HAP 并安装)
场景 B:
只改了 harmonyApp里的 ArkTS / 壳层 C++(没动 Kotlin)
shell
# 不用跑 Gradle publish
# 直接在 DevEco 里 Run / 热重载即可
3.用 Hello HarmonyOS!做一次「改哪里生效」小实验
· 只改 commonMainGreeting文案模板(例如 Hello → Hi)→ 必须 publish + DevEco Run,真机才会变。

07 常见踩坑
|
现象 |
处理 |
|---|---|
|
改了 commonMain / ohosMain,真机界面没变 |
重新 publish,确认 so 时间戳更新后再 DevEco Run |
|
so 与 h 不是同一次构建 |
整份重新 link / publish,禁止混用旧文件 |
|
找不到 MainArkUIViewController |
核对 @CName、napi 注册名、Index.d.ts 三者一致 |
|
链接或运行缺库 |
检查 gradle.properties 的 rendererBackend;确认 ohpm 已装 @cpf-kmp-cmp/compose |
08 小结
· commonMain写跨端 UI 与业务;ohosMain做鸿蒙入口和 actual;composeApp负责编出 so;harmonyApp是壳与加载方。
· Hello HarmonyOS!= commonMain文案模板 + ohosMain平台名 + harmonyApp把 so 挂到页面。
· 记住两条 Gradle:link*SharedOhosArm64出产物;publish*BinariesToHarmonyApp 出产物并拷进鸿蒙工程。
· libkn.so= 可跑的跨端 UI;libkn_api.h= Native 说明书;二者必须配套。
· 改跨端代码 / 资源 → publish + DevEco Run;只改鸿蒙壳 → 直接 DevEco Run。
即刻体验
CPF-KMP-CMP 鸿蒙平台首个正式版本已正式发布。欢迎上手验证、在社区提交 Issue/Discussion 反馈问题与需求,共同打磨 KMP&CMP 在鸿蒙平台上的生态与产品能力。
快速开始:(可复制链接至网页打开)
https://atomgit.com/CPF-KMP-CMP/docs
发布公告:(可复制链接至网页打开)
https://atomgit.com/org/CPF-KMP-CMP/discussions/15
参与社区共建
加入 CPF-KMP-CMP 社区,参与 KMP&CMP 鸿蒙版本规划和技术共建。
更多推荐



所有评论(0)