Skip to main content

harmony-api

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

Quellinformationen

Repository
zerx-lab/zpass
Letzte Quellaktivität
29. Mai 2026 um 03:50
Erkannte Sprache von SKILL.md
Chinesisch
Sterne
12
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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';
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen