从 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 鸿蒙版本规划和技术共建。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐