Skip to main content

harmony-api

在 harmony/ 子项目调用系统能力、配置权限、或新增原生(NAPI)API 前调用。覆盖 Stage 模型与 UIAbility 生命周期、module.json5 权限配置、本项目所用 12 个 Kit(剪贴板/加密/文件/网络/HUKS/生物认证/扫码等)的真实用法、NAPI 原生桥(libcryptocore.so)链路,以及 BusinessError/hilog 运行时约定。

ソース情報

リポジトリ
zerx-lab/zpass
ソースの最終更新活動
2026年5月29日 03:50
検出された SKILL.md の言語
中国語
スター
12
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
harmony-api
description
在 harmony/ 子项目调用系统能力、配置权限、或新增原生(NAPI)API 前调用。覆盖 Stage 模型与 UIAbility 生命周期、module.json5 权限配置、本项目所用 12 个 Kit(剪贴板/加密/文件/网络/HUKS/生物认证/扫码等)的真实用法、NAPI 原生桥(libcryptocore.so)链路,以及 BusinessError/hilog 运行时约定。
# HarmonyOS 系统 API 开发 本 skill 是 ZPass `harmony/` 子项目调用系统能力的权威范式。所有 import 路径、API、字段 都取自项目真实代码与本地 SDK 声明,照抄优先于凭记忆。 ## 一、何时用本 skill 满足任一条件即应先读本 skill: - 要在 ArkTS 里调用系统能力:剪贴板、加密、文件读写、网络、HUKS、生物认证、扫码、振动等 - 要配置或申请权限(改 `module.json5` 的 `requestPermissions`,或运行时弹权限框) - 要新增 / 修改原生 NAPI API(动 `cryptocore/src/harmony.rs`、`.d.ts` 声明、`oh-package.json5`) - 要处理 UIAbility 生命周期、窗口沉浸式、系统栏、安全区 - 遇到 `BusinessError` 错误码、`hilog` 日志、`async` 与原生异步的映射问题 ## 二、Stage 模型与工程结构 本项目是 **Stage 模型**(`entry/build-profile.json5` 的 `"apiType": "stageMode"`), 单 `entry` 模块(HAP),`module.json5` 里 `"type": "entry"`、`mainElement: "EntryAbility"`。 编译目标(以 `harmony/build-profile.json5` 的 `products[0]` 为准,不要写错): - `targetSdkVersion`: **6.0.0(20)** — 即 API 20 - `compatibleSdkVersion`: **5.0.0(12)** — 即 API 12,最低兼容 - `runtimeOS`: `HarmonyOS` 注意:`harmony/.sdk-ref/sdk-api/` 下的 `.d.ts` 声明取自本地已装的 **API 24** SDK,**仅供查阅 签名与 import 路径,不是编译目标**。写代码时不要因为某 API 在 API 24 声明里存在就假设它在 API 12 可用;拿不准时以本项目已实际调用过的 API 为准。 关键工程文件: - `harmony/build-profile.json5` — 应用级编译目标 / product - `harmony/entry/build-profile.json5` — 模块级 `apiType` / `nativeLib`(把 `entry/libs/<arch>/*.so` 打入 HAP) - `harmony/entry/oh-package.json5` — 模块依赖(含 `libcryptocore.so` 的 `file:` 依赖) - `harmony/oh-package.json5` — 工程级 devDependencies(hvigor / hypium) - `harmony/entry/src/main/module.json5` — abilities / pages / requestPermissions - `harmony/entry/src/main/resources/base/profile/main_pages.json` — 路由页面列表 字体:本项目实际字体是 `'HarmonyOS Sans'` / `'HarmonyOS Sans Condensed'`。 (Geist 是 phone(RN) 端 token,**勿带入 harmony**。) ## 三、UIAbility 生命周期与 context 入口 `harmony/entry/src/main/ets/entryability/EntryAbility.ets`,`extends UIAbility`, 生命周期顺序:`onCreate` → `onWindowStageCreate` → `onForeground` ↔ `onBackground` → `onWindowStageDestroy` → `onDestroy`。 import 行(真实): ```ts import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { display, mediaquery, window } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; ``` ### context 与 filesDir 写入 AppStorage `onCreate` 里把沙箱 `filesDir` 写进 AppStorage,供 VaultStorage / TrustedDeviceHuks 读取 (OHOS 5+ 沙箱路径可变,禁止硬编码): ```ts onCreate(_want: Want, _launchParam: AbilityConstant.LaunchParam): void { const ctx = this.context; AppStorage.setOrCreate<string>('filesDir', ctx.filesDir); } ``` 下游用 `AppStorage.get<string>('filesDir')` 取回(见 VaultStorage.ets / TrustedDeviceHuks.ets)。 ### 加载页面 `onWindowStageCreate` 里 `windowStage.loadContent('pages/Index', cb)`,`pages/Index` 必须 在 `main_pages.json` 的 `src` 数组里登记,否则路由失败。 ### 沉浸式 / 系统栏 / 安全区(边到边) 四步(`applyImmersive`): 1. `mainWin.setWindowLayoutFullScreen(true)` — 窗口扩展到状态栏 / 导航栏区域 2. `mainWin.setWindowSystemBarProperties({...})` — 系统栏背景透明、按主题设前景色 3. `win.getWindowAvoidArea(type)` 取避让区,换算成 vp 写进 `safeAreaState` 4. `mainWin.on('avoidAreaChange', cb)` 订阅横竖屏 / 折叠态切换 真实片段: ```ts const mainWin = windowStage.getMainWindowSync(); mainWin.setWindowLayoutFullScreen(true).catch((err: BusinessError) => { hilog.error(DOMAIN, TAG, 'setWindowLayoutFullScreen failed: %{public}s', JSON.stringify(err)); }); mainWin.setWindowSystemBarProperties({ statusBarColor: '#00000000', statusBarContentColor: fg, // 暗底白前景 / 亮底黑前景 navigationBarColor: '#00000000', navigationBarContentColor: fg, }); // avoidArea:window API 返回 px,ArkUI 单位是 vp,要用像素密度换算 const density = display.getDefaultDisplaySync().densityPixels || 1; const area = mainWin.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); safeAreaState.top = area.topRect.height / density; mainWin.on('avoidAreaChange', (data) => { if (data.type === window.AvoidAreaType.TYPE_SYSTEM) { safeAreaState.top = data.area.topRect.height / density; } else if (data.type === window.AvoidAreaType.TYPE_KEYBOARD) { safeAreaState.keyboard = data.area.bottomRect.height / density; } }); ``` 避让区类型用到:`TYPE_SYSTEM`(状态栏)、`TYPE_NAVIGATION_INDICATOR`(底部导航条)、 `TYPE_KEYBOARD`(软键盘)。 安全区状态走 `@ObservedV2` 单例(`state/SafeArea.ets`),不是 AppStorage: ```ts @ObservedV2 export class SafeAreaState { @Trace top: number = 0; @Trace bottom: number = 0; @Trace keyboard: number = 0; // 键盘占据高度(vp),不弹时 0 } export const safeAreaState: SafeAreaState = new SafeAreaState(); ``` 页面里用 `@Local safeArea: SafeAreaState = safeAreaState;`,再 `.padding({ bottom: this.safeArea.keyboard })`。 原因:`@StorageProp` 是 ArkUI V1 装饰器,不能用在 `@ComponentV2` 里;本项目全量 V2。 ### 跟随系统暗 / 亮模式 `mediaquery.matchMediaSync('(dark-mode: true)')` 拿初值 + `listener.on('change', cb)` 持续同步, 驱动 themeStore,再回调刷新系统栏前景色。`onDestroy` 里 `this.darkModeListener?.off('change')`。 ## 四、module.json5 配置与权限 ### 静态声明(已申请,照抄即可) `module.json5` 的 `requestPermissions`(真实): ```json5 "requestPermissions": [ { "name": "ohos.permission.ACCESS_BIOMETRIC", "reason": "$string:permission_biometric_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } }, { "name": "ohos.permission.VIBRATE" }, { "name": "ohos.permission.INTERNET" } ] ``` 权限用途映射: | 权限 | 用途 | 触发场景 | |---|---|---| | `ohos.permission.ACCESS_BIOMETRIC` | 信任设备 / 生物解锁 | TrustedDeviceHuks(HUKS+UserAuth) | | `ohos.permission.VIBRATE` | 剪贴板复制触感反馈 | Clipboard.lightHaptic | | `ohos.permission.INTERNET` | HIBP 泄露检测 / LAN 同步 | Breach / SyncProtocol | 要点: - `user_grant` 级权限(如 `ACCESS_BIOMETRIC`)必须带 `reason`(指向 `$string:` 资源)和 `usedScene` - `system_grant` 级权限(`VIBRATE` / `INTERNET`)只写 `name` 即可,安装时自动授予 - abilities 里 `EntryAbility` 的 `skills` 含 `entity.system.home` + `action.system.home`(桌面图标入口) ### 运行时申请(user_grant 权限按需弹框) 声明在 `module.json5` 只是「可申请」;`user_grant` 权限实际授予要运行时弹框。 API(已用 `@kit.AbilityKit` 的 `abilityAccessCtrl`,声明 `@ohos.abilityAccessCtrl.d.ts`): ```ts import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit'; const atManager = abilityAccessCtrl.createAtManager(); const perms: Permissions[] = ['ohos.permission.ACCESS_BIOMETRIC']; const result = await atManager.requestPermissionsFromUser(context, perms); // result.authResults[i] === 0 表示已授予 ``` 注意:`ScanKit` 的相机权限由系统级扫码 UI 自己弹,**不需要**手动申请(见第五节 ScanKit)。 ## 五、12 个 Kit 实战速查 每个 Kit 下:真实 import 行 + 关键 API + 所需权限 + 本项目最小片段 + 易踩坑。 (佐证文件见第九节。) ### 1. @kit.AbilityKit(UIAbility / context / 权限 / picker context) ```ts import { AbilityConstant, UIAbility, Want, common } from '@kit.AbilityKit'; ``` - 关键:`UIAbility` 生命周期、`this.context`(`UIAbilityContext`)、`common.Context` - `common.Context` 作为参数传给 picker(见 Transfer.ets `exportVaultToFile(context: common.Context)`) - 权限:无 - 坑:页面里拿 context 用 `getContext(this) as Context`;Ability 里用 `this.context`,二者不可混 ### 2. @kit.ArkUI(window / display / mediaquery / router / promptAction) ```ts import { display, mediaquery, window } from '@kit.ArkUI'; import { promptAction, router } from '@kit.ArkUI'; ``` - 关键:见第三节(沉浸式 / 安全区 / 暗模式);页面跳转 `router.back()` / `router.pushUrl` - `promptAction.showToast({ message, duration })`、`showDialog`、`showActionMenu` - 权限:无 - 坑:`window` API 返回 px,UI 是 vp,必须 `display.getDefaultDisplaySync().densityPixels` 换算 ### 3. @kit.BasicServicesKit(pasteboard / BusinessError) ```ts import { pasteboard } from '@kit.BasicServicesKit'; import { BusinessError } from '@kit.BasicServicesKit'; ``` - 关键:`pasteboard.getSystemPasteboard()`、`pasteboard.createData(MIMETYPE_TEXT_PLAIN, value)`、 `pb.setData(data)` / `pb.getData()` / `data.getPrimaryText()` - 权限:无(剪贴板本身不要权限;触感反馈走另一个 Kit) - 片段(Clipboard.ets `copyEphemeral`:30s 后剪贴板未变则清空): ```ts const pb = pasteboard.getSystemPasteboard(); const data = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, value); await pb.setData(data); setTimeout(async () => { const cur = (await pb.getData()).getPrimaryText(); if (cur === value) await pb.setData(pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '')); }, 30_000); ``` - 坑:`BusinessError` 也从这个 Kit 导,不是从 `@ohos.base`(项目统一这么写) ### 4. @kit.SensorServiceKit(vibrator) ```ts import { vibrator } from '@kit.SensorServiceKit'; ``` - 关键:`vibrator.startVibration({ type: 'time', duration }, { id, usage })` - 权限:`ohos.permission.VIBRATE` - 片段(Clipboard.ets `lightHaptic`): ```ts await vibrator.startVibration({ type: 'time', duration: 12 }, { id: 0, usage: 'touch' }); ``` - 坑:模拟器 / 无马达设备会 reject,必须 try/catch 静默忽略,不要让它阻断主流程 ### 5. @kit.CryptoArchitectureKit(cryptoFramework — 系统级哈希) ```ts import { cryptoFramework } from '@kit.CryptoArchitectureKit'; ``` - 关键:`cryptoFramework.createMd('SHA1' | 'SHA256')` → `md.updateSync({ data })` → `md.digestSync()` - 用途:HIBP 的 SHA-1(Breach.ets)、sync 的 SHA-256(SyncProtocol.ets) - 权限:无 - 片段(Breach.ets,SHA-1 大写 hex): ```ts const md = cryptoFramework.createMd('SHA1'); md.updateSync({ data: utf8(password) }); const bytes = new Uint8Array(md.digestSync().data); ``` - 坑:本项目 **HMAC-SHA256 故意不走** `cryptoFramework.createMac`。原因:OHOS HMAC 要先 `createSymKeyGenerator('HMAC|SHA256')` 把字节 key 包成 SymKey 再 `convertKey`,调用栈过长; SyncProtocol.ets 直接基于系统 SHA-256 手写 RFC 2104 HMAC(block size 64)。新增哈希优先复用此模式。 - 坑:Argon2id / XChaCha20 **不走系统加密**,走 cryptocore NAPI(见第六节) ### 6. @kit.CoreFileKit(fileIo / picker) ```ts import { fileIo as fs } from '@kit.CoreFileKit'; import { fileIo as fs, picker } from '@kit.CoreFileKit'; ``` - 关键:`fs.open(path, OpenMode)` → `fs.write(fd, buf)` / `fs.read(fd, buf)` → `fs.close(fd)`; `fs.access(path)`(boolean Promise)、`fs.unlink`、`fs.moveFile(src, dst)`、`fs.readText`、`fs.stat(fd)` - `OpenMode`:`READ_WRITE | CREATE | TRUNC`(写)、`READ_ONLY`(读) - picker:`DocumentSaveOptions` / `DocumentSelectOptions` + `DocumentViewPicker(context)` 的 `.save()` / `.select()` - 权限:沙箱 filesDir 读写无权限;picker 选文件由系统 UI 授权 - 片段(VaultStorage.ets 原子写:写 tmp + rename): ```ts const handle = await fs.open(tmp, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC); try { await fs.write(handle.fd, text); } finally { await fs.close(handle.fd); } await unlinkIfExists(dst); // moveFile 目标存在会失败,先 unlink await fs.moveFile(tmp, dst); ``` - 坑:OHOS fs 是 open/write/close 三步,不是 Node 的 `writeFile` 一步;`moveFile` 目标存在会失败, 必须先 `unlink`。文件落 `filesDir`(卸载随之清除)。 ### 7. @kit.NetworkKit(http) ```ts import { http } from '@kit.NetworkKit'; ``` - 关键:`http.createHttp()` → `request.request(url, options)` → `request.destroy()`(finally 必须调) - options:`method`(`http.RequestMethod.GET/POST`)、`header`、`extraData`、`connectTimeout`、 `readTimeout`、`expectDataType`(`http.HttpDataType.ARRAY_BUFFER` 收二进制) - 响应:`resp.responseCode`、`resp.result`(文本时 string,二进制时 ArrayBuffer) - 权限:`ohos.permission.INTERNET` - 片段(SyncProtocol.ets `postBinary`,二进制响应): ```ts const request = http.createHttp(); try { const resp = await request.request(url, { method: http.RequestMethod.POST, header: { 'content-type': 'application/octet-stream' }, extraData: ab, // ArrayBuffer connectTimeout: 10_000, readTimeout: 60_000, expectDataType: http.HttpDataType.ARRAY_BUFFER, // 否则默认按文本解析 }); if (resp.result instanceof ArrayBuffer) return new Uint8Array(resp.result); } finally { request.destroy(); } ``` - 坑:默认按文本解析响应,收二进制**必须**设 `expectDataType: ARRAY_BUFFER`; 每次请求结束 `request.destroy()` 释放连接;catch 里 `e as BusinessError` 取 `code`/`message` ### 8. @kit.ArkTS(util — base64 / TextEncoder) ```ts import { util } from '@kit.ArkTS';
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る