鸿蒙端 SDK 创建、单元测试、发布与依赖完整指南
本文档介绍从零创建鸿蒙(OpenHarmony/HarmonyOS)SDK、编写单元测试、发布到官方三方库中心仓,并在项目中依赖使用的完整流程。涉及 C 层(Native)开发时,提供基于源码(BUILD.gn)和应用层(CMake)两种方式的完整实现示例。
一、SDK 创建
1.1 项目结构
鸿蒙 SDK 通常以 HAR(Harmony Archive) 形式发布。根据是否包含 C/C++ 原生代码、是否包含页面,结构有所不同。
1.1.1 纯 ArkTS 结构(无 C 层、无页面)
bash
my_sdk/
├── oh-package.json5 # 包配置(必填)
├── build-profile.json5 # 构建配置
├── src/main/
│ ├── module.json5 # 模块配置
│ └── ets/
│ ├── index.ets # 入口,导出对外 API
│ ├── utils/ # 工具函数
│ └── ...
├── README.md
├── CHANGELOG.md
└── LICENSE
1.1.2 含页面 + C/C++ 的完整结构
bash
my_sdk/
├── oh-package.json5
├── build-profile.json5
├── src/main/
│ ├── module.json5
│ ├── ets/ # ArkTS 源码
│ │ ├── index.ets # 入口:import 页面 + export API
│ │ ├── pages/ # 页面(@Entry 路由页)
│ │ │ ├── MainPage.ets # 主页面
│ │ │ ├── DetailPage.ets
│ │ │ ├── components/ # 页面内可复用组件
│ │ │ │ ├── Toolbar.ets
│ │ │ │ └── BottomPanel.ets
│ │ │ └── utils/ # 页面相关工具
│ │ │ ├── OptionsParser.ets
│ │ │ └── BoundsUtils.ets
│ │ ├── ui/ # 通用 UI(Canvas 绘制、自定义 View)
│ │ │ └── OverlayPainter.ets
│ │ ├── crop/ # 业务核心(或 domain/)
│ │ │ ├── CropOptions.ets
│ │ │ ├── CropTask.ets
│ │ │ └── ResultHandler.ets
│ │ └── utils/ # 通用工具
│ │ └── HttpUtils.ets
│ └── cpp/ # C/C++ 原生代码(可选)
│ ├── CMakeLists.txt
│ ├── napi_init.cpp
│ └── types/ # NAPI 类型声明
│ └── libentry/
│ ├── oh-package.json5 # name: "libentry.so"
│ └── index.d.ts
├── ohosTest/ # 单元测试
│ └── ets/test/
├── README.md
├── CHANGELOG.md
└── LICENSE
1.1.3 目录职责说明
| 目录 | 职责 | 示例 |
|---|---|---|
pages/ |
带 @Entry 的路由页面,负责页面编排与生命周期 |
CropEntryPage、MainPage |
pages/components/ |
页面内可复用 UI 组件(@Component) |
CropToolbar、CropBottomPanel |
pages/utils/ |
页面相关解析、计算、辅助逻辑 | CropOptionsParser、CropBoundsUtils |
ui/ |
通用 UI 绘制、Canvas、自定义 View | CropOverlay、RulerWidget |
crop/ 或 domain/ |
业务核心、数据模型、任务执行 | CropOptions、ImageCropTask |
utils/ |
通用工具(网络、文件、格式等) | HttpDownloadUtils |
cpp/ |
C/C++ 原生实现,通过 NAPI 暴露给 ArkTS | napi_init.cpp |
1.1.4 页面相关调整
新增页面
-
在
pages/下新建XxxPage.ets,使用@Entry({ routeName: 'XxxPage' })装饰:typescript@Entry({ routeName: 'XxxPage' }) @Component struct XxxPage { build() { Column() { /* ... */ } } } -
在
index.ets中增加import './pages/XxxPage'(若 index.ets 在src/main/ets/下)或import './src/main/ets/pages/XxxPage'(若 index.ets 在包根),使路由能注册该页面。 -
调用方通过
router.pushNamedRoute({ name: 'XxxPage' })或router.pushUrl()跳转。
修改页面布局
- 页面根节点一般为
Column、Stack、Row,按需调整子组件顺序和layoutWeight。 - 使用
@State控制 UI 状态,@Prop在父子组件间传参。 - 链式写法注意 ArkTS 的 ASI:
}后的.width()等需与}同一行或确保不被解析为独立语句。
调整页面层级
- 将可复用部分抽到
pages/components/:@Component export struct Xxx,在页面中Xxx({ ... })使用。 - 将通用绘制逻辑抽到
ui/,如CropOverlayPainter负责 Canvas 绘制。
路由与参数传递
- 使用
AppStorage.setOrCreate('key', value)存数据,页面通过@StorageLink('key')读取。 - 或使用
router.pushUrl({ url: 'pages/XxxPage', params: { id: 1 } }),在目标页router.getParams()获取。url 格式需与module.json5中配置的routes一致。
1.1.5 C/C++ 结构说明
当 SDK 包含 Native 时,需在 src/main/ 下增加 cpp/:
bash
cpp/
├── CMakeLists.txt # 构建配置
├── napi_init.cpp # NAPI 注册与实现
└── types/ # 供 ArkTS 调用的类型声明
└── libentry/ # 目录名随意,oh-package.json5 中 name 填 "libentry.so"
├── oh-package.json5 # name: "libentry.so", types: "./index.d.ts"
└── index.d.ts # 声明 C 层暴露的接口
主模块 oh-package.json5 的 dependencies 中需添加:
json5
"libentry.so": "file:./src/main/cpp/types/libentry"
build-profile.json5 中配置 externalNativeOptions 指向 CMakeLists.txt。详见第七章「C 层开发」。
1.2 oh-package.json5 配置
json5
{
"name": "my_sdk", // 包名,发布后用于依赖
"version": "1.0.0", // 语义化版本
"description": "SDK 功能描述",
"main": "src/main/ets/index.ets", // 入口文件
"author": "your_name",
"license": "Apache-2.0",
"repository": "https://gitee.com/xxx/my_sdk",
"dependencies": {} // 依赖的其他 ohpm 包
}
必填项 :name、version、main、license
1.3 module.json5 配置
json5
{
"module": {
"name": "my_sdk",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
type: "har"表示构建为 HAR 静态共享包deviceTypes指定支持的设备类型
1.4 构建 HAR
在项目根目录执行:
bash
# 安装依赖
ohpm install
# 构建 HAR
hvigorw assembleHar
构建产物位于 build/default/outputs/default/ 目录,生成 .har 文件。
二、单元测试
2.1 测试类型
| 类型 | 目录 | 运行环境 | 适用场景 |
|---|---|---|---|
| Local Test | test/ |
本地 JVM | 纯逻辑、工具函数、不依赖设备 |
| Instrument Test | ohosTest/ |
设备/模拟器 | 需系统 API、UI、生命周期 |
2.2 创建测试目录
在 DevEco Studio 中:
- 右键项目 → New → Directory → 输入
ohosTest(Instrument Test)或test(Local Test) - 在
ohosTest或test下创建ets/test/子目录
或手动创建:
bash
ohosTest/ # Instrument Test(设备/模拟器)
└── ets/
└── test/
└── MySdkTest.ets
test/ # Local Test(本地 JVM,可选)
└── ets/
└── test/
└── MyUtilTest.ets
Instrument Test 更常用,HAR 包通常使用 ohosTest。
2.3 使用 Hypium 框架
在 oh-package.json5 的 devDependencies 中添加:
json5
{
"devDependencies": {
"@ohos/hypium": "1.0.x"
}
}
2.4 编写测试用例
typescript
// ohosTest/ets/test/MySdkTest.ets
import { describe, it, expect } from '@ohos/hypium';
import { MyUtil } from '../../src/main/ets/MyUtil'; // 路径以实际目录层级为准
export default function test() {
describe('MyUtil 测试', function () {
it('add 应返回两数之和', 0, async () => {
const result = MyUtil.add(1, 2);
expect(result).assertEqual(3);
});
it('formatPath 应正确处理路径', 0, async () => {
const path = MyUtil.formatPath('/a/b/c');
expect(path).assertContain('a');
});
});
}
2.5 运行测试
- DevEco Studio:右键测试文件 → Run 'MySdkTest'
- 命令行:
bash
hvigorw test
2.6 常用断言
| 断言 | 说明 |
|---|---|
expect(x).assertEqual(y) |
相等 |
expect(x).assertTrue() |
为 true |
expect(x).assertContain(y) |
包含 |
expect(x).assertNotNull() |
非空 |
expect(x).assertDeepEquals(y) |
深度相等 |
三、发布到官方库(OpenHarmony 三方库中心仓)
3.1 注册与准备
- 打开 OpenHarmony 三方库中心仓
- 使用 Gitee 账号登录并完成实名认证
- 进入「个人中心」完成发布者信息
3.2 生成密钥
bash
# 创建目录
mkdir -p ~/.ssh_ohpm
# 生成 RSA 密钥对
ssh-keygen -m PEM -t RSA -b 4096 -f ~/.ssh_ohpm/mykey -N ""
将 ~/.ssh_ohpm/mykey.pub 公钥内容上传到中心仓个人中心的「公钥管理」。
3.3 配置 ohpm
bash
# 设置私钥路径
ohpm config set key_path ~/.ssh_ohpm/mykey
# 设置发布 ID(个人中心获取)
ohpm config set publish_id <your_publish_id>
# 设置发布仓库地址
ohpm config set publish_registry https://ohpm.openharmony.cn/ohpm
3.4 准备发布文件
确保项目根目录包含:
| 文件 | 说明 |
|---|---|
oh-package.json5 |
包配置 |
README.md |
使用说明、API 介绍、示例 |
CHANGELOG.md |
版本变更记录 |
LICENSE |
开源协议(如 Apache-2.0) |
3.5 发布命令
bash
# 进入 HAR 所在目录(或包含 oh-package.json5 的目录)
cd /path/to/my_sdk
# 发布
ohpm publish .
3.6 常见问题
| 问题 | 处理 |
|---|---|
| Missing file "oh-package.json5" | 确保在包含 oh-package.json5 的包根目录执行 ohpm publish . |
| 签名失败 | 检查 key_path、公钥是否已上传 |
| 版本冲突 | 修改 oh-package.json5 中的 version 后重新发布 |
| Node 版本 | 建议使用 Node 18+,执行 node -v 检查 |
四、在项目中依赖使用
4.1 配置依赖
在项目根目录的 oh-package.json5 的 dependencies 中声明:
json5
{
"dependencies": {
"my_sdk": "1.0.0"
}
}
依赖版本号支持语义化范围,如 "1.0.x"、"^1.0.0"。
4.2 安装依赖
bash
ohpm install
4.3 导入使用
typescript
// 按包名导入
import { MyUtil, MyClass } from 'my_sdk';
// 使用
const result = MyUtil.doSomething();
const obj = new MyClass();
4.4 依赖私有仓或特定源
在项目根目录创建或编辑 oh-package.json5:
json5
{
"dependencies": {
"my_sdk": "1.0.0"
},
"devDependencies": {},
"registry": "https://ohpm.openharmony.cn/ohpm"
}
或通过环境变量:
bash
export OHPM_REGISTRY=https://ohpm.openharmony.cn/ohpm
ohpm install
五、Flutter 插件中的鸿蒙 SDK 结构示例
以 image_cropper 为例,其鸿蒙端结构:
bash
image_cropper/ohos/
├── oh-package.json5 # 主包配置
├── build-profile.json5
├── src/
│ ├── main/
│ │ ├── module.json5
│ │ └── ets/
│ │ └── components/
│ │ └── plugin/
│ │ └── ImageCropperPlugin.ets
│ └── lib/
│ └── image_crop_ohos/ # 子库(HAR)
│ ├── oh-package.json5
│ ├── build-profile.json5
│ ├── index.ets # 导出入口
│ └── src/main/ets/
│ ├── crop/
│ ├── pages/
│ └── ui/
└── ...
主包通过 dependencies: { "image_crop_ohos": "file:./src/lib/image_crop_ohos" } 引用本地子库。若将 image_crop_ohos 单独发布到中心仓,其他项目可直接依赖:
json5
"dependencies": {
"image_crop_ohos": "1.0.0"
}
六、流程总览
java
┌─────────────────┐
│ 1. 创建 SDK 项目 │
│ oh-package.json5 │
│ module.json5 │
└────────┬────────┘
│
├──────────────────────────────────┐
│ 若需 C 层能力 │
▼ │
┌─────────────────┐ │
│ 1.5 添加 Native │ CMake / BUILD.gn │
│ NAPI 模块 │ → libxxx.so │
└────────┬────────┘ │
│ │
▼ ▼
┌─────────────────┐
│ 2. 编写单元测试 │
│ ohosTest/ │
│ @ohos/hypium │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 3. 构建 HAR │
│ hvigorw │
│ assembleHar │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 4. 发布到中心仓 │
│ ohpm publish │
│ 密钥 + 文档 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 5. 项目依赖使用 │
│ ohpm install │
│ import 'my_sdk' │
└─────────────────┘
七、C 层(Native)开发
当 SDK 需要调用 C/C++ 代码(如高性能计算、移植三方库、系统底层能力)时,需使用 NAPI(Native API) 作为 ArkTS 与 C/C++ 的桥梁,相当于 Android 的 JNI。
7.1 两种开发方式对比
| 方式 | 适用场景 | 构建系统 | 是否需要 OpenHarmony 源码 |
|---|---|---|---|
| 方式一:源码 + BUILD.gn | 系统级、预装应用、设备厂商 | BUILD.gn | ✅ 需要 |
| 方式二:应用层 + CMake | 普通应用、HAR 包、发布到中心仓 | CMake | ❌ 不需要 |
7.2 方式一:基于 OpenHarmony 源码(BUILD.gn)
适用于有完整 OpenHarmony 源码、需将 NAPI 编译进系统镜像的场景。
7.2.1 目录结构
bash
mysubsys/ # 子系统
├── ohos.build
└── hello/ # 组件
└── hellonapi/ # 模块
├── BUILD.gn
└── hellonapi.cpp
7.2.2 添加子系统 ohos.build
在 mysubsys/ohos.build:
json
{
"subsystem": "mysubsys",
"parts": {
"hello": {
"module_list": [
"//mysubsys/hello/hellonapi:hellonapi"
],
"inner_kits": [],
"system_kits": [],
"test_list": []
}
}
}
7.2.3 注册到 build/subsystem_config.json
json
"mysubsys": {
"project": "hmf/mysubsys",
"path": "mysubsys",
"name": "mysubsys",
"dir": ""
}
7.2.4 C++ 源码实现 hellonapi.cpp
cpp
#include <string>
#include "napi/native_api.h"
#include "napi/native_node_api.h"
// 1. 业务接口实现
static napi_value getHelloString(napi_env env, napi_callback_info info) {
napi_value result;
std::string words = "Hello OpenHarmony NAPI";
NAPI_CALL(env, napi_create_string_utf8(env, words.c_str(), words.length(), &result));
return result;
}
// 2. 注册对外接口
static napi_value registerFunc(napi_env env, napi_value exports) {
static napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
// 3. 定义并注册 NAPI 模块
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = nullptr,
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void hellonapiModuleRegister() {
napi_module_register(&hellonapiModule);
}
7.2.5 BUILD.gn 构建脚本
gn
import("//build/ohos.gni")
ohos_shared_library("hellonapi") {
include_dirs = [
"//foundation/arkui/napi/interfaces/kits",
"//foundation/arkui/napi/interfaces/inner_api",
]
cflags_cc = [ "-Wno-error", "-Wno-unused-function" ]
sources = [ "hellonapi.cpp" ]
deps = [ "//foundation/arkui/napi:ace_napi" ]
relative_install_dir = "module"
subsystem_name = "mysubsys"
part_name = "hello"
}
7.2.6 编译与烧录
bash
# 增量编译
./build.sh --product-name rk3568 --ccache --build-target=hellonapi --target-cpu arm64
# 全量编译后烧录镜像
7.2.7 ETS 调用
typescript
import hellonapi from '@ohos.hellonapi';
let str = hellonapi.getHelloString();
需在 SDK 目录下提供 @ohos.hellonapi.d.ts 声明:
typescript
declare namespace hellonapi {
function getHelloString(): string;
}
export default hellonapi;
7.3 方式二:基于 DevEco Studio + CMake(应用层)
适用于普通应用开发者,无需 OpenHarmony 源码,在 DevEco Studio 中创建 Native C++ 模块。
7.3.1 项目结构
bash
entry/
├── src/
│ └── main/
│ ├── cpp/ # C++ 源码
│ │ ├── CMakeLists.txt
│ │ ├── napi_init.cpp
│ │ └── types/ # 类型声明(可选)
│ │ └── libentry/
│ │ ├── oh-package.json5
│ │ └── index.d.ts
│ └── ets/
│ └── ...
├── build-profile.json5
└── oh-package.json5
7.3.2 CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.4.1)
project(entry)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${NATIVERENDER_ROOT_PATH})
# 生成 libentry.so
add_library(entry SHARED napi_init.cpp)
# 链接 NAPI 库
target_link_libraries(entry PUBLIC libace_napi.z.so)
7.3.3 napi_init.cpp 实现
cpp
#include "napi/native_api.h"
#include "napi/native_node_api.h"
static napi_value add(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int32_t a, b;
napi_get_value_int32(env, args[0], &a);
napi_get_value_int32(env, args[1], &b);
napi_value result;
napi_create_int32(env, a + b, &result);
return result;
}
static napi_value registerFunc(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{ "add", nullptr, add, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
static napi_module entryModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc,
.nm_modname = "libentry",
.nm_priv = nullptr,
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void entryModuleRegister() {
napi_module_register(&entryModule);
}
7.3.4 build-profile.json5 配置
在模块的 build-profile.json5 中配置 Native 编译:
json5
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "-DOHOS_STL=c++_shared",
"abiFilters": ["armeabi-v7a", "arm64-v8a"]
}
}
}
7.3.5 类型声明与 SO 关联
在 src/main/cpp/types/libentry/ 下创建:
oh-package.json5:
json5
{
"name": "libentry.so",
"types": "./index.d.ts",
"version": "1.0.0",
"description": "Native NAPI module"
}
index.d.ts:
typescript
export const add: (a: number, b: number) => number;
7.3.6 主模块 oh-package.json5 引用
在 entry/oh-package.json5 的 dependencies 中添加:
json5
{
"dependencies": {
"libentry.so": "file:./src/main/cpp/types/libentry"
}
}
7.3.7 ETS 调用
typescript
import libentry from 'libentry.so';
let sum = libentry.add(1, 2); // 3
7.4 调用已有三方 SO
若已有预编译的 .so 文件,无需编写 C++ 源码,只需:
- 将
.so放入src/main/libs/<abi>/目录(如src/main/libs/arm64-v8a/libmylib.so) - 创建类型声明包,在
oh-package.json5中name填 SO 名(如libmylib.so),types指向index.d.ts - 主模块
oh-package.json5的dependencies中添加:"libmylib.so": "file:./src/main/cpp/types/libmylib" - 在
build-profile.json5的externalNativeOptions中配置abiFilters,或通过 CMake 的add_library(IMPORTED)引入预编译 so(具体以 DevEco 文档为准)
目录示例:
bash
src/main/
├── libs/
│ ├── arm64-v8a/
│ │ └── libmylib.so
│ └── armeabi-v7a/
│ └── libmylib.so
└── cpp/types/libmylib/
├── oh-package.json5
└── index.d.ts
types/libmylib/oh-package.json5:
json5
{
"name": "libmylib.so",
"types": "./index.d.ts",
"version": "1.0.0"
}
index.d.ts:
typescript
export const nativeMethod: (param: string) => number;
主模块 oh-package.json5:
json5
{
"dependencies": {
"libmylib.so": "file:./src/main/cpp/types/libmylib"
}
}
7.5 NAPI 常用类型转换
| ETS 类型 | C/C++ 获取 | C/C++ 返回 |
|---|---|---|
| number | napi_get_value_int32 / napi_get_value_double |
napi_create_int32 / napi_create_double |
| string | napi_get_value_string_utf8 |
napi_create_string_utf8 |
| boolean | napi_get_value_bool |
napi_get_boolean(创建 true/false 的 napi_value) |
| ArrayBuffer | napi_get_arraybuffer_info |
napi_create_arraybuffer |
| 对象 | napi_get_property |
napi_create_object |
7.6 C 层流程总览
scss
┌─────────────────────────────────────────────────────────────┐
│ C++ 实现 (napi_init.cpp) │
│ - 实现业务逻辑 napi_value xxx(napi_env, napi_callback_info) │
│ - registerFunc 注册接口 │
│ - napi_module_register 注册模块 │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 构建 (CMake / BUILD.gn) │
│ - add_library(entry SHARED ...) │
│ - target_link_libraries(entry libace_napi.z.so) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 产物 libentry.so + index.d.ts │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ETS: import libentry from 'libentry.so' │
│ libentry.add(1, 2) │
└─────────────────────────────────────────────────────────────┘