关于 harmonyos-development
harmonyos-development 是面向 HarmonyOS 应用开发者的开发技能,覆盖 ArkTS、ArkUI、开发工具链及多个 API 版本。它区分 API 24 生产基线与 API 26 预览内容,并汇总平台能力和版本变化。
主要功能
- 01涵盖 ArkTS 与 ArkUI
- 02整理 HarmonyOS API 版本
- 03提供工具链与项目基线信息
- 04汇总平台套件和 API 变化
适用场景
- 01开发 HarmonyOS 原生应用
- 02按 API 版本适配应用
- 03查阅平台功能与工具链变化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dengshiyinga/ harmonyos-ai-skill --skill harmonyos-development安装后在 Claude Code 中输入/harmonyos-development确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/cases.yamlexamples/lazyforeach-list.etspermission-request.etsrecipes/debug-build-error.mdreview-arkts-code.mdreferences/ai-development-tools.mdapi26-preview.mdarkts-rules.mdarkui-components.mdbuild-sign-release.mdnative-api-compatibility.mdnavigation.mdperformance.mdpermissions.mdplatform-baseline.mdREADME.mdstage-model.mdstate-management.md详细介绍
HarmonyOS(鸿蒙)开发
涵盖 HarmonyOS 6.1(API 23,稳定版)/ 6.1.1(API 24,Release)/ HarmonyOS 7 开发者预览版(API 26 Beta1)/ NEXT 原生应用开发——华为移动操作系统系列,可独立于 Android 运行(自 2024 年发布的 HarmonyOS NEXT 起,不再基于 AOSP)。主要语言是 ArkTS(TypeScript 的严格、静态检查超集),主要 UI 框架是 ArkUI(声明式、状态驱动)。生产环境默认使用 API 24 Release 作为基线;API 26 Beta1 仅用于预览、适配和早期兼容性工作。
平台概览
| 项目 | 值 |
|---|---|
| 操作系统 | HarmonyOS 6.1(稳定版,发布于 2026/04/20,API 23)。HarmonyOS 6.1.1(Release,发布于 2026/05/26,API 24)。HarmonyOS 7 / 26.0.0 Beta1(开发者预览版,发布于 2026/06/12,API 26)。纯 HarmonyOS,不基于 AOSP |
| 语言 | ArkTS(主要语言)、Cangjie(Beta)、通过 NAPI 使用 C/C++ |
| UI 框架 | ArkUI 声明式框架(跨平台框架 ArkUI-X) |
| 编译器 | ArkCompiler — AOT 编译为原生机器码;LiteActor 并发模型 |
| 包管理器 | ohpm — oh-package.json5;注册中心位于 DevEco Service(OHPM Central) |
| IDE | DevEco Studio 6.1.1 Release(6.1.1.280;用于 API 24 生产环境)。DevEco Studio 26.0.0 Beta1(26.0.0.461;用于 API 26 预览版) |
| 应用模型 | Stage 模型(FA 模型已过时——新应用中请勿使用) |
| 打包格式 | HAP(entry/feature)、HSP(共享包)、HAR(静态归档)、原子化 .app |
| 推荐 API | 生产环境使用 API 24 Release。HarmonyOS 7 预览/适配仅使用 API 26 Beta1。 |
| 示例目录 | https://developer.huawei.com/consumer/cn/samples/ |
近期版本发布时间线:
- HarmonyOS 6.0.1(21) — 2025/11/25(随 Mate 80 系列推出的首个稳定版)
- HarmonyOS 6.0.2(22) — 2026/01/23(增量更新)
- HarmonyOS 6.0.0.328 Pollen Beta(23) — 2026/02/28(封闭 Beta,覆盖 25 款机型)
- HarmonyOS 6.1(23) — 2026/04/20(稳定版正式发布)
- HarmonyOS 6.1.1(24) Beta 1 — 2026/04/30(开发者 Beta)
- HarmonyOS 6.1.1(24) Release — 2026/05/26(API 24 Release;DevEco Studio 6.1.1.280)
- HarmonyOS 7 / 26.0.0 Beta1 — 2026/06/12(API 26 开发者预览版;DevEco Studio 26.0.0.461)
API 23(HarmonyOS 6.1)新增内容
ArkUI 增强:
Navigation支持将路由栈绑定到组件本身,并指定NavDestination作为导航栏(主页)——无需再单独设置根容器Menu新增anchorPosition属性:通过水平/垂直偏移量控制弹出菜单相对于锚点左上角的位置Image组件的 SVG 解析能力得到增强- 新增一批用于属性样式的 C API(原生侧)
C 侧新增能力(Native/NAPI):
- UDMF(统一数据管理框架)C API
- 组件拖放 C API
- 加密算法 C API
数据:
relationalStore增强sendable函数,改善跨线程数据传递
图形与 AI:
- Graphics Accelerate Kit 中的 AI 超级帧功能(通过帧插值让动画更流畅)
ArkWeb: 进一步增强能力(拦截/ Cookie)
API 24 新增内容(HarmonyOS 6.1.1 Release)
版本基线: API 24 现已成为 Release SDK(不再只是 Beta)。API 24 项目请使用 DevEco Studio 6.1.1 Release(6.1.1.280)、HarmonyOS SDK 6.1.1 Release、Hvigor 6.24.2、ohpm 6.1.2.268 和 Node.js 18.20.1。
相较于 Beta1 的新增内容:
- Ability Kit —
AbilityStage新增回调,可在创建首个 Ability 之前,以及进程从应用快照启动时触发。 - ArkTS — 新增
enableLocalHandleDetection,使 EventHandler/libuv 任务保持在预期作用域内,避免泄漏;XML 解析新增XmlSAXHandler回调,以支持 SAX 风格的解析。 - ArkWeb — 下载完成回调可获取原始 URL 和 referrer URL。
- Call Service Kit — 支持根据来电/去电号码查询企业呼叫服务。
- Camera Kit 专业控制功能 — 闪光灯状态事件订阅/取消订阅;查询/设置 OIS;镜头焦距/等效焦距/最近对焦距离/畸变/内参校准/传感器尺寸/像素阵列/色彩滤镜数据;逻辑摄像头组合;自动/手动曝光;手动对焦;ISO;物理光圈。
- CANN Kit — PC 设备上的大语言模型推理加速 API。
- MDM Kit — 管理当前用户的隐藏设置项。
API 24 Release 中仍适用的 Beta1 新增内容:
- Camera Kit“人物跟踪” — 自动摄像头跟踪 API,通过实时裁剪/缩放使人物保持在画面中央。适用于视频通话、健身录制、视频博客:
// Conceptual API — full signature in official docs cameraSession.enableSubjectTracking(camera.TrackingMode.PERSON); - 延迟预览输出 — 将延迟预览直接添加到流管线中,而不是使用常规预览输出,并单独配置其 Surface。
- ArkTS VM 诊断 — 按 VM 线程提供堆信息、GC 后的堆警告回调,以及
taskpool.execute()超时设置。 - ArkUI — 并行窗口状态查询、跨 Ability 实例迁移自定义组件、动态布局容器、
UIContext根节点查找、异步拖放决策、onNeedSoftkeyboard、Canvas/OffscreenCanvasantialias、Tabs 嵌套滚动、多行省略模式(MULTILINE_START、MULTILINE_CENTER)。 - ArkWeb — User-Agent Client Hints、默认上下文菜单开关、URL 白名单以及加载/跳转安全控制。
- Audio Kit — 用于采集/渲染的独立音频会话策略/行为,以及适用于 USB/BLE MIDI 设备的 OH_MIDI C API。
- 新增/扩展的 Kits — Content Embed Kit、Enterprise Threat Protection Kit、FAST Kit、NearLink Kit、Network Boost Kit、Screen Time Guard Kit、Device Security Kit、Desktop Extension Kit。
- DevEco Studio — API 24 项目、适用于 C++ 和资源编辑的 Hot Reload、扩展的 AppFreeze 解析、ComMemory UI 内存分析、用于加快严格语法检查的
strictCheckerOnly。
HarmonyOS 7 / API 26 Beta1 预览版(2026/06/12)
状态(截至 2026/07/25 核实): 开发者 Beta,并非默认生产基线。Huawei 尚未发布 API 26 Beta2、RC 或 Release。仅当用户询问 HarmonyOS 7、API 26、HDC 2026、预览版适配或 Beta1 功能时,才提及 API 26 功能。对于生产代码,除非项目明确面向 API 26 预览版,否则优先使用 API 24 Release。
开发套件基线: HarmonyOS SDK 26.0.0 Beta1(OpenHarmony SDK Ohos_sdk_public 26.0.0.23,API Version 26.0.0 Beta1)和 DevEco Studio 26.0.0 Beta1 (26.0.0.461)。工具链:HarmonyOS Emulator 26.0.0.200、Hvigor/hvigorw 6.26.1、ohpm 26.0.0.410、Node.js 24.14.1、hstack 6.0.0、compileSdkVersion: "26.0.0"、targetSdkVersion: "4.0.0(10)~26.0.0"。
版本号规则: 从 API 26.0.0 开始,HarmonyOS 开发套件 API 版本采用 SemVer(X.Y.Z),不再使用旧版 X.Y.Z(N) 格式。X 表示具有重要能力或会影响适配的变更的大版本,Y 表示新增能力的小版本,Z 表示兼容性修复/小幅改进。
API 26 Beta1 高价值变更:
- Ability Kit — 支持 AgentCard;基于 ArkTS 脚本开发应用 Skill;通过包名和克隆索引查询应用名称;新增 ArkTS 脚本管理 API;新增
ModularObjectExtensionAbility的 C API。 - Accessibility Kit — 集成系统关怀模式,打造适老化应用体验。
- Accessory Kit — 新增 Kit,支持配件唤醒、系统服务联动、按需调度和安全信任管理。
- Account Kit —
LoginWithHuaweiIDButton支持自定义多语言文本和加载动画。 - AR Engine — 新增 C API,用于控制摄像头闪光灯;支持通过 ArkTS 访问预览流图像;支持加载 3D Gaussian 模型;新增用于外接摄像头和传感器数据的 ArkTS/C API。
- ArkUI / UI Design — 支持为 Toggle、Tips、Toast、对话框、菜单、弹窗、自定义对话框、半模态和 Popup 配置系统材质;支持组件级沉浸式光感知;支持为
@Reusable/@ReusableV2配置全局复用池;支持标准悬浮窗;更新标题区域自定义功能。 - ArkWeb — Chromium 内核从 132 升级至 144;新增安全配置选项。
- AVCodec Kit — H.265 硬件编码器支持 CBRHQ 模式;支持 Audio Vivid 编码和 C API。
- AVSession Kit — 新增额外键枚举值,用于特定场景的 AVSession 元数据。
- Background Tasks Kit — 提醒倒计时实例新增
repeatInterval和repeatCount。 - Core File Kit — 新增
UNCACHE打开选项、递归listFileExt、基于 mmap 的文件映射,以及将应用沙箱目录共享为系统可见目录的功能。 - Core Vision Kit — 支持图像超分辨率,以及对图像进行语义文本搜索。
- Data Augmentation Kit — 新增邮件智能处理器,支持分类、摘要和待办事项提取。
- Device Security Kit — 增强 Star Shield 机密风险控制引擎;统一风险控制凭据;支持摄像头、麦克风和位置隐私策略控制;支持文件事件订阅和筛选。
- Driver Development Kit — 支持查询外接 USB 集线器和开发用户态驱动程序。
- Enterprise Data Guard Kit — 新增文件分类策略 API
getPolicy和isKia。 - Enterprise Space Kit — 支持查询双空间状态,并判断工作空间是否为企业空间。
- FAST Kit — 支持实数 FFT 和逆 FFT;支持智能序列预测。
- Graphics Accelerate Kit — 新增游戏预启动功能,提升启动体验。
- Image Kit — 新增 GIF/JFIF/TIFF/PNG/AVIS 元数据类,以及 XMP 元数据。
- Input Kit — 支持注入键盘和鼠标输入事件。
- Live View Kit — 新增辅助区域模板,支持百分比进度环。
- NDK / JSVM — 支持通过外部内存创建
ArrayBuffer。 - NearLink Kit —
startScan可扫描所有可发现的附近 NearLink 设备。 - Network Boost Kit —
netBoost.setDataFlowDesc支持根据五元组数据设置流描述。 - Notification Kit — 增强通知管理和显示能力,包括半模态通知设置入口。
- Online Authentication Kit — 支持 DID(去中心化标识符)密钥生成、凭据导入/查询/删除和数据签名。
- PDF Kit — 支持将跨多个页面的指定区域转换为一张图像。
- Performance Analysis Kit — 支持灰度发布故障日志收集和 HiAppEvent 应用卡死警告订阅。
- Preview Kit — 支持文件加速扫描、预加载策略自定义、可用性查询和文件操作事件上报。
- Push Kit — Live View 推送消息支持 Wearable 设备。
- Remote Communication Kit — 新增用于选择 HTTP 版本的
HttpVersionSelectCallback、用于流式上传的HMS_Rcp_SetRequestGetDataCallback()、用于设置表单顺序的HMS_Rcp_SetFormOrder(),以及 QUIC C API。 - Scenario Fusion Kit — 场景共享 Button 支持图像、视频和文本。
- Share Kit — 手机向 PC/2in1/平板轻触分享时,可在接收端显示轻触位置。
- Spatial Recon Kit — 支持 3DGS Gaussian 编辑,以及根据单张照片生成空间照片。
- Scan Kit — 支持查询当前设备是否支持默认/自定义扫码 UI。 适配期间需要留意的 API 26 行为和 UX 变化:
- Ability Kit — 公共包变更通用事件(
COMMON_EVENT_PACKAGE_ADDED、REMOVED、CHANGED、CACHE_CLEARED)会在targetSdkVersion >= 26.0.0时为 In-House 应用增加限制;In-House 应用必须在app.json5中配置allowListenBundleChangedEvent,以供第三方监听器使用。 - ArkTS / JSVM — Chromium/V8 核心从 132 升级到 144;异步函数类型检测得到修复;Wasm jitless 默认行为发生变化;解析 XML 时,
fastConvertToJSObject现在会保留同级文本节点。 - ArkUI —
NodeAdapter.onAttachToNode、鼠标rawDeltaX/rawDeltaY、LayoutPolicy.matchParent、EmbeddedComponent焦点、WithTheme、主页NavDestination上的queryNavDestinationInfo/onResult、NODE_SWIPER_EVENT_ON_CONTENT_DID_SCROLL以及阴影模糊半径行为均有会影响适配的变化。 - 权限 —
ohos.permission.READ_IMAGEVIDEO、getUidRxBytes、getUidTxBytes以及通用权限策略行为会按 API 26 规则发生变化。 - UX — Button/Button 样式的 Toggle/Select/Chip/ChipGroup 的表单控件最小触摸目标从 28vp 增至 32vp;内置文本换行和小语种行高得到优化;Dialog、Toast、AlphabetIndexer 和文本选择菜单默认启用沉浸式系统材质。可通过
metadata中名称为ohos.arkui.UIMaterial.state、值为disable的配置全局禁用,或通过组件级配置uiMaterial.Material.empty禁用。
API 26 V2 行为范围说明(官方文档更新于 2026/06/17):
- 始终生效: JSVM/Chromium 从 132 升级到 144、异步函数类型检测、
fastConvertToJSObject对同级文本的保留、ArkUIrawDeltaX/rawDeltaY、仅限 Stage 的 ArkUI API 约束、主页NavDestination的queryNavDestinationInfo/onResult、@ReusableV2动态复用标识符、ArkWeb Chromium 从 132 升级到 144、READ_IMAGEVIDEO权限行为,以及 notofonts 小语种字体更新。 - 仅在
targetSdkVersion >= 26.0.0时生效: In-House 包变更事件限制、JSVM Wasm jitless 默认行为、NodeAdapter.onAttachToNode、带前置CustomSpan/ImageAttachment的富文本段落样式、LayoutPolicy.matchParent、EmbeddedComponent焦点、WithTheme、NODE_SWIPER_EVENT_ON_CONTENT_DID_SCROLL、组件阴影模糊半径、getUidRxBytes/getUidTxBytes、权限策略变化、32vp 表单控件触摸目标、内置文本样式优化、沉浸式材质默认行为,以及半模态居中对话框的最大高度。 DevEco Studio 26.0.0 Beta1 新增内容: - AI 编码:自定义 Agent 的令牌用量显示、对话回滚、使用 MCP Market 工具 / LSP 工具 / ArkTS 和 C++ 语义代码搜索进行项目问答、内置 Inline Chat 命令(如文件注释和参数校验)、
UI Verification工具,以及自定义命令。 - 编辑与调试:API 26.0.0 项目、加载 / 卸载模块、ArkUI 状态变量关系查看器、Code Scanner 资源泄漏检查、自定义 Clang-Tidy、ACL 权限请求、8 分屏预览、Car 多屏模拟器、场景模拟、远程模拟器控制、Native 调试启动加速、设备投屏、SQL 高亮数据库调试、dump 文件堆栈解析、HiLog 标签筛选、AppAnalyzer 报告诊断,以及 OOM / 应用卡顿 / 资源泄漏诊断。
- 性能分析:Memory 泳道新增 ArkWeb PA 和 JS Heap 子泳道,用于显示 Malloc 分配和 ArkWeb 渲染进程 JS 堆用量;另新增 Statistics 标签页,用于显示 VMA 数量以及 PSS 内存的最小值 / 最大值 / 平均值。
- 构建与发布:
apiCompatibilityCheck、tsImportSoCheck、模块nativeLib.enableSoDirCollection、syncNative、HvigorgetAllDependencyInfo、AppGallery 软件包重签名、Linux 模拟器支持,以及 ohpmrc 的auto_skip_install、metadata_cache_effective、metadata_cache和精确版本元数据查询。 - 兼容性变更:DevEco Studio 和 Command Line Tools 将 Node.js 从 18 升级到 24;自定义 Hvigor / ohpm / ohpm-repo 插件需要适配 Node.js 24。
ohpm-repo 5.5.1不再依赖node-fetch;依赖该内置依赖项的插件必须替换它,或自行安装[email protected]。
DevEco Testing 26.0.0 Beta1:
- 稳定性测试可针对指定入口触发稳定性问题,并扩大内存泄漏检测范围。
- UX 测试支持在直屏和折叠屏设备之间进行多设备布局对比。
- 测试服务矩阵包括本地应用列表预检、性能基线 / 监控、稳定性基线、内存泄漏测试、UX 基线和多设备布局对比、安全基线、功耗基线、功能体验基线、探索性测试、回归测试、设备投屏、UIViewer、应用图管理、性能报告自动分析,以及报告对比。
HarmonyOS AI 开发工具与能力亮点(于 2026/06 正式推出):
- DevEco Code — 专注于 HarmonyOS 的 AI 编码 Agent,支持规划、代码生成、构建 / 运行、设备日志、UI 验证、ArkTS 检查、知识查询、调试和迭代修复。它与 DevEco Studio 相辅相成,而非取代 SDK / 工具链。
- DevEco CLI — 面向 Agent 的命令行,可用于项目创建、语法检查、构建、设备运行 / 调试及其他 HarmonyOS 工程操作;可用于第三方编码 Agent、自动化和 CI/CD 集成。
- CodeGenie — DevEco Studio AI 助手 / 插件仍是独立的产品入口。华为列出的生产工具链版本包括 CodeGenie 6.1.1 Release、Command Line Tools 6.1.1 Release 和 DevEco Studio 6.1.1 Release;API 26 工具链仍为 Beta1。
- Agent Framework Kit — 通过 UI 控件从应用中启动多个系统 Agent。请将其与 Intents Kit(声明应用意图)、基于 ArkTS 脚本的应用 Skill(开放应用能力)以及设备侧 A2A(Agent 间通信)区分开来。
- HarmonyOS 7 体验领域 — 空间音频处理节点、应用 / 游戏快速启动、冷启动网络预连接、QUIC 和弱网直播优化,以及 LTPO 可变帧率是重点平台能力;在将其描述为普遍可用的 API 前,请确认已安装 API 26 SDK 且设备支持。
- API 26 云调试 — AGC 远程设备云调试可按 API 26 或系统版本
7.0.0.23筛选设备,以便尽早进行兼容性验证。 若要了解工具选择、能力边界和回答规则,请阅读references/ai-development-tools.md。
官方文档更新(2026/06/12)
华为的发布文档在 26.0.0 Beta1 之后没有新增 SDK,但文档变更日志新增了几份值得在回答中提及的面向开发者的指南:
- Ability Kit 应用生命周期 — 新指南涵盖完整的应用生命周期状态变化、启动配置(如窗口模式和起始页面路径)、快速启动、用户/应用/系统退出,以及主动/被动重启流程。
- Ark Intelligence 开发框架 — 新指南涵盖框架概览、Intent Framework 开发、基于 ArkTS 脚本的应用 Skill 开发,以及设备侧 A2A 框架开发,用于向小艺等系统 Agent 暴露应用能力。
- 窗口管理 — 20 多种开发场景经过重新整理并扩充,涵盖窗口类型、模式、布局和焦点,并新增了使用
hidumper定位常见窗口日志/问题的指南。 - ArkTS Sendable 实践 — 新的 Sendable 迁移/实践指南展示了如何在 ArkTS 中使用 TurboTransJSON 操作 Sendable 对象。
- FA 模型文档迁移 — 除轻量智能穿戴设备外,FA 模型已不再是推荐的应用模型;HarmonyOS 应用通用文档现仅保留 Stage 模型内容,而 FA 模型指南已移至轻量穿戴设备应用开发基础部分。
官方文档更新(2026/07)
尚未发布更新的 HarmonyOS 7 SDK,但几份官方指南现已影响实现和审查相关的回答:
- Native C API 兼容性 — SDK API 22+ 可对比
compatibleSdkVersion更新的 API 使用带APIAVAILABLE的弱引用。必须正确配置链接依赖、为旧设备上不存在的库配置弱库、提供运行时回退,并在兼容设备中最旧的设备和使用新 API 的设备上进行测试。仅编译成功还不够,因为缺少依赖可能只会在运行时导致失败。请阅读references/native-api-compatibility.md。 - Linux CI 流水线 — 使用 JDK 17 和对应的 Command Line Tools,优先使用其捆绑的 Node.js,安装项目/模块依赖,使用
--no-daemon运行 Hvigor,保护签名密钥,使用 HDC 安装签名后的 HAP,并启用caseSensitiveCheck,以发现被 Windows 或 macOS 隐藏的文件名/导入项不匹配问题。请阅读references/build-sign-release.md。 - ArkTS 泄漏检测 —
@ohos.hiviewdfx.jsLeakWatcher主要用于开发阶段;生产环境使用应限于小规模灰度发布人群。根据怀疑的层级,结合 JS Heap 快照、HWASan/AddrSanitizer、AppFreeze 和 HiAppEvent 使用。请阅读references/performance.md。 - 容器响应式 ArkUI — 当布局需要根据其所在组件而非窗口做出响应时,使用
ContainerReader。API 26 还介绍了@Reusable/@ReusableV2的集中式复用池;复用需要显式进行生命周期清理和状态重置。请阅读references/arkui-components.md。
项目结构(Stage 模型)
MyApp/
├─ AppScope/
│ ├─ app.json5 # global app config (bundleName, icon, label)
│ └─ resources/ # shared resources
├─ entry/ # main HAP (entry module)
│ ├─ src/main/
│ │ ├─ ets/
│ │ │ ├─ entryability/EntryAbility.ets # UIAbility subclass
│ │ │ ├─ entrybackupability/
│ │ │ └─ pages/Index.ets # ArkUI pages
│ │ ├─ resources/ # strings, colors, media, profiles
│ │ └─ module.json5 # module config, abilities, permissions
│ └─ build-profile.json5
├─ oh-package.json5 # ohpm dependencies
└─ build-profile.json5 # project-level
Stage 模型 — 核心概念
组件类型
- UIAbility — 具有 UI,可处理用户交互,是入口点。每个任务对应一个实例。
- ExtensionAbility — 用于后台/扩展场景:
ServiceExtensionAbility— 后台服务(系统应用)FormExtensionAbility— 主屏幕小组件(服务卡片)WorkSchedulerExtensionAbility— 延迟任务InputMethodExtensionAbility、WallpaperExtensionAbility、BackupExtensionAbility等
- AbilityStage — 模块级生命周期容器(每个 HAP 一个)
- WindowStage — 作用域限定于 UIAbility 的窗口容器
UIAbility 生命周期
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { }
onDestroy(): void { }
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => { /* ... */ });
}
onWindowStageDestroy(): void { }
onForeground(): void { }
onBackground(): void { }
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { }
}
启动另一个 Ability
import { common, Want } from '@kit.AbilityKit';
const ctx = getContext(this) as common.UIAbilityContext;
const want: Want = {
bundleName: 'com.example.app',
abilityName: 'DetailAbility',
parameters: { id: 42 }
};
ctx.startAbility(want);
// or startAbilityForResult for a return value
module.json5 基础要点
{
"module": {
"name": "entry",
"type": "entry", // entry | feature | shared
"srcEntry": "./ets/entryability/EntryAbility.ets",
"deviceTypes": ["phone", "tablet", "2in1"],
"abilities": [{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"skills": [{
"actions": ["action.system.home"],
"entities": ["entity.system.home"]
}]
}],
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
ArkTS 语言 — 严格性规则
ArkTS = TypeScript 减去动态模式,再加上更严格的静态类型,以提升 AOT 性能。
与 TS 相比,禁止或有变更的内容:
- 不要把
any/unknown当作日常逃生通道——请声明真实类型 - 不允许在对象上动态添加或删除属性(对未类型化对象执行
obj.foo = bar会导致编译错误) - 不允许使用
Function.prototype.bind、call、apply来重塑this - 不允许使用结构类型捷径——请使用名义类 / 接口
- 不允许使用
Object.keys/Object.assign任意重塑对象 - 对于真正的字典映射,请使用
Record<K,V> - 使用
class/interface并显式声明字段
// Prefer:
class User { constructor(public id: number, public name: string) {} }
// Over:
// const user: any = { id: 1, name: 'A' }
命名约定(官方编码风格指南)
| 元素 | 约定 | 示例 |
|---|---|---|
| 类、结构体、枚举 | UpperCamelCase | PersonInfo、ColorType |
| 变量、参数、方法 | lowerCamelCase | userName、getUserInfo() |
| 常量 | UPPER_SNAKE_CASE | MAX_VALUE、DEFAULT_TIMEOUT |
| 布尔变量 | 使用 is、has、can 前缀 | isVisible、hasPermission |
格式:缩进 2 个空格,每行最多 120 个字符,采用 K&R 花括号风格,if/for/while 始终使用 {}。
ArkTS 高性能规则(摘自官方最佳实践)
- 对不变的值使用
const——可启用引擎优化 - 不要在同一变量中混用整数和浮点数——
let n = 1; n = 1.1;会产生装箱开销 - 数值计算使用 TypedArrays(
Int8Array、Float32Array) - 避免稀疏数组——
arr[9999] = 0会强制使用哈希表存储(速度慢得多) - 不要在数组中混用类型——
[1, "a", 2]会导致优化失效 - 在热点循环外缓存属性查找结果
- 避免在性能关键循环中抛出异常——改用哨兵值
- 尽量减少热点路径中的闭包——改为通过函数参数传递变量
- 使用 Array 方法(
forEach、map、filter、reduce)——这些方法已在内部优化 - 保持
build()纯粹且声明式——不要产生副作用,在aboutToAppear()中加载数据 - 键值操作使用
HashMap而不是Record——查找和插入速度更快 - 减少多级间接导出——优先使用直接导出
export { foo } from './module' - 对启动时不需要的模块使用延迟导入(
import lazy { Foo } from './heavy')
ArkUI — 声明式 UI
自定义组件基础
@Entry // marks route/page entry
@Component
struct Index {
@State count: number = 0;
build() {
Column({ space: 12 }) {
Text(`Count: ${this.count}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('Increment')
.onClick(() => { this.count++; })
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
组件生命周期回调
所有 @Component:
| 回调 | 时机 | 说明 |
|---|---|---|
aboutToAppear() | 创建后、build() 之前 | 可在此处更改状态;更改会应用于首次 build() |
onDidBuild() | build() 完成后 | 不要更改状态或调用 animateTo()。API 12+ |
aboutToDisappear() | 销毁前 | 不要更改状态(尤其是 @Link)。不要使用 async/await |
aboutToReuse(params) | 可复用组件从缓存中重新添加时 | 使用新参数更新状态。API 10+ |
aboutToRecycle() | 组件移入复用缓存时 | 释放占用较大的资源。API 10+ |
仅限 @Entry:
| 回调 | 时机 |
|---|---|
onPageShow() | 每次页面显示时 |
onPageHide() | 每次页面隐藏时 |
onBackPress(): boolean | 用户点击返回键时。返回 true 可覆盖默认行为 |
执行顺序(冷启动):
Parent aboutToAppear → Parent build → Parent onDidBuild → Child aboutToAppear → Child build → Child onDidBuild → onPageShow
布局容器
| 容器 | 适用场景 | 性能 |
|---|---|---|
Column / Row | 线性排列 | 最佳 — 单次布局 |
Stack | 重叠 / 堆叠 | 良好 |
Flex | 子项需要拉伸 / 收缩 | 较慢 — flexGrow/flexShrink 需要额外一轮布局 |
RelativeContainer | 复杂布局,避免深层嵌套 | 良好 — 结构扁平 |
GridRow / GridCol | 响应式多设备网格 | 良好 |
List | 带回收机制的可滚动列表 | 最适合长列表(搭配 LazyForEach) |
Column/Row 对齐方式:
- 主轴:
justifyContent(FlexAlign.Start | .Center | .End | .SpaceBetween | .SpaceAround | .SpaceEvenly) - 交叉轴:Column →
alignItems(HorizontalAlign.Start | .Center | .End),Row →alignItems(VerticalAlign.Top | .Center | .Bottom)
Stack:alignContent 有 9 个位置(TopStart、Top、TopEnd、Start、Center、End、BottomStart、Bottom、BottomEnd)。zIndex 控制图层顺序。
**Flex 与 Column/Row:**Flex 的 flexShrink/flexGrow 需要重新布局。不需要 Flex 行为时,始终优先使用 Column/Row。
**RelativeContainer:**将 __container__ 用作容器本身的锚点 ID。每个子项都需要 .id()。设置 .alignRules({ top: { anchor: 'id', align: VerticalAlign.Bottom } })。
**Blank():**填充 Row/Column 中的剩余空间 — 适用于“标签……值”布局:Row() { Text('Name'); Blank(); Text('Value') }。
**displayPriority:**容器收缩时,优先级较低的子项会自动隐藏。Row() { A().displayPriority(1); B().displayPriority(3); C().displayPriority(1) } — A 和 C 会先隐藏。
**layoutWeight:**按比例调整大小 — Row() { Column().layoutWeight(1); Column().layoutWeight(2) } 的比例为 1:2。
AttributeModifier — 可复用的样式对象:
class PrimaryButtonModifier implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
instance.width('100%').height(48).fontSize(16).fontColor(Color.White).backgroundColor('#007DFF');
}
applyPressedAttribute(instance: ButtonAttribute): void {
instance.backgroundColor('#0056B3');
}
}
// Usage:
Button('Submit').attributeModifier(new PrimaryButtonModifier())
性能关键模式
大型列表使用 LazyForEach(仅渲染可见项):
class MyDataSource implements IDataSource {
private data: string[] = []
totalCount(): number { return this.data.length }
getData(index: number): string { return this.data[index] }
registerDataChangeListener(listener: DataChangeListener): void { /* ... */ }
unregisterDataChangeListener(listener: DataChangeListener): void { /* ... */ }
}
List() {
LazyForEach(this.dataSource, (item: string, index: number) => {
ListItem() { Text(item) }
}, (item: string) => item) // key generator — MUST produce unique keys
}
.cachedCount(5) // preload 5 items off-screen
@Reusable 组件(组件创建速度提升 69%):
@Reusable
@Component
struct MyListItem {
@State message: Message = new Message('default')
aboutToReuse(params: Record<string, ESObject>) {
this.message = params.message as Message
}
aboutToRecycle() { /* release heavy resources */ }
build() { Text(this.message.value) }
}
规则:仅在同一父级内有效;不要在 @Reusable 内嵌套 @Reusable;与 LazyForEach 搭配使用。
布局性能规则:
- 嵌套最多 3 层 — 每层都会增加布局开销
- 使用
if/else替代.visibility()— 隐藏的组件仍会参与布局 - 使用
RelativeContainer扁平化深层 Row/Column/Flex 层级(有记录显示性能提升 26%) - 在
Scroll内的List上设置明确的尺寸 — 否则会一次性加载所有子项 - 避免将
@StorageLink用于频繁变化的数据 — 它会向所有订阅者传播变化
onVisibleAreaChange — 在组件进入 / 离开视口时触发逻辑:
Image(item.url)
.onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => {
if (isVisible && currentRatio >= 1.0) { this.loadHighRes(); } // fully visible
if (!isVisible) { this.releaseImage(); } // off screen
})
使用场景:图片延迟加载、滚动时自动播放 / 暂停视频、曝光分析。
动画
显式动画(animateTo) — 闭包中的状态变化会触发动画:
this.getUIContext()?.animateTo({
duration: 300,
curve: Curve.EaseOut,
onFinish: () => { console.info('done') }
}, () => {
this.width = 200 // this change animates
})
属性动画(.animation()) — 隐式动画,应用于前面的属性:
Text('Hello')
.width(this.myWidth)
.animation({ duration: 500, curve: Curve.EaseIn })
曲线值:Curve.Linear | .Ease | .EaseIn | .EaseOut | .EaseInOut | .FastOutSlowIn | .Friction | .Sharp | .Smooth
弹簧曲线(字符串):'spring(velocity,mass,stiffness,damping)'、'springMotion(response,dampingFraction)'、'responsiveSpringMotion(response,dampingFraction)'
共享元素转场:
// Bind same geometryTransition ID on source and target
Image($r('app.media.photo')).geometryTransition('picture')
// Wrap state change in animateTo
this.getUIContext()?.animateTo({ duration: 300 }, () => { this.isExpanded = !this.isExpanded })
关键帧动画(keyframeAnimateTo) — 多步骤序列:
this.getUIContext()?.keyframeAnimateTo({ iterations: 2 }, [
{ duration: 100, event: () => { this.translateX = 10; } },
{ duration: 100, event: () => { this.translateX = -10; } },
{ duration: 100, event: () => { this.translateX = 0; } },
]);
**动画性能提示:**优先使用变换属性(scale/translate/rotate/opacity),而非布局属性(width/height/margin)— 变换可跳过重新布局。
Tabs — 底部 / 顶部导航
@Entry @Component
struct MainPage {
@State currentIndex: number = 0;
@Builder tabBuilder(index: number, title: string, icon: Resource) {
Column() {
SymbolGlyph(icon).fontSize(24)
.fontColor([this.currentIndex === index ? '#007DFF' : '#99000000'])
Text(title).fontSize(10).margin({ top: 4 })
.fontColor(this.currentIndex === index ? '#007DFF' : '#99000000')
}.justifyContent(FlexAlign.Center).height('100%').width('100%')
}
build() {
Tabs({ barPosition: BarPosition.End }) { // BarPosition.Start for top
TabContent() { HomePage() }
.tabBar(this.tabBuilder(0, 'Home', $r('sys.symbol.house')))
TabContent() { MinePage() }
.tabBar(this.tabBuilder(1, 'Me', $r('sys.symbol.person')))
}
.barHeight(56)
.onChange((index) => { this.currentIndex = index; })
.scrollable(false) // disable swipe between tabs
}
}
玻璃模糊标签栏:.barOverlap(true).barBackgroundBlurStyle(BlurStyle.Thin)。
Swiper — 轮播 / 横幅
Swiper() {
ForEach(this.banners, (item: BannerItem) => {
Image(item.url).width('100%').height(180).borderRadius(12)
})
}
.loop(true)
.autoPlay(true)
.interval(3000)
.indicator(new DotIndicator()
.color('#33000000').selectedColor('#007DFF')
.itemWidth(8).selectedItemWidth(16))
WaterFlow — Pinterest 风格布局
WaterFlow({ scroller: this.scroller }) {
LazyForEach(this.dataSource, (item: CardItem) => {
FlowItem() {
Column() {
Image(item.image).width('100%').borderRadius(8)
Text(item.title).fontSize(14).padding(8)
}
}
}, (item: CardItem) => item.id)
}
.columnsTemplate('1fr 1fr') // 2 columns
.columnsGap(8)
.rowsGap(8)
.cachedCount(10)
Grid — 固定网格布局
Grid() {
ForEach(this.items, (item: GridItemData) => {
GridItem() {
Column() {
Image(item.icon).width(40).height(40)
Text(item.name).fontSize(12).margin({ top: 4 })
}
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr') // 4 columns
.rowsGap(12)
.columnsGap(12)
.height(200)
TextInput / TextArea
TextInput({ placeholder: 'Enter username' })
.type(InputType.Normal) // .Email, .Number, .Password, .PhoneNumber
.maxLength(20)
.onChange((value: string) => { this.username = value; })
.onSubmit((enterKey: EnterKeyType) => { /* handle submit */ })
TextArea({ placeholder: 'Enter description', text: $$this.desc })
.maxLength(200)
.showCounter(true) // character count indicator
使用 $$ 双向绑定:TextInput({ text: $$this.value }) — 无需 onChange。
Router — 基本页面导航
import { router } from '@kit.ArkUI';
// Push to new page (with params)
router.pushUrl({
url: 'pages/Detail',
params: { id: '123', title: 'Hello' }
});
// Get params on target page
const params = router.getParams() as Record<string, string>;
// Go back
router.back();
// Replace current page (no back stack)
router.replaceUrl({ url: 'pages/Login' });
注意:对于基于 Navigation 的应用,优先使用
NavPathStack.pushPath(),而非 Router。
AlertDialog / Toast
// Alert dialog
AlertDialog.show({
title: 'Confirm',
message: 'Delete this item?',
primaryButton: { value: 'Cancel', action: () => {} },
secondaryButton: { value: 'Delete', fontColor: Color.Red,
action: () => { this.deleteItem(); }
},
});
// Toast
this.getUIContext().getPromptAction().showToast({
message: 'Operation successful',
duration: 2000,
});
常用表单组件 — 快速参考
| 组件 | 关键属性 | 示例 |
|---|---|---|
Checkbox | select、onChange((val: boolean) => {}) | Checkbox({ name: 'agree' }).select(this.agreed) |
Toggle | type(ToggleType.Switch)、isOn、onChange | Toggle({ type: ToggleType.Switch, isOn: $$this.on }) |
Radio | value、group、checked、onChange | Radio({ value: 'male', group: 'gender' }).checked(true) |
Select | options: SelectOption[]、selected、value、onSelect | Select([{value:'A'},{value:'B'}]).selected(0) |
Slider | value、min、max、step、onChange | Slider({ value: $$this.val, min: 0, max: 100 }) |
DatePicker | start、end、selected、onChange | DatePicker({ selected: this.date }).onChange((v) => {}) |
TimePicker | selected、useMilitaryTime、onChange | TimePicker({ selected: this.time }) |
Search | value、placeholder、onSubmit、onChange | Search({ value: $$this.keyword, placeholder: 'Search' }) |
Progress | value、total、type(ProgressType.Linear) | Progress({ value: 60, total: 100 }) |
LoadingProgress | — | LoadingProgress().width(48).color('#007DFF') |
EventHub — UIAbility 与页面间通信
// In UIAbility — emit event
this.context.eventHub.emit('dataReady', { items: [...] });
// In page — subscribe
const context = getContext(this) as common.UIAbilityContext;
context.eventHub.on('dataReady', (data: Record<string, ESObject>) => {
this.items = data.items;
});
// Unsubscribe
context.eventHub.off('dataReady');
HarmonyOS 6.0 视觉效果(沉浸光感视效 / 液态玻璃)
HarmonyOS 6.0(API 23)引入了系统级“沉浸光感”视觉效果。用户可通过“设置”→“桌面和个性化”→“沉浸光效”(强/均衡/弱)启用。开发者可通过以下 ArkUI 属性实现类似效果:
BlurStyle 枚举(API 9–11):
| 名称 | 起始版本 | 效果 |
|---|---|---|
Thin / Regular / Thick | API 9 | 材质模糊 |
BACKGROUND_THIN / BACKGROUND_REGULAR / BACKGROUND_THICK / BACKGROUND_ULTRA_THICK | API 10 | 景深(从最小到最大) |
COMPONENT_ULTRA_THIN / COMPONENT_THIN / COMPONENT_REGULAR / COMPONENT_THICK / COMPONENT_ULTRA_THICK | API 11 | 组件级材质 |
NONE | API 10 | 无模糊 |
backgroundBlurStyle(API 9+):
Column() { /* content */ }
.backgroundBlurStyle(BlurStyle.Thin, {
colorMode: ThemeColorMode.LIGHT, // SYSTEM | LIGHT | DARK
adaptiveColor: AdaptiveColor.DEFAULT, // DEFAULT | AVERAGE
scale: 1.0 // 0.0–1.0 (blur intensity)
})
foregroundBlurStyle(API 10+):
Image($r('app.media.photo'))
.foregroundBlurStyle(BlurStyle.Regular)
backgroundEffect(API 11+)— 精细控制:
Column() { /* content */ }
.backgroundEffect({
radius: 20, // blur radius
saturation: 15, // [0, 50] recommended
brightness: 0.6, // [0, 2] recommended
color: '#80FFFFFF' // mask color
})
blur / backdropBlur(API 7+)— 数值半径:
Column() { /* content */ }
.backdropBlur(20, { grayscale: [30, 50] }) // background blur
.blur(10) // foreground blur
backgroundBrightness(API 12+):
Column() { /* content */ }
.backgroundBrightness({ rate: 0.5, lightUpDegree: 0.2 })
视觉效果滤镜(API 12+):
import { uiEffect } from '@kit.ArkGraphics2D';
const blurFilter = uiEffect.createFilter().blur(10);
Column() { /* content */ }
.backgroundFilter(blurFilter) // background filter
.foregroundFilter(blurFilter) // content filter
pointLight(API 11+,仅限系统 API — 第三方应用不可用):
// System apps only! Supports: Image, Column, Flex, Row, Stack, Button, Toggle
Flex()
.pointLight({
lightSource: { positionX: '50%', positionY: '50%', positionZ: 80, intensity: 2, color: Color.White },
illuminated: IlluminatedType.BORDER, // NONE | BORDER | CONTENT | BORDER_CONTENT
bloom: 0.5 // luminous intensity 0–1
})
单个组件最多可由 12 个光源照亮。HarmonyOS 6.0 新增双边缘流光和紫外背景流光效果。
systemMaterialEffect(HDS 层,API 23+,仅限 HarmonyOS SDK):
import { hdsMaterial } from '@kit.ArkUI';
Column() { /* content */ }
.systemMaterialEffect({
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
})
注意:hdsMaterial 属于闭源的 HarmonyOS Design System(HDS),不属于 OpenHarmony。需要 HarmonyOS 6.0 SDK(API 23+)。
状态管理装饰器
| 装饰器 | 作用范围 | 用途 |
|---|---|---|
@State | 组件内部 | 自有可变状态;触发重新渲染 |
@Prop | 父级 → 子级 | 单向副本(子级有本地副本) |
@Link | 父级 ↔ 子级 | 双向绑定(传递时使用 $var) |
@Provide / @Consume | 祖先级 → 后代级 | 按键进行跨层级隐式绑定 |
@Observed(类)+ @ObjectLink(属性) | 类实例 | 观察类属性的变化 |
@Watch('handler') | 以上任意类型 | 值变化时的回调 |
@StorageLink / @StorageProp | 全应用范围的 AppStorage | 全局响应式状态 |
@LocalStorageLink / @LocalStorageProp | 页面作用域 | 作用域内的响应式状态 |
传递 @Link:
@Entry @Component struct Parent {
@State val: number = 0;
build() { Child({ val: $val }) } // $ prefix for @Link
}
@Component struct Child {
@Link val: number;
build() { Button(`${this.val}`).onClick(() => this.val++) }
}
观察类对象:
@Observed class Task { constructor(public title: string, public done: boolean) {} }
@Component struct TaskRow {
@ObjectLink task: Task; // re-renders when task.title/done changes
build() { Text(this.task.title) }
}
@Observed 实例数组要求在行组件中使用 @ObjectLink——父级的 @State tasks: Task[] 只会响应数组变更(push/splice/重新赋值),不会响应单项变更。
状态管理性能规则(摘自官方文档):
- 尽量缩小状态作用范围:仅将直接影响 UI 的变量声明为
@State @Prop每次更新都会创建深拷贝——对于大型对象,优先使用@Link(按引用传递)或@ObjectLink@Link是组件间通信的首选——可避免不必要的重新渲染- 对于嵌套对象,使用
@Observed+@ObjectLink——实现细粒度属性观察 @ObjectLink是只读的——不能重新赋值整个对象(this.task = new Task()会破坏绑定)- 避免对频繁变化的数据使用
@StorageLink——全局状态变更会传播给所有订阅者 - 观察深度(V1):
@State/@Prop/@Link仅观察第一层属性。不会检测嵌套变更。对于数组:仅观察 push/splice/重新赋值/length,不观察单项变更。
V2 状态装饰器(API 12+,自 API 23 起稳定——推荐用于新代码)
截至 HarmonyOS 6.1(API 23),V2 装饰器已从实验性转为稳定。官方建议:新项目迁移到 V2。
| V1 | V2 替代项 | 变更 |
|---|---|---|
@Component | @ComponentV2 | 语义更清晰 |
@State | @Local | 不能从外部初始化——仅用于内部状态 |
@Prop | @Param + @Once | 只读输入;@Once 用于一次性初始化 |
@Link | @Param + @Event | 双向:通过 @Param 输入,通过回调 @Event 输出 |
@Observed + @ObjectLink | @ObservedV2 + @Trace | 支持跨多层嵌套的深度观察 |
@Watch | @Monitor('prop') | 更精确的深度监听器 |
AppStorage | AppStorageV2 | 与 @ObservedV2 + @Trace 统一 |
| (无) | PersistenceV2 | 使用 V2 观察的持久化存储;自动保存到磁盘 |
@Provide / @Consume | @Provider() / @Consumer() | 更名;语义相同 |
@ObservedV2
class UserInfo {
@Trace name: string = ''; // changes to this trigger UI refresh
@Trace age: number = 0; // changes to this trigger UI refresh
address: string = ''; // NO @Trace → changes do NOT trigger refresh
}
规则:@ObservedV2 和 @Trace 必须配合使用(单独使用任一装饰器都不起作用)。只有使用 @Trace 装饰的属性才会参与 UI 渲染。
AppStorageV2——全局响应式状态:
import { AppStorageV2 } from '@kit.ArkUI';
@ObservedV2
class UserState { @Trace name: string = 'Guest'; }
// Connect (creates if not exists)
const user = AppStorageV2.connect(UserState, 'user', () => new UserState())!;
// In component
@ComponentV2
struct Header {
user: UserState = AppStorageV2.connect(UserState, 'user')!;
build() { Text(this.user.name) }
}
PersistenceV2——自动持久化的状态(应用重启后仍然保留):
import { PersistenceV2, Type } from '@kit.ArkUI';
@ObservedV2
class Settings {
@Trace @Type(String) theme: string = 'light';
@Trace @Type(Number) fontSize: number = 14;
}
// Connect — auto-loads from disk if exists, writes on change
const settings = PersistenceV2.connect(Settings, 'app_settings', () => new Settings())!;
// Optional: error/success callback
PersistenceV2.notifyOnError((key, reason, msg) => { console.error(reason, msg); });
PersistenceV2 要正确序列化,必须使用
@Type装饰器。
StateStore——全局状态管理(2026 年,官方推荐用于中大型应用)
将状态逻辑与 UI 组件完全分离。与 @ObservedV2 + @Trace 配合使用。
import { StateStore } from '@kit.ArkUI';
@ObservedV2
class CounterStore {
@Trace count: number = 0;
increment(): void {
this.count++;
}
}
// Create global store (do this once, e.g. in EntryAbility or top-level)
const counterStore = StateStore.createStore(new CounterStore());
// In any component — read state
@Entry
@Component
struct CounterPage {
build() {
Column() {
Text(`Count: ${counterStore.getState().count}`)
Button('Add').onClick(() => {
counterStore.getState().increment();
})
}
}
}
适用场景: 多个页面/组件共享相同状态;状态逻辑复杂;需要线程安全的更新(TaskPool 工作线程可以安全地更新 StateStore)。
文档:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-global-state-management-state-store
构建器、样式、扩展
@Builder function Label(text: string) { Text(text).fontSize(16) }
@Styles function Card() { .padding(12).borderRadius(12).backgroundColor('#FFF') }
@Extend(Text) function title() { .fontSize(22).fontWeight(FontWeight.Bold) }
// Usage:
Text('Hello').title()
Column() { Label('x') } .Card()
导航(推荐使用 Navigation 组件,而非 Router)
@Entry @Component struct App {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();
build() {
Navigation(this.pathStack) {
Button('Go').onClick(() => this.pathStack.pushPath({ name: 'Detail', param: 42 }))
}
.navDestination((name: string, param: Object) => {
if (name === 'Detail') Detail({ id: param as number })
})
}
}
旧版 router 模块(@ohos.router)仍然可用,但正在逐步淘汰——自 API 12 起,Navigation + NavPathStack 是官方替代方案。Huawei 发布了从 router 迁移到 Navigation 的过渡指南。新项目请始终使用 Navigation;对于旧代码,可在方便时进行迁移。
NavPathStack 完整 API:
// Push
pathStack.pushPath({ name: 'Page', param: data });
pathStack.pushPathByName('Page', data);
pathStack.pushPathByName('Page', data, (popInfo) => {
console.info('Pop result: ' + JSON.stringify(popInfo.result));
});
// Pop, Replace, Remove
pathStack.pop();
pathStack.replacePath({ name: 'Page', param: data });
pathStack.removeIndex(0);
pathStack.movePageToTop('Page');
// Query
pathStack.getParamByIndex(index);
pathStack.getParamByName('Page');
pathStack.getAllPathName();
pathStack.size();
路由拦截:
pathStack.setInterception({
willShow: (from, to, operation) => { /* validate/redirect */ },
didShow: (from, to, operation) => { /* analytics */ }
});
显示模式:Stack(单列)、Split(双列)、Auto(自适应,阈值为 600vp)。
HarmonyOS Kits(常用导入)
import { UIAbility, Want, common, abilityAccessCtrl } from '@kit.AbilityKit';
import { window, display, promptAction } from '@kit.ArkUI';
import { http, webSocket, connection } from '@kit.NetworkKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit'; // photo/video picker
import { fileIo as fs } from '@kit.CoreFileKit';
import { geoLocationManager } from '@kit.LocationKit';
import { relationalStore, preferences, distributedKVStore } from '@kit.ArkData';
import { notificationManager } from '@kit.NotificationKit';
import { speechRecognizer } from '@kit.CoreSpeechKit'; // ASR / TTS
import { hilog } from '@kit.PerformanceAnalysisKit';
完整 Kit 目录——官方 SDK 类别(developer.huawei.com/consumer/cn/sdk/)
应用框架 Application Framework
| Kit | 导入键 | 用途 |
|---|---|---|
| Ability Kit | AbilityKit | UIAbility、ExtensionAbility、Want、上下文、路由 |
| Accessibility Kit | AccessibilityKit | 屏幕阅读器、通用设计、无障碍服务 |
| ArkData | ArkData | relationalStore、preferences、distributedKVStore |
| ArkTS | — | 语言规范和 Ark 编译器工具 |
| ArkUI | ArkUI | window、display、promptAction、Navigation、组件 |
| ArkWeb | ArkWeb | WebView / Web 组件嵌入 |
| Background Tasks Kit | BackgroundTasksKit | 延迟/定时后台工作、临时任务 |
| Core File Kit | CoreFileKit | fileIo、选择器、沙箱路径 |
| Form Kit | FormKit | FormExtensionAbility、主屏幕服务卡片/小组件 |
| IME Kit | IMEKit | 输入法引擎开发 |
| IPC Kit | IPCKit | 进程间通信(Parcel、RemoteObject) |
| Localization Kit | LocalizationKit | i18n、l10n、RTL、伪本地化 |
应用服务 Application Services
| Kit | 导入键 | 用途 |
|---|---|---|
| Account Kit | AccountKit | 一键 Huawei ID 登录、OAuth 2.0 |
| Ads Kit | AdsKit | 广告 SDK(横幅、原生、插屏) |
| App Linking Kit | AppLinkingKit | 深层链接、延迟深层链接 |
| Calendar Kit | CalendarKit | 日历事件、提醒 |
| Contacts Kit | ContactsKit | 联系人读取/写入/搜索 |
| Content Embed Kit | ContentEmbedKit | 跨应用文档嵌入和协同编辑 |
| IAP Kit | IAPKit | 应用内购买(消耗型、非消耗型、订阅) |
| Live View Kit | LiveViewKit | 锁屏/通知中心中的实时活动 |
| Location Kit | LocationKit | geoLocationManager、地理围栏、地理编码 |
| Map Kit | MapKit | Huawei Maps SDK、路线规划、地点搜索 |
| Notification Kit | NotificationKit | 本地通知和推送通知、角标 |
| Payment Kit | PaymentKit | Huawei Pay 交易处理 |
| Push Kit | PushKit | 推送通知投递服务 |
| Scan Kit | ScanKit | 二维码/条形码扫描 |
| Share Kit | ShareKit | 跨应用内容分享 |
| Call Service Kit | CallServiceKit | 企业来电识别和服务查询 |
| Weather Service Kit | WeatherServiceKit | 天气数据(当前/每日/每小时/预警/指数/潮汐) |
| Pen Kit | Penkit | 触控笔/手写组件(M-Pencil 设备) |
| Wear Engine | WearEngine | 手机↔手表通信、设备发现 |
| Health Service Kit | HealthServiceKit | 健康数据服务 |
系统 System
| 套件 | 导入键 | 用途 |
|---|---|---|
| Asset Store Kit | AssetStoreKit | 安全的凭据/机密存储 |
| Basic Services Kit | BasicServicesKit | 电池、振动、温度、亮度、剪贴板 |
| Connectivity Kit | ConnectivityKit | 蓝牙、Wi-Fi、NFC、USB、热点 |
| Crypto Architecture Kit | CryptoArchitectureKit | 加密、密钥管理、证书 |
| Device Security Kit | DeviceSecurityKit | 代码签名查询、数字盾牌、安全事件 |
| Distributed Service Kit | DistributedServiceKit | deviceManager、跨设备发现 |
| Enterprise Threat Protection Kit | EnterpriseThreatProtectionKit | 企业文件威胁扫描、隔离、恢复、删除 |
| MDM Kit | MDMKit | 企业设备、应用和设置管理 |
| Network Boost Kit | NetworkBoostKit | 网络传输优化和低功耗传输模式 |
| NearLink Kit | NearLinkKit | NearLink 设备能力和合作伙伴设备管理 |
| Screen Time Guard Kit | ScreenTimeGuardKit | 屏幕使用时间授权和应用控制策略 |
媒体 Media
| 套件 | 导入键 | 用途 |
|---|---|---|
| Audio Kit | AudioKit | 音频播放、录制、路由、焦点 |
| AVCodec Kit | AVCodecKit | 硬件加速编解码(H.264、H.265、AAC……) |
| Camera Kit | CameraKit | 相机预览、拍照、视频录制 |
| Image Kit | ImageKit | 图像解码、转换、EXIF |
| Media Kit | MediaKit | AVPlayer、AVRecorder(统一播放/录制) |
| Media Library Kit | MediaLibraryKit | photoAccessHelper、媒体库 CRUD |
| Scan Kit | ScanKit | 二维码/条形码扫描 |
图形 Graphics
| 套件 | 导入键 | 用途 |
|---|---|---|
| ArkGraphics 2D | ArkGraphics2D | 2D Canvas 绘制、特效、模糊、阴影 |
| ArkGraphics 3D | ArkGraphics3D | 3D 场景图、glTF 渲染 |
| UI Design Kit | UIDesignKit | hdsDrawable 图标处理、HdsNavigation 组件 |
| XComponent | (ArkUI 内置) | 通过 NAPI 提供原生 OpenGL ES / Vulkan 表面 |
AI
| 套件 | 导入键 | 用途 |
|---|---|---|
| Core Speech Kit | CoreSpeechKit | 端侧 ASR / TTS 引擎 |
| Core Vision Kit | CoreVisionKit | OCR、人脸检测、图像分割、超分辨率 |
| CANN Kit | CANNKit | AI 计算加速;API 24 新增 PC 大模型推理支持 |
| FAST Kit | FASTKit | 并发哈希表、向量运算、滤波器 |
| Intents Kit | IntentsKit | 意图识别(30 多个领域、60 多个内置意图) |
| MindSpore Lite | MindSporeLiteKit | 边缘模型推理(Caffe/TF/ONNX/MindIR) |
| Natural Language Kit | NaturalLanguageKit | 分词、NER、关键词提取 |
性能与开发工具
| 套件 | 导入键 | 用途 |
|---|---|---|
| Performance Analysis Kit | PerformanceAnalysisKit | hilog、HiAppEvent、崩溃分析 |
照片/视频选择器(正确 API — picker.PhotoViewPicker 已弃用)
import { photoAccessHelper } from '@kit.MediaLibraryKit';
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
const picker = new photoAccessHelper.PhotoViewPicker();
const result = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 9
});
const uris: string[] = result.photoUris;
文件/文档选择器
import { picker } from '@kit.CoreFileKit';
// Pick document files (PDF, DOCX, etc.)
const documentPicker = new picker.DocumentViewPicker(context);
const result = await documentPicker.select({
maxSelectNumber: 5,
fileSuffixFilters: ['.pdf', '.docx', '.xlsx'], // optional filter
});
const uris: string[] = result; // file URIs with temporary read permission
// Save file to user-chosen location
const savePicker = new picker.DocumentViewPicker(context);
const saveResult = await savePicker.save({
newFileNames: ['export.pdf'],
});
const destUri: string = saveResult[0];
CoreSpeechKit — ASR 音频格式要求
import { speechRecognizer } from '@kit.CoreSpeechKit';
const engine = await speechRecognizer.createEngine({
language: 'zh-CN',
online: 0 // 0=on-device, 1=cloud
});
// Audio MUST be: PCM, 16kHz, mono, 16-bit
// Chunk size: 640 or 1280 bytes, write every 20ms or 40ms
engine.startListening({
sessionId: `asr_${Date.now()}`,
audioInfo: {
audioType: 'pcm',
sampleRate: 16000,
soundChannel: 1,
sampleBit: 16
}
});
HTTP 请求示例
import { http } from '@kit.NetworkKit';
async function fetchJson(url: string): Promise<string> {
const req = http.createHttp();
try {
const res = await req.request(url, {
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' },
connectTimeout: 5000, readTimeout: 10000
});
return res.result as string;
} finally {
req.destroy();
}
}
网络连接监控
import { connection } from '@kit.NetworkKit';
// Check current network state
const hasNet = connection.hasDefaultNetSync();
// Monitor network changes
const netCon = connection.createNetConnection();
netCon.on('netAvailable', () => { /* network restored */ });
netCon.on('netLost', () => { /* network lost */ });
netCon.on('netCapabilitiesChange', (info: connection.NetCapabilityInfo) => {
const isWifi = info.netCap.bearerTypes.includes(connection.NetBearType.BEARER_WIFI);
const isCellular = info.netCap.bearerTypes.includes(connection.NetBearType.BEARER_CELLULAR);
});
netCon.register(() => {}); // activate subscriptions
// Cleanup
netCon.unregister(() => {});
WebSocket
import { webSocket } from '@kit.NetworkKit';
const ws = webSocket.createWebSocket();
ws.on('open', () => { ws.send('hello'); });
ws.on('message', (err, data: string | ArrayBuffer) => {
console.info('received:', typeof data === 'string' ? data : 'binary');
});
ws.on('close', (err, { code, reason }) => { /* closed */ });
ws.on('error', (err) => { /* handle */ });
ws.connect('wss://example.com/ws', { header: { 'Authorization': 'Bearer xxx' } });
// Send
ws.send(JSON.stringify({ type: 'chat', text: 'hi' }));
// Close
ws.close();
按类型启动应用(startAbilityByType)
import { common } from '@kit.AbilityKit';
const context = getContext(this) as common.UIAbilityContext;
// Open navigation app with destination
context.startAbilityByType('navigation', {
sceneType: 1,
destinationLatitude: 39.9042,
destinationLongitude: 116.4074,
destinationName: 'Beijing',
} as Record<string, Object>);
// Open browser
context.startAbilityByType('browser', {
uri: 'https://example.com',
} as Record<string, Object>);
支持的类型:navigation、browser、email、finance、transit 等。
权限
在 module.json5 → requestPermissions 中声明。对于需要用户授予的权限,可在运行时通过 abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context, [...]) 请求。
原子化服务 / 元服务(原子化服务 / 元服务)
无需安装、体积较小(≤10MB)的应用,可通过以下方式启动:
- 服务卡片(使用
FormExtensionAbility构建的主屏幕小组件) - 搜索、扫码、NFC、跨设备接续
通过 module.json5 中的 "installationFree": true 配置。构建时会生成包含多个 HAP 的 .app 包。
分布式功能
- 跨设备接续(流转)—
continueAbility/onContinue/onNewWant生命周期 - 分布式数据对象 — 在设备间同步状态
- 分布式文件系统 — 共享沙箱
- 设备管理器 — 发现可信设备
打包类型
| 类型 | 用途 |
|---|---|
| HAP(Harmony Ability Package) | 可运行模块:entry 或 feature 类型 |
| HSP(Harmony Shared Package) | 应用内共享模块,运行时动态链接 |
| HAR(Harmony Archive) | 静态库,在构建时打包到 HAP 中 |
通过 AppGallery Connect(华为应用市场)分发。
官方示例:https://developer.huawei.com/consumer/cn/samples/
sys.symbol — 图标字形系统
HarmonyOS Symbol 是一种包含 1500 多个矢量图标的字体,支持多层着色和 7 种动画类型。
SymbolGlyph($r('sys.symbol.bell_fill'))
.fontSize(24)
.fontColor([Color.Blue, Color.Green])
.symbolEffect(new BounceSymbolEffect(EffectScope.WHOLE), true)
已确认可用的名称(SDK 6.0.1):xmark · plus · minus · checkmark · chevron_right · chevron_left · star · star_fill · bell · bell_fill · doc · video · mic · mic_fill · clock · trash · pencil · camera · person
不存在的名称(常见错误):photo · doc_richtext · sparkles · checklist · image(请改用 doc 或 camera)· location_fill(请改用文本标签)
查找有效名称:https://developer.huawei.com/consumer/cn/design/harmonyos-symbol
DevEco Studio 6.x — 项目设置
DevEco Studio 6.x 需要额外的 Hvigor 基础设施文件。缺少这些文件时,IDE 会显示“工程结构及配置需要升级”。
当前 API 24 Release 工具链(HarmonyOS 6.1.1,2026/05/26):
- DevEco Studio:6.1.1 Release (6.1.1.280)
- HarmonyOS SDK:6.1.1 Release(OpenHarmony SDK
Ohos_sdk_public 6.1.1.125,API 24 Release) - Hvigor / hvigorw:6.24.2
- ohpm:6.1.2.268
- Node.js:18.20.1
compileSdkVersion:6.1.1(24)targetSdkVersion:4.0.0(10) ~ 6.1.1(24)
**推荐工作流:**始终让 DevEco Studio 生成脚手架(新建项目 → Empty Ability),然后再复制源文件。这样可以避免使用过时的 Hvigor 包装器/插件版本。
hvigorfile.ts(根目录和每个模块)
// root
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default { system: appTasks, plugins:[] }
// entry/hvigorfile.ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default { system: hapTasks, plugins:[] }
hvigor/wrapper/hvigor-config.json5
{
// Use the exact version generated by DevEco Studio for your SDK.
// API 24 Release uses Hvigor 6.24.2.
"hvigorVersion": "6.24.2",
"dependencies": {
// Keep this aligned with the DevEco-generated scaffold.
"@ohos/hvigor-ohos-plugin": "<generated-by-deveco>"
}
}
build-profile.json5 — SDK 版本配置位置
SDK 版本只能在一个位置配置——要么放在 app 下,要么放在每个 product 内,不能同时配置。混用会导致“Sync Failed”。
API 24 的 DevEco Studio 会在项目级 build-profile.json5 的 strictMode 下添加 strictCheckerOnly,用于仅对 .ets 文件运行严格语法检查,从而缩短检查期间的端到端编译时间:
{
"app": {
"compileSdkVersion": "6.1.1(24)",
"compatibleSdkVersion": "4.0.0(10)",
"targetSdkVersion": "6.1.1(24)",
"strictMode": {
"strictCheckerOnly": true
}
}
}
API 24 还会在项目级 oh-package.json5 中添加 properties,用于多环境依赖管理。请用它配置特定环境的依赖值,而不要复制多个包文件。
ArkTS 严格模式编译器错误(SDK 6.0.1)
不允许将对象字面量用作类型
// ❌
parseOutput(): { text: string; tags: string[] } { ... }
// ✅
interface ParsedOutput { text: string; tags: string[]; }
parseOutput(): ParsedOutput { ... }
不允许使用 any / unknown
const task = JSON.parse(rawStr) as AgentTask; // ✅ cast immediately
navDestination 需要 @Builder 函数引用
// ❌ inline lambda
.navDestination((name, param) => { if (name==='X') MyPage() })
// ✅ top-level @Builder
@Builder function PageRouter(name: string) { if (name==='X') MyPage() }
Navigation(stack) { ... }.navDestination(PageRouter)
@Entry 的 build() 根节点必须是容器
请用 Stack()、Column() 或 Row() 包裹——自定义组件本身不是容器。
display.width 的单位是像素,而非 vp
function isTablet(): boolean {
const dm = display.getDefaultDisplaySync();
return (dm.width / dm.densityPixels) >= 840;
}
所有 user_grant 权限都需要 reason + usedScene
{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:permission_media_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
INTERNET 属于 system_grant——无需额外字段。
常见注意事项
-
不要混用 FA 和 Stage 模型 — FA 是旧版;HarmonyOS NEXT 仅支持 Stage。
-
ArkUI 回调中的
this— 必须使用箭头函数;常规的function () {}会丢失this。 -
嵌套对象上的
@State— 嵌套属性的更改不会触发更新;请使用@Observed/@ObjectLink,或重新赋值整个对象。 -
数组项更新 — 替换数组项(
arr[i] = newItem),或在数组项类上使用@Observed。 -
资源引用 — 使用
$r('app.string.foo')、$r('app.media.icon'),而不是字符串路径。 -
组件内的
getContext(this)返回UIAbilityContext;请显式转换类型。 -
禁止在
build()中执行异步操作 — 请在aboutToAppear()中加载数据,并存储到@State中。 -
对于用户授权权限,必须声明权限并在运行时请求权限。
-
ohpm 是包管理器(类似于 npm)— 依赖项位于
oh-package.json5中。 -
在设备上预览 — DevEco Previewer 无法完全模拟;务必在真实 HarmonyOS 设备或模拟器上测试。
-
Navigation 没有
hideSideBar— 请改用.hideBackButton(true)。 -
promptAction.showToast()已弃用 — 请改用getUIContext().getPromptAction().showToast(...);为确保安全,请用 try-catch 包裹。 -
悬浮 FAB 按钮会遮挡列表最后一项 — 使用
Navigation.menus()添加主要操作按钮,或为 List 添加底部内边距,其值等于 FAB 高度加边距。 -
为
on/off使用具名回调 — 匿名函数无法注销。请始终保存引用:// 错误:无法 off() 匿名函数 session.on('stateChange', (state) => { ... }); // 正确:使用具名引用 const cb = (state: StateType) => { ... }; session.on('stateChange', cb); session.off('stateChange', cb); -
批量修改状态 — 多次更改
@State会触发多次重新渲染。请先累积到临时变量中,再赋值一次:// 错误:重新渲染 3 次 this.list.push(a); this.list.push(b); this.list.push(c); // 正确:重新渲染 1 次 const tmp = [...this.list, a, b, c]; this.list = tmp; -
状态装饰器选择优先级 —
@State+@Prop/@Link/@ObjectLink(父子层级)>@Provide+@Consume(深层嵌套)>LocalStorage(页面级)>AppStorage(全局)。请避免将AppStorage用于频繁变化的数据。 -
单位转换 — 通过
this.getUIContext().px2vp(value)使用px2vp(px)/vp2px(vp)。屏幕密度:display.getDefaultDisplaySync().densityPixels。 -
保持屏幕常亮 — 视频播放或导航期间调用
win.setWindowKeepScreenOn(true);暂停时重置。
稳定性 — 崩溃类型与错误处理
| 类型 | 描述 |
|---|---|
| JS_ERROR | ArkTS/JS 运行时异常(最常见)— TypeError: Cannot read property 'x' of undefined |
| CPP_CRASH | 原生 C/C++ 崩溃(SIGSEGV、SIGABRT) |
| APP_FREEZE | 主线程阻塞超过 6s(相当于 ANR)。根本原因:线程锁(57%)、系统资源(14%)、主线程繁重工作(9%) |
| OOM | 内存不足导致进程被终止 |
全局错误处理程序:
import { errorManager } from '@kit.AbilityKit';
const observer: errorManager.ErrorObserver = {
onUnhandledException(errMsg: string): void {
console.error('Uncaught: ' + errMsg);
},
onException?(errObject: Error): void { // API 10+
console.error(errObject.name + ': ' + errObject.message);
}
};
const observerId = errorManager.on('error', observer);
崩溃事件订阅(HiAppEvent):
import { hiAppEvent } from '@kit.PerformanceAnalysisKit';
hiAppEvent.addWatcher({
name: "crashWatcher",
appEventFilters: [{
domain: hiAppEvent.domain.OS,
names: [hiAppEvent.event.APP_CRASH]
}],
onReceive: (domain, appEventGroups) => { /* process crash */ }
});
后台任务 — 4 种类型
| 类型 | API | 时长 | 用例 |
|---|---|---|---|
| Transient | requestSuspendDelay | 最长约 3 min | 保存数据、上传日志 |
| Continuous | startBackgroundRunning | 不限(需要通知) | 音乐、导航、录音 |
| Deferred | workScheduler.startWork | 由系统决定 | 定时同步、清理 |
| Agent Reminders | ReminderRequestTimer | 由系统管理 | 闹钟、计时器 |
Continuous 任务示例:
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
// module.json5: "abilities": [{ "backgroundModes": ["audioRecording"] }]
// permission: ohos.permission.KEEP_BACKGROUND_RUNNING
const info: wantAgent.WantAgentInfo = {
wants: [{ bundleName: 'com.example.app', abilityName: 'MainAbility' }],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0, actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
const agent = await wantAgent.getWantAgent(info);
backgroundTaskManager.startBackgroundRunning(
this.context, backgroundTaskManager.BackgroundMode.AUDIO_RECORDING, agent
);
9 种后台模式:dataTransfer · audioPlayback · audioRecording · location · bluetoothInteraction · multiDeviceConnection · taskKeeping(仅限二合一设备)
根据用户活动确定的 Deferred 任务频率: 活跃=2h,频繁=4h,常规=24h,较少=48h,从未使用=禁止。
安全编码规则(摘自官方最佳实践)
- 对非交互式能力设置
exported: false - 校验所有跨越信任边界的参数(Want intent、rpc.RemoteObject)
- 使用参数化查询 — 绝不通过字符串拼接构造 SQL
- 将 HTTP 替换为 HTTPS;校验 SSL 证书
- 绝不在剪贴板中存储个人数据
- 使用
Asset Store Kit存储敏感短数据(密码、令牌) - 避免通过隐式 intent 传递个人数据
- 在生产构建中使用代码混淆
- 使用精确的
InputType(.USER_NAME、.Password)实现系统级保护 - 绝不在生产版本中使用调试签名
权限检查 + 请求模式:
import { abilityAccessCtrl, bundleManager } from '@kit.AbilityKit';
// Check
const atManager = abilityAccessCtrl.createAtManager();
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
const status = await atManager.checkAccessToken(
bundleInfo.appInfo.accessTokenId, 'ohos.permission.CAMERA');
// Step 1 — Request from user
const result = await atManager.requestPermissionsFromUser(context,
['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE']);
if (result.authResults[0] === 0) {
// Granted
} else if (result.dialogShownResults?.[0]) {
// User saw dialog but denied — show in-app guidance, don't re-pop
} else {
// Step 2 — Fallback: open settings dialog (user previously denied permanently)
atManager.requestPermissionOnSetting(context,
['ohos.permission.CAMERA']).then((statuses) => {
// statuses[0]: 0 = granted, -1 = denied
});
}
数据加密级别: EL1(设备级)→ EL2(用户级,默认)→ EL3(锁定时可访问)→ EL4(锁定时不可访问)
网络安全配置 — 生产应用使用 HTTPS/证书固定:
创建 src/main/resources/base/profile/network_config.json:
{
"network-security-config": {
"domain-config": [{
"domains": [{ "include-subdomains": true, "name": "api.example.com" }],
"trust-anchors": [{ "certificates": "/data/storage/el1/bundle/entry/resources/resfile/ca_cert.pem" }]
}]
}
}
在 module.json5 中引用:"metadata": [{ "name": "NetworkSecurityConfig", "resource": "$profile:network_config" }]。
测试 — arkxtest 框架
软件包:@ohos/hypium(Mocha 风格)。包含三个子框架:JsUnit(单元测试)、UiTest(UI 自动化)、PerfTest(性能测试)。
JsUnit
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } from '@ohos/hypium';
export default function abilityTest() {
describe('MyTestSuite', () => {
beforeAll(() => { /* once before all */ });
beforeEach(() => { /* before each */ });
afterEach(() => { /* after each */ });
afterAll(() => { /* once after all */ });
it('sync_test', 0, () => {
expect(1 + 1).assertEqual(2);
});
it('async_test', 0, async (done: Function) => {
let result = await someAsyncOp();
expect(result).assertContain('expected');
done();
});
});
}
主要断言: assertEqual(v) · assertContain(v) · assertTrue() · assertFalse() · assertNull() · assertUndefined() · assertNaN() · assertInstanceOf(type) · assertThrowError(fn) · assertDeepEquals(v) · assertClose(v, tolerance) · assertLarger(v) · assertLess(v) · not()(取反)· assertPromiseIsResolved() · assertPromiseIsRejected()
测试文件位于 entry/src/ohosTest/ets/test/。UI 自动化请参阅下方的 UiTest 部分。
**仓颉(Cangjie)**是华为的新语言(测试版)——在 Cangjie 稳定之前,所有生产应用都使用 ArkTS。
ArkCompiler — 运行时详情
- AOT 模式 — 静态类型信息用于生成优化的本机机器码;无需 JIT 预热
- LiteActor 并发 — Actor 模型,每个线程的内存彼此隔离;通过消息传递进行通信
TaskPool 与 Worker 对比
| 特性 | TaskPool | Worker |
|---|---|---|
| 线程管理 | 自动(创建/复用/销毁) | 手动管理生命周期 |
| 最大线程数 | 根据物理核心数自动扩展 | 每个进程最多 64 个 |
| 任务时长限制 | 3 分钟(不包括异步 I/O) | 无限制 |
| 优先级 | HIGH / MEDIUM / LOW / IDLE | 仅 API 18+ |
| 取消 | 支持 | 不支持 |
| 线程复用 | 是 | 否 |
| 任务组 | 支持 | 不支持 |
| 延迟/周期执行 | 支持 | 不支持 |
**适用 Worker 的情况:**任务超过 3 分钟、需要持久状态,或需要强关联的同步任务。
@Concurrent 规则
- 所有 TaskPool 函数都必须使用,且仅限
.ets文件 - 允许使用:普通函数、异步函数
- **禁止使用:**箭头函数、类方法、匿名函数、生成器函数
- 不得使用闭包变量 — 无法引用外层作用域;只能使用局部变量、参数和导入项
- 无法调用同一文件中的其他函数(违反闭包规则)— 必须导入这些函数
import { taskpool } from '@kit.ArkTS';
@Concurrent
function heavyCalc(n: number): number { return n * n; }
const result = await taskpool.execute(heavyCalc, 42);
// With priority
const task = new taskpool.Task(heavyCalc, 42);
await taskpool.execute(task, taskpool.Priority.HIGH);
// Delayed and periodic
taskpool.executeDelayed(heavyCalc, 2000, 42); // after 2s
taskpool.executePeriodically(heavyCalc, 5000, 42); // every 5s
// TaskGroup — execute multiple tasks as a group
const group = new taskpool.TaskGroup();
group.addTask(heavyCalc, 10);
group.addTask(heavyCalc, 20);
group.addTask(heavyCalc, 30);
const results = await taskpool.execute(group); // returns array of results
**长时间运行的任务:**TaskPool 中的异步代码(Promise/I/O)没有时间限制(只有受 CPU 限制的同步代码才受 3 分钟限制)。HarmonyOS 6.0 正式支持在 TaskPool 中运行长时间异步任务。
@Sendable — 共享堆引用传递
SharedHeap(进程级,所有线程均可访问)上的对象 — 对于 1MB 数据,速度比序列化快 100 倍。
@Sendable
class SharedData {
value: number = 0; // must be explicitly initialized
}
**限制:**只能继承 Sendable 类。属性类型仅限:基本类型、Sendable 类、@arkts.collections 容器。运行时不能添加/删除属性。不能使用计算属性。不能使用 # 私有成员(请使用 private 关键字)。
Worker 模式
// Main thread
const worker = new worker.ThreadWorker('entry/ets/workers/myWorker.ets');
worker.postMessage({ type: 'compute', data: payload });
worker.onmessage = (e) => { /* handle result */ };
worker.terminate();
// Worker thread (myWorker.ets)
const workerPort = worker.workerPort;
workerPort.onmessage = (e) => {
const result = processData(e.data);
workerPort.postMessage(result);
};
**TaskPool 和 Worker 均适用:**无法从工作线程访问 AppStorage 或 UI 库。不同的线程上下文无法共享上下文对象。
OHPM — 包管理器
ohpm install @ohos/axios # install a library
ohpm install # install all from oh-package.json5
oh-package.json5 示例:
{
"name": "entry",
"dependencies": {
"@ohos/axios": "^2.0.0"
}
}
OHPM 中央仓库:https://developer.huawei.com/consumer/cn/deveco-service
常用 ohpm 库
@ohos/axios — HTTP 客户端(ohpm install @ohos/axios,需要 ohos.permission.INTERNET):
import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from '@ohos/axios';
// GET (generic response type)
interface UserInfo { id: number; name: string }
axios.get<UserInfo, AxiosResponse<UserInfo>, null>('/user', { params: { ID: 12345 } })
.then((res: AxiosResponse<UserInfo>) => console.info(res.data.name))
.catch((err: AxiosError) => console.error(err.message));
// POST
interface NewUser { firstName: string; lastName: string }
axios.post<string, AxiosResponse<string>, NewUser>('/user',
{ firstName: 'Fred', lastName: 'Flintstone' });
// Create instance (recommended for project-wide use)
const http = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: { 'X-Custom-Header': 'value' },
});
// Interceptors
http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.headers['Authorization'] = 'Bearer ' + AppStorage.get<string>('token');
return config;
}, (err: AxiosError) => Promise.reject(err));
http.interceptors.response.use(
(res: AxiosResponse) => res,
(err: AxiosError) => {
if (err.response?.status === 401) { /* redirect to login */ }
return Promise.reject(err);
}
);
// File download
import fs from '@ohos.file.fs';
const filePath = getContext(this).cacheDir + '/file.jpg';
axios({ url: 'https://example.com/file.jpg', method: 'get', filePath,
onDownloadProgress: (e) => {
console.info('Progress: ' + (e.loaded && e.total ? Math.ceil(e.loaded / e.total * 100) : 0) + '%');
}
});
// File upload (FormData)
import { FormData } from '@ohos/axios';
const form = new FormData();
form.append('file', 'internal://cache/photo.jpg');
axios.post<string, AxiosResponse<string>, FormData>('/upload', form, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (e) => console.info(e.loaded + '/' + e.total),
});
// Cancel request
const ctrl = new AbortController();
axios.get('/slow', { signal: ctrl.signal });
ctrl.abort();
responseType:'string'|'object'|'array_buffer'。双向 TLS 需要 API 11 或更高版本。
@ohos/pulltorefresh — 下拉刷新 + 加载更多(ohpm install @ohos/pulltorefresh,支持 List/Scroll/Tabs/Grid/WaterFlow,API 12 或更高版本):
import { PullToRefresh, PullToRefreshType } from '@ohos/pulltorefresh';
@Component
struct NewsPage {
@State data: string[] = ['Item 1', 'Item 2', 'Item 3'];
@State refreshing: boolean = false;
build() {
PullToRefresh({
pullToRefreshType: PullToRefreshType.LIST,
refreshing: $refreshing,
customList: () => { this.ListBuilder(); },
onRefresh: () => new Promise<string>((resolve) => {
setTimeout(() => {
this.data = ['Refreshed 1', 'Refreshed 2'];
resolve('Done');
}, 1000);
}),
onLoadMore: () => new Promise<string>((resolve) => {
setTimeout(() => {
for (let i = 0; i < 10; i++) this.data.push('New ' + this.data.length);
resolve('');
}, 1000);
}),
customLoad: null,
customRefresh: null,
})
}
@Builder ListBuilder() {
List() {
ForEach(this.data, (item: string) => {
ListItem() { Text(item).padding(16) }
})
}
}
}
@ohos/lottie — JSON 动画(ohpm install @ohos/lottie,也可考虑使用 @ohos/lottie-turbo,性能提升 30% 以上):
import lottie, { AnimationItem } from '@ohos/lottie';
@Component
struct LottieDemo {
private canvasCtx = new CanvasRenderingContext2D(new RenderingContextSettings(true));
private animItem: AnimationItem | null = null;
private animName: string = 'myAnim';
aboutToDisappear(): void { lottie.destroy(); }
build() {
Canvas(this.canvasCtx).width(200).height(200)
.onReady(() => {
this.canvasCtx.imageSmoothingEnabled = true;
lottie.destroy(this.animName);
this.animItem = lottie.loadAnimation({
container: this.canvasCtx, renderer: 'canvas',
loop: true, autoplay: false, name: this.animName,
contentMode: 'Contain',
path: 'common/animation.json', // relative to ets parent folder
frameRate: 30,
});
this.animItem.addEventListener('DOMLoaded', () => {
this.animItem?.play();
});
})
}
}
// Controls: .play() .pause() .stop() .goToAndPlay(frame, true) .setSpeed(2)
混淆:在 obfuscation-rules.txt 中添加
-keep ./oh_modules/@ohos/lottie
@ohos/imageknife — 带缓存的图片加载(ohpm install @ohos/imageknife,API 18 或更高版本;后继库:imageknifepro):
import { ImageKnifeComponent, ImageKnifeAnimatorComponent } from '@ohos/imageknife';
import { ImageKnife } from '@ohos/imageknife';
// Init file cache once (in EntryAbility.onCreate)
await ImageKnife.getInstance().initFileCache(context, 256, 256 * 1024 * 1024);
// Basic usage
ImageKnifeComponent({
imageKnifeOption: {
loadSrc: 'https://example.com/image.png',
placeholderSrc: $r('app.media.loading'),
errorholderSrc: $r('app.media.error'),
objectFit: ImageFit.Auto,
border: { radius: 50 }, // circular crop
onLoadListener: {
onLoadSuccess: (data) => { console.info('Loaded'); return data; },
onLoadFailed: (err) => console.error('Failed: ' + err),
},
progressListener: (progress: number) => console.info('Progress: ' + progress),
}
}).width(100).height(100)
// GIF / animated WebP
ImageKnifeAnimatorComponent({
imageKnifeOption: { loadSrc: 'https://example.com/anim.gif' }
}).width(300).height(300)
混淆:在 obfuscation-rules.txt 中添加
-keep ./oh_modules/@ohos/imageknife
dayjs — 日期格式化(ohpm install dayjs,标准 npm API):
import dayjs from 'dayjs';
dayjs().format('YYYY-MM-DD HH:mm:ss');
dayjs('2024-01-01').fromNow(); // requires relativeTime plugin
dayjs().add(7, 'day').format('YYYY-MM-DD');
dayjs().diff(dayjs('2024-01-01'), 'day'); // days difference
调试与工具
- DevEco Studio — 主要 IDE,包含模拟器、预览器、性能分析器和 HiLog 查看器
- hdc — Harmony Device Connector(类似 adb):
hdc shell、hdc file send、hdc hilog - HiLog — 日志记录:
hilog.info(0x0001, 'TAG', 'message %{public}s', arg) - 工具:SmartPerf / DevEco Profiler — CPU/GPU/内存/能耗性能分析
- DevEco Testing — UI 自动化、性能测试、猴子/压力测试、兼容性测试
发布
- 获取 HarmonyOS 开发者账号并完成实名认证
- 在 AppGallery Connect 中生成签名证书和配置文件
- 在
build-profile.json5中配置签名 - 构建发布版 HAP/APP 包:
hvigorw assembleApp - 上传到 AppGallery Connect 进行审核
重要参考资料
- 官方文档:https://developer.huawei.com/consumer/cn/doc/
- 最佳实践:https://developer.huawei.com/consumer/cn/best-practices
- 示例:https://developer.huawei.com/consumer/cn/samples/
- Codelabs:https://developer.huawei.com/consumer/cn/codelabsPortal/serviceTypes
- ArkTS 指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts
- ArkUI 指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkui
- 状态管理:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview
- 导航:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-set-navigation-routing
- 并发:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-concurrency
- SDK / Kits:https://developer.huawei.com/consumer/cn/sdk
- OHPM 仓库:https://developer.huawei.com/consumer/cn/deveco-service
- DevEco Studio:https://developer.huawei.com/consumer/cn/deveco-studio/
- 符号图标:https://developer.huawei.com/consumer/cn/design/harmonyos-symbol
- AppGallery Connect:https://developer.huawei.com/consumer/cn/agconnect
- StateStore:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-global-state-management-state-store
- GitCode 示例:https://gitcode.com/HarmonyOS_Samples
官方示例项目(GitCode)
在 https://gitcode.com/HarmonyOS_Samples/<name> 上有 430 多个开源 HarmonyOS 示例。按类别列出的重要项目:
音频与视频
- AVCodecVideo — 通过 AVCodec 播放和录制视频(H.264/H.265、AudioVivid)
- AdaptiveVideo — 短视频沉浸式播放与旋转播放
- AudioCast — 将音频投放到外部设备
- AudioToVideoSync — 音视频同步
- DecodePlayControl — Surface 模式视频播放控制
- PlayShortVideosBasedOnVideoComponent — 短视频播放器(进度条、全屏、倍速、自动播放)
- SmoothSwitchShortVideos — 通过预加载实现短视频流畅切换
- MusicHome — 自适应音乐专辑应用(一次开发,多设备部署)
- MusicCard — 通过 Form Kit 创建音乐服务卡片
- NetAdaptiveVideoStream — 自适应码率视频流
- HMOS_LiveStream — 直播(主播端 + 观众端)
- UseAVTranscoderVideo — 视频转码
- SwipePlayer — 滑动切换视频播放器
- VideoCast — 将视频投放到外部设备
相机与图像
- CustomCamera — Camera Kit:预览、闪光灯、对焦、曝光、前后摄像头切换
- ImageGetAndSave — 图像获取与保存
- PicturePreview — 支持缩放和滑动的图像预览
- PixelMapImageEdit — 通过 PixelMap 编码和解码编辑图像
- SmartPhotoPicker — 通过 PhotoPicker 推荐照片
- MultiPictureBeautification — 多设备图像美化
- LongSnapshotPractice — 长截图实现
- MultipleImage — 使用 Swiper 实现多图轮播
- ImageToVideo — 将图像合成为视频
- CoreVisionKitOCR — 基于 OCR 的文本自动填充
UI 组件与布局
- CommonListFlows — 使用 List 组件实现常见列表场景
- ListScrollComponent — 支持懒加载的可滚动列表
- GridScrollComponent — 经过性能优化的网格滚动
- WaterFlowScrollComponent — 瀑布流布局
- SwiperPerformance — Swiper 性能优化
- ComponentEncapsulation — 组件封装模式
- CommentReply — 使用 RichEditor 实现评论回复(文本、表情、@提及)
- DialogHub — 通用弹窗库
- CustomizeKeyboard — 自定义键盘实现
- DragFramework — 支持图像、富文本和列表的拖放功能
- TextExpand — 可展开和折叠的文本
- PureTabs — 基于选项卡的导航 UI
- FoldedHover — 折叠设备悬停模式
- CardInfoRefresh — 使用 Form Kit 创建、交互和刷新服务卡片
- PageFlip — 翻页效果
- ResponsiveLayout — 适用于多种设备类型的响应式布局
- Immersive — 沉浸式全屏 UI
应用架构与导航
- AppLifecycleManagement — 应用生命周期状态管理
- AppStartUp — 启动任务初始化(AppStartup)
- StageModelContext — Stage 模型上下文用法
- HMRouter — 基于 HMRouter 的页面导航
- NavigationSettings — 使用 Navigation 构建设置应用(小窗/大窗)
- JumpBetweenApps — 通过 App Linking 实现应用间跳转
- CrossModuleReference — 原生 HAR/HSP 模块交叉引用
- CrossModuleResourceAccess — 跨模块资源访问($r、resourceManager)
- DynamicComponent — 动态组件创建
- DesktopShortcut — 通过 module.json5 添加桌面快捷方式入口
- EmebedAbility — 通过 FullScreenLaunchComponent 嵌入原子化服务
数据与存储
- KVStore — 键值数据库读写
- DatabaseReadWrite — 通过 C-API 使用关系型数据库
- DataCache — 通过数据缓存加速冷启动
- BackupRestore — 通过备份/恢复框架迁移数据
- GenerateSandboxFile — 沙箱文件生成
- NativeFileIO — 原生侧文件读写
- NativeFileAccess — 原生侧文件访问
- TurboTransJSON — 高性能 JSON 序列化(turbo_trans)
网络与通信
- NetworkReconnection — 应用网络重连
- NetBoost — 多网络并发加速
- RcpFileTransfer — 通过 Remote Communication Kit 上传/下载文件
- RemoteCommunicationPlatform — RCP 网络请求(表单、证书、DNS)
- WebCrossDomain — 通过 ArkWeb 拦截器和 Cookie 实现 Web 跨域
- MultiWeb — 响应式多 Web 布局
- OnlineEditorCollaboration — 支持跨设备协作的在线文档编辑
安全与身份验证
- UserAuth — 人脸/指纹身份验证 + 密码库自动填充
- PermissionApplication — 权限申请流程
- AntiPeep — 敏感信息防窥保护
- UniversalKeystoreCollection — HUKS 密钥管理(加密/解密、签名/验证)
- DigitalShield — 交易生物识别身份验证
- DeviceSecurityKit_sampleCode_SafetyDetectDemo_ArkTS — 设备环境和 URL 安全检测
AI 与机器学习
- MindSporeLiteArkTS — 端侧图像分类(MindSpore Lite ArkTS API)
- MindSporeLiteCpp — 端侧图像分类(MindSpore Lite C++ API)
- MSLiteHumanSegmentation — 端侧人体分割
- MSLiteSceneRecognition — 端侧场景检测
- MSLiteStyleTransform — 端侧图像风格迁移
- RAG_QA — 基于 RAG 的问答,支持端侧知识处理
多设备与可穿戴设备
- MultiDeviceCamera — 在手机、折叠屏设备和平板上使用相机,并支持预览旋转
- MultiDeviceCommunication — 一次开发,多设备适用的即时通信应用
- Phone_Connection — 手机与手表通信及心率监测
- SmartWatchCarControl — 手表控车应用
- SmartWatchMap — 手表地图应用
- SmartWatchTakeTaxi — 手表打车应用
- WearableBus — 手表公交应用
- WearableMusic — 手表音乐应用
- APILevelAdapt — 多 API 版本兼容(ArkTS + Native)
并发与性能
- UseTaskPool — 通过 TaskPool 执行多线程任务
- UseSendable — Sendable 跨线程通信与 UI 刷新
- MultiThreadIO — 使用 TaskPool + @Sendable 实现数据库和文件 I/O
- NativeSubMainThreadCommunication — Native 子线程与 UI 主线程通信
- FunctionFlowRuntimeKit-SampleCode-ConcurrentQueue — FFRT 并发队列
- FunctionFlowRuntimeKit-SampleCode-TaskGraph — FFRT 任务图依赖关系
- UtilizeHWCEfficiently — 低功耗 HWC 合成
系统与平台
- BackTaskImplement — 用于应用连续性的后台任务
- LiveViewLockScreen — 锁屏实时视图
- IntentsKitGameRevisit — 基于意图的游戏回访推荐
- ContinuePublish — 跨设备内容发布(应用接续)
- HiTraceMeterPrefTag — 使用 HiTraceMeter 进行性能跟踪
- ObtainingDeviceID — 获取设备标识符
- BluetoothLowEnergy — BLE 设备连接与通信
- NFCTag — 基于 NFC 启动应用
- QueryAppPackageInfo — 查询应用包信息
- SystemEnvVarSubscriber — 订阅系统环境变量
- DesktopExtensionKit-samplecode — 通过 Desktop Extension Kit 集成状态栏
图形与 3D
- Graphics3D — 3D 引擎 API 使用示例
- DocsSample_XComponent — XComponent 自渲染与 AI 分析
- DocsSample_Graphics — 图形子系统示例
Web 与混合开发
- H5Launch — 加速 H5 冷启动
- ExecutingJSWithJSVM — JSVM-API:创建引擎、执行 JS、销毁引擎
- DocsSample_ArkWeb — ArkWeb 组件示例
- SmallWindowScene — 小窗(悬浮窗)场景
完整目录:
https://gitcode.com/HarmonyOS_Samples— 使用git clone https://gitcode.com/HarmonyOS_Samples/<name>.git克隆任意示例
API 21 (SDK 6.0.1) — 已确认的编译错误及修复方法
这些错误已在 Mate 70 Pro 的实际构建中验证。以下所有条目在 assembleDevHqf 阶段均导致 ArkTS Compiler Error。
DataChangeListener 要求同时实现新旧方法名称
在 API 21 中,任何 implements DataChangeListener 的类都必须包含以下所有内容:
// New names (current)
onDataReloaded(): void
onDataAdd(index: number): void
onDataDelete(index: number): void
onDataChange(index: number): void
onDataMove(from: number, to: number): void
// Deprecated aliases — still required by the interface in API 21
onDataAdded(index: number): void
onDataDeleted(index: number): void
onDataChanged(index: number): void
onDataMoved(from: number, to: number): void
onDatasetChange(dataOperations: DataOperation[]): void
这适用于所有类,包括测试桩(NoopListener 等)。
notificationManager.addSlot() — 传入 SlotType,而非 NotificationSlot
// ❌ API 9 style
await notificationManager.addSlot({ type: SlotType.SOCIAL_COMMUNICATION, desc: '...' })
// ✅ API 12+ style
await notificationManager.addSlot(notificationManager.SlotType.SOCIAL_COMMUNICATION)
此外:始终显式标注常量,以防类型被缩窄为字面量类型:
// ❌ infers as SlotType.SOCIAL_COMMUNICATION — not assignable to SlotType
const SLOT_ID = notificationManager.SlotType.SOCIAL_COMMUNICATION
// ✅
const SLOT_ID: notificationManager.SlotType = notificationManager.SlotType.SOCIAL_COMMUNICATION
NotificationRequest.slotType — 不兼容的 SlotType 模块
NotificationRequest.slotType 的类型为 @ohos.notification.SlotType(旧模块),而 notificationManager.SlotType 来自 @ohos.notificationManager(新模块)。它们是名义上不同的类型——将新类型赋给旧类型会导致编译错误。直接省略 slotType(它是可选的):
const request: notificationManager.NotificationRequest = {
id: NOTIFICATION_ID,
// slotType omitted — routes to the slot created by addSlot() automatically
content: { ... }
}
API 21 中的 notificationManager 不存在 NotificationSlotLevel——请移除所有相关用法。
Permissions 类型 — 无法导入 @ohos.bundleManager
import type { Permissions } from '@ohos.bundleManager' 会报错“Cannot find module”。bundleManager 只能通过 @kit.AbilityKit 访问。解决方法:声明一个本地子集类型,其值均为有效的 Permissions 字面量:
// ✅ subtype of Permissions — assignable to the SDK's Permissions parameter
type AppPermission = 'ohos.permission.CAMERA' | 'ohos.permission.APPROXIMATELY_LOCATION'
async function hasPermission(name: AppPermission): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
const status = atManager.checkAccessTokenSync(bundleInfo.appInfo.accessTokenId, name)
return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
requestPermissionsFromUser — void & Promise 重载交叉类型
该函数同时具有回调重载(返回 void)和 Promise 重载(返回 Promise<PermissionRequestResult>)。TypeScript 有时会将它们合并成交叉类型 void & Promise<…>,导致 .authResults 不存在。此外,API 21 中的 abilityAccessCtrl.PermissionRequestResult 未从命名空间导出。
修复方法:在本地声明结果接口并进行类型断言:
interface PermResult {
authResults: abilityAccessCtrl.GrantStatus[]
}
async function requestPermission(ctx: common.UIAbilityContext, name: AppPermission): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
const result = await (atManager.requestPermissionsFromUser(ctx, [name]) as Promise<PermResult>)
return result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
getContext(this) 已弃用
在所有组件方法中替换:
// ❌
const ctx = getContext(this) as common.UIAbilityContext
// ✅
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext
模块级(非组件)函数必须将 ctx 作为参数接收。
多设备/折叠屏适配(API 21)
断点检测
import { display } from '@kit.ArkUI'
export const BP_SM = 'sm' // < 600 vp — phone portrait
export const BP_MD = 'md' // < 840 vp — phone landscape / small tablet
export const BP_LG = 'lg' // >= 840 vp — large tablet / foldable unfolded
export function getBreakpoint(): string {
try {
const d = display.getDefaultDisplaySync()
const widthVp = d.width / d.densityPixels
if (widthVp < 600) return BP_SM
if (widthVp < 840) return BP_MD
return BP_LG
} catch (e) {
return BP_SM
}
}
折叠屏 — 监听折叠/展开事件
// display.on callback receives (id: number) — NOT empty params
private displayListener: (id: number) => void = (_id: number) => {
this.breakpoint = getBreakpoint()
}
aboutToAppear(): void {
this.breakpoint = getBreakpoint()
display.on('change', this.displayListener)
}
aboutToDisappear(): void {
display.off('change', this.displayListener)
}
响应式布局切换(使用 if/else,而非 .visibility)
在手机和平板布局之间切换时,始终使用 if/else。.visibility(Visibility.None) 会隐藏组件,但仍会进行布局计算——浪费资源,还可能导致测量错误。
if (this.breakpoint === BP_SM) {
// Phone: single-column List + LazyForEach
List({ space: 8 }) { LazyForEach(...) }
} else {
// Tablet/foldable: GridRow with responsive columns
Scroll() {
GridRow({
columns: { sm: 1, md: 2, lg: 3 },
gutter: { x: 12, y: 12 },
breakpoints: { value: ['600vp', '840vp'] }
}) {
ForEach(this.gridItems, (item) => { GridCol() { MyCard({ record: item }) } })
}
}
}
GridRow/GridCol 不支持 LazyForEach
GridRow/GridCol 是响应式布局容器,而非懒加载器。请在其中使用 ForEach。若要使网格随数据变化而保持响应式,请维护一个通过 DataChangeListener 更新的 @State gridItems: T[]:
class GridRefreshListener implements DataChangeListener {
private updateFn: () => void
constructor(fn: () => void) { this.updateFn = fn }
// Must implement ALL methods — see API 21 DataChangeListener note above
onDataReloaded(): void { this.updateFn() }
onDataAdd(_i: number): void { this.updateFn() }
onDataAdded(_i: number): void { this.updateFn() }
// ... all other methods
onDataChange(_i: number): void { /* @ObjectLink handles per-item updates */ }
onDataChanged(_i: number): void { }
}
// In @Entry @Component:
@State gridItems: MyRecord[] = []
private gridListener = new GridRefreshListener(() => {
this.gridItems = [...this.dataSource.getAllData()]
})
aboutToAppear() { this.dataSource.registerDataChangeListener(this.gridListener) }
aboutToDisappear() { this.dataSource.unregisterDataChangeListener(this.gridListener) }
通过 @Provide/@Consume 共享断点
在 @Entry 根节点中声明,并在任意 NavDestination 子节点中使用:
// Root
@Provide('breakpoint') breakpoint: string = BP_SM
// Detail page (NavDestination)
@Consume('breakpoint') breakpoint: string
@Provide/@Consume 可跨 Navigation/NavDestination 使用,因为它们处于同一组件子树中。
Location Kit(geoLocationManager)
import { geoLocationManager } from '@kit.LocationKit'
// Permission required: ohos.permission.APPROXIMATELY_LOCATION (user_grant)
// module.json5: reason + usedScene required
const pos = await geoLocationManager.getCurrentLocation({
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: geoLocationManager.LocationRequestScenario.UNSET,
timeoutMs: 10000
})
const addresses = await geoLocationManager.getAddressesFromLocation({
latitude: pos.latitude,
longitude: pos.longitude,
maxItems: 1
})
// Build readable location string from GeoAddress fields:
// administrativeArea (省) → subAdministrativeArea (市) → locality (区) → subLocality → placeName
Weather Service Kit — 天气数据 API
使用设备位置时,需要
ohos.permission.LOCATION(或APPROXIMATELY_LOCATION)。
import { weatherService } from '@kit.WeatherServiceKit';
const weatherRequest: weatherService.WeatherRequest = {
location: { latitude: 39.9042, longitude: 116.4074 }, // Beijing
limitedDatasets: [
weatherService.Dataset.CURRENT, // current conditions
weatherService.Dataset.DAILY, // daily forecast
weatherService.Dataset.HOURLY, // hourly forecast
weatherService.Dataset.ALERTS, // severe weather alerts
weatherService.Dataset.INDICES, // life indices (UV, air quality...)
weatherService.Dataset.TIDES, // coastal tides
weatherService.Dataset.MINUTE, // minute-level precipitation
],
};
try {
const weather = await weatherService.getWeather(weatherRequest);
// weather.currentWeather.temperature, .humidity, .conditionCode, ...
// weather.dailyForecast?.days[] (each: tempMax/tempMin/precipitation/sunrise/sunset)
// weather.hourlyForecast?.hours[]
// weather.weatherAlerts?.alerts[] (severity, summary)
} catch (err) {
console.error('Weather fetch failed:', err);
}
Notification Kit(notificationManager)
import { notificationManager } from '@kit.NotificationKit'
// 1. Create slot (call once at app init)
await notificationManager.addSlot(notificationManager.SlotType.SOCIAL_COMMUNICATION)
// 2. Check and request notification enable
const enabled = await notificationManager.isNotificationEnabled()
if (!enabled) await notificationManager.requestEnableNotification() // deprecated but functional
// 3. Publish
const request: notificationManager.NotificationRequest = {
id: 1001,
// OMIT slotType — see API 21 type mismatch note above
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title: '标题', text: '正文', additionalText: '副文本' }
},
deliveryTime: Date.now(),
showDeliveryTime: true,
tapDismissed: true
}
await notificationManager.publish(request)
// 4. Cancel
await notificationManager.cancel(1001)
relationalStore(SQLite)
import { relationalStore } from '@kit.ArkData'
import { common } from '@kit.AbilityKit'
// Init (call in aboutToAppear / UIAbility.onCreate)
const store = await relationalStore.getRdbStore(ctx, {
name: 'my_db.db',
securityLevel: relationalStore.SecurityLevel.S1
})
await store.executeSql('CREATE TABLE IF NOT EXISTS records (...)')
// Insert
const values: relationalStore.ValuesBucket = { id: '1', title: 'Hello' }
await store.insert('records', values)
// Query (ordered)
const predicates = new relationalStore.RdbPredicates('records')
predicates.orderByDesc('timestamp')
const rs = await store.query(predicates, ['id', 'title', 'timestamp'])
while (rs.goToNextRow()) {
const id = rs.getString(rs.getColumnIndex('id'))
}
rs.close()
// Update
const predicates2 = new relationalStore.RdbPredicates('records')
predicates2.equalTo('id', '1')
await store.update({ title: 'Updated' }, predicates2)
// Delete
const predicates3 = new relationalStore.RdbPredicates('records')
predicates3.equalTo('id', '1')
await store.delete(predicates3)
preferences(键值设置)
import { preferences } from '@kit.ArkData'
const store = await preferences.getPreferences(ctx, 'user_settings')
// Read (with default)
const val = (await store.get('sort_order', 'time_desc')) as string
// Write + flush
await store.put('sort_order', 'time_asc')
await store.flush()
UiTest — 常用模式(arkxtest)
import { Driver, ON, Component } from '@ohos.UiTest'
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'
const DELEGATOR = AbilityDelegatorRegistry.getAbilityDelegator()
// Launch app before tests
beforeAll(async (done: Function) => {
await DELEGATOR.startAbility({ bundleName: 'com.example.app', abilityName: 'EntryAbility' })
await new Promise(r => setTimeout(r, 2000)) // wait for UI to render
done()
})
it('example', 0, async (done: Function) => {
const driver = Driver.create()
// Find by text / type / content description
const btn = await driver.findComponent(ON.text('发布'))
const input = await driver.findComponent(ON.type('TextInput'))
const items = await driver.findComponents(ON.type('SymbolGlyph'))
// Actions — ALL must be awaited
await btn.click()
await input.inputText('hello')
// Wait after state-changing actions
await new Promise(r => setTimeout(r, 800))
// Component refs become stale after state changes — re-find
const btnAfter = await driver.findComponent(ON.text('发布'))
// Get bounds for position-based selection
const bounds = await btn.getBounds() // { top, left, right, bottom }
// Navigate back
await driver.pressBack()
done()
})
关键规则:
it()中的每个 UiTest API 调用都必须使用await- 任何更改状态的点击/输入操作后,组件引用都可能已过期——务必重新执行
findComponent - 测试只能在真机或模拟器上运行——Previewer 不支持 UiTest
- 每个
it代码块都使用全新的Driver.create()(不要在测试之间共享) - 导航后使用
sleep/setTimeout,让新页面完成渲染后再进行断言
ArkWeb — Web 组件
通过 @kit.ArkWeb 中的 Web 组件在 ArkTS 中嵌入 Web 内容。
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct BrowserPage {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'https://example.com', controller: this.controller })
.javaScriptAccess(true)
.domStorageAccess(true)
.fileAccess(true)
.onPageBegin((event) => { console.log('loading:', event?.url); })
.onPageEnd((event) => { console.log('loaded:', event?.url); })
.onErrorReceive((event) => { console.error('web error:', event?.error.getErrorInfo()); })
.darkMode(WebDarkMode.Auto) // follow system dark mode
.forceDarkAccess(true)
}
}
}
JS ↔ ArkTS 桥接(javaScriptProxy)
// Register ArkTS object callable from JS
Web({ src: 'https://example.com', controller: this.controller })
.javaScriptProxy({
object: {
callNative: (msg: string) => {
console.log('from JS:', msg);
return 'ArkTS received: ' + msg;
}
},
name: 'NativeBridge',
methodList: ['callNative'],
controller: this.controller
})
// In the web page: window.NativeBridge.callNative('hello');
// Call JS from ArkTS
this.controller.runJavaScript('window.updateUI("data")', (err, result) => {
console.log('JS result:', result);
});
自定义 User-Agent 和 Cookie
// Append to default UA
webview.WebviewController.setCustomUserAgent(
webview.WebviewController.getDefaultUserAgent() + ' MyApp/1.0'
);
// Manage cookies
import { webCookie } from '@kit.ArkWeb';
webCookie.setCookie('https://example.com', 'token=abc; path=/');
webCookie.saveCookieAsync(); // persist to disk
拦截资源请求
Web({ src: '...', controller: this.controller })
.onInterceptRequest((event) => {
if (event?.request.getRequestUrl().includes('/api/')) {
// Return custom response
const resp = new WebResourceResponse();
resp.setResponseData('{"intercepted":true}');
resp.setResponseMimeType('application/json');
resp.setResponseEncoding('utf-8');
resp.setResponseCode(200);
resp.setReasonMessage('OK');
return resp;
}
return null; // null = load normally
})
Form Kit — ArkTS 服务卡片(服务卡片)
服务卡片运行在沙箱化的 FormExtensionAbility 进程中;它们使用 ArkUI 的一个子集。
FormExtensionAbility 生命周期
// entry/src/main/ets/formextensionability/EntryFormAbility.ets
import { FormExtensionAbility, formBindingData, FormInfo, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want) {
// Called when user adds card to home screen
const formData = { title: 'Hello', count: 0 };
return formBindingData.createFormBindingData(formData);
}
onCastToNormalForm(formId: string) { }
onUpdateForm(formId: string) {
// Periodic/requested update
const data = formBindingData.createFormBindingData({ count: Date.now() });
formProvider.updateForm(formId, data);
}
onRemoveForm(formId: string) { }
onFormEvent(formId: string, message: string) {
// Triggered by postCardAction in the card UI
console.log('card event:', formId, message);
}
}
卡片 UI(EntryFormAbility/pages/Card.ets)
// Cards are ArkUI components but with restricted APIs (no @State mutation from events)
// Use postCardAction to route events back to FormExtensionAbility
@Entry
@Component
struct CardPage {
@LocalStorageProp('title') title: string = '';
@LocalStorageProp('count') count: number = 0;
build() {
Column({ space: 8 }) {
Text(this.title).fontSize(16).fontWeight(FontWeight.Bold)
Text(`Count: ${this.count}`).fontSize(14)
Button('+1')
.onClick(() => {
postCardAction(this, {
action: 'message',
params: { event: 'increment' }
});
})
}.padding(12)
}
}
module.json5 — 声明卡片
{
"extensionAbilities": [{
"name": "EntryFormAbility",
"srcEntry": "./ets/formextensionability/EntryFormAbility.ets",
"type": "form",
"metadata": [{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}]
}]
}
resources/base/profile/form_config.json
{
"forms": [{
"name": "widget",
"displayName": "$string:widget_display_name",
"description": "$string:widget_desc",
"src": "./ets/formextensionability/pages/Card.ets",
"uiSyntax": "arkts",
"window": { "designWidth": 720, "autoDesignWidth": true },
"colorMode": "auto",
"isDynamic": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2",
"supportDimensions": ["1*2", "2*2", "2*4"]
}]
}
UIDesignKit — 图标处理与 HdsNavigation
适用于 HarmonyOS 5.0+(API 12+)。提供华为设计系统组件。
hdsDrawable — 图标自适应处理
import { hdsDrawable } from '@kit.UIDesignKit';
// Render app icon with system-consistent adaptive shape (squircle, circle, etc.)
const drawable = new hdsDrawable.HdsAdaptiveIconDrawable(
context, // UIAbilityContext or ApplicationContext
iconResource, // Resource ($r('app.media.icon'))
{ size: 48 } // options: size in vp
);
const pixelMap = await drawable.getPixelMap();
HdsNavigation — 系统风格导航组件
import { HdsNavigation, HdsNavigationItem } from '@kit.UIDesignKit';
@Entry
@Component
struct MainPage {
@State currentIndex: number = 0;
private tabs: HdsNavigationItem[] = [
{ icon: $r('app.media.home'), selectedIcon: $r('app.media.home_filled'), label: '首页' },
{ icon: $r('app.media.mine'), selectedIcon: $r('app.media.mine_filled'), label: '我的' },
];
build() {
Column() {
// Page content
Blank()
HdsNavigation({
items: this.tabs,
selectedIndex: this.currentIndex,
onItemClick: (index: number) => { this.currentIndex = index; }
})
}.height('100%')
}
}
HdsNavigation 支持:动态模糊背景、自定义内容区域(徽标、圆点指示器)、条目上的消息计数徽标。
Map Kit — MapComponent
import { mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';
@Entry
@Component
struct MapPage {
private mapOptions: mapCommon.MapOptions = {
position: {
target: { latitude: 39.9042, longitude: 116.4074 }, // Beijing
zoom: 12
}
};
private mapController?: map.MapComponentController;
build() {
Column() {
MapComponent({
mapOptions: this.mapOptions,
mapCallback: (err, controller) => {
if (!err) {
this.mapController = controller;
this.addMarker();
}
}
}).width('100%').layoutWeight(1)
}
}
private addMarker() {
this.mapController?.addMarker({
position: { latitude: 39.9042, longitude: 116.4074 },
title: 'Tiananmen',
snippet: 'Beijing city center'
});
}
}
module.json5 中的必需权限:ohos.permission.LOCATION 和 ohos.permission.APPROXIMATELY_LOCATION。
冷启动优化
HarmonyOS 将冷启动时间定义为:应用启动 → 首帧渲染完成。中端设备的目标:< 1000ms。
延迟导入(import())
// ❌ Eager — all modules parsed at startup even if unused
import { HeavyModule } from '../utils/HeavyModule';
// ✓ Lazy — parsed only when first used
let heavyModule: typeof import('../utils/HeavyModule') | null = null;
async function useHeavy() {
if (!heavyModule) {
heavyModule = await import('../utils/HeavyModule');
}
heavyModule.HeavyModule.doWork();
}
网络请求 — 延迟至首帧之后
// EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', (err) => {
if (!err) {
// First frame committed — now safe to start network
AppStartupData.prefetch();
}
});
}
其他冷启动规则
- 避免在
onCreate()/onWindowStageCreate()中执行耗时的同步操作 — 超过 10ms 的任务应使用 TaskPool - 尽量减少模块加载时的全局单例构造
- 在列表项组件上使用
@Reusable,避免首次显示时重新测量/重新布局 - 避免在紧凑的渲染循环中调用
hilog(I/O 开销) - 使用 DevEco Profiler → Launch 任务进行分析,查看精确的帧时间线
内存优化
onMemoryLevel 回调
// AbilityStage.ets or UIAbility.ets
onMemoryLevel(level: AbilityConstant.MemoryLevel): void {
if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_CRITICAL) {
// Release non-essential caches immediately
ImageCache.instance.clear();
DataCache.instance.trimToSize(10);
} else if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_LOW) {
DataCache.instance.trimToSize(50);
}
}
LRUCache — 有界图像/数据缓存
import { util } from '@kit.ArkTS';
class ImageCache {
static instance = new ImageCache();
private lru = new util.LRUCache<string, PixelMap>(50); // max 50 entries
put(key: string, pm: PixelMap) { this.lru.put(key, pm); }
get(key: string): PixelMap | undefined { return this.lru.get(key); }
clear() { this.lru.clear(); }
trimToSize(n: number) {
while (this.lru.length > n) {
this.lru.afterRemoval(false, this.lru.keys()[0], undefined, undefined);
}
}
}
可清除内存(大型位图)
import { image } from '@kit.ImageKit';
// Create PixelMap as purgeable — OS can reclaim when memory is tight,
// and will regenerate it from the source on next access
const pixelMap = await image.createPixelMap(buffer, {
size: { width: 1920, height: 1080 },
editable: false
});
// No extra API needed — PixelMap is automatically purgeable when editable=false
// and created from a decodable source (file path or buffer)
通用内存规则
- 在
aboutToDisappear()/onBackground()中取消注册监听器,以防止内存泄漏 - 在长期存活的闭包中避免捕获
this(这会使组件保持存活) - 对于重复渲染,应复用 PixelMap 对象,而不是重新创建
- 对缩略图使用
image.ImageSource+ 延迟解码,不要解码完整分辨率的图像 - TaskPool 线程不共享堆;
@Sendable对象可避免复制,但会转移所有权
Camera Kit
CameraPicker — 无需权限即可拍照/录制视频
拍摄照片或视频最简单的方式 — 启动系统相机 UI。无需相机权限。
import { camera, cameraPicker as picker } from '@kit.CameraKit';
import { fileIo, fileUri } from '@kit.CoreFileKit';
// Create a temp file to receive the capture result
const pathDir = getContext(this).filesDir;
const filePath = pathDir + `/${Date.now()}.tmp`;
fileIo.createRandomAccessFileSync(filePath, fileIo.OpenMode.CREATE);
const pickerProfile: picker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
saveUri: fileUri.getUriFromPath(filePath) // omit to save to media library
};
// Launch system camera — user takes photo/video and confirms
const result = await picker.pick(
getContext(this),
[picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO],
pickerProfile
);
if (result.resultCode === 0) {
const uri = result.resultUri; // file URI of captured media
const isPhoto = result.mediaType === picker.PickerMediaType.PHOTO;
}
完整相机会话(预览 + 拍照)
需要 ohos.permission.CAMERA。流程:CameraManager → CameraInput → Session → PreviewOutput/PhotoOutput。
import { camera } from '@kit.CameraKit';
// 1. Get camera manager and device
const cameraManager = camera.getCameraManager(context);
const cameras = cameraManager.getSupportedCameras();
const cameraDevice = cameras[0];
// 2. Create input and open
const cameraInput = cameraManager.createCameraInput(cameraDevice);
await cameraInput.open();
// 3. Get output capabilities
const capability = cameraManager.getSupportedOutputCapability(
cameraDevice, camera.SceneMode.NORMAL_PHOTO
);
// 4. Create preview output (surfaceId from XComponent)
const previewOutput = cameraManager.createPreviewOutput(
capability.previewProfiles[0], surfaceId
);
// 5. Create photo output
const photoOutput = cameraManager.createPhotoOutput(capability.photoProfiles[0]);
// 6. Build and start session
const session = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
session.beginConfig();
session.addInput(cameraInput);
session.addOutput(previewOutput);
session.addOutput(photoOutput);
await session.commitConfig();
await session.start();
// 7. Capture photo
photoOutput.on('photoAvailable', (err, photo) => {
const imageObj = photo.main;
imageObj.getComponent(image.ComponentType.JPEG, (err, component) => {
const buffer: ArrayBuffer = component.byteBuffer!;
// Save buffer to file, then release
imageObj.release();
});
});
photoOutput.capture({ quality: camera.QualityLevel.QUALITY_LEVEL_HIGH });
// 8. Cleanup: session.stop() → cameraInput.close() → previewOutput.release() → session.release()
用于相机预览的 XComponent
@Entry @Component
struct CameraPreview {
private ctrl = new XComponentController();
private surfaceId = '';
build() {
XComponent({ type: XComponentType.SURFACE, controller: this.ctrl })
.onLoad(() => {
this.surfaceId = this.ctrl.getXComponentSurfaceId();
// Use this.surfaceId to create previewOutput
})
.width('100%').height('100%')
}
}
关键权限:ohos.permission.CAMERA(拍照/录制视频)、ohos.permission.MICROPHONE(录音)。
Audio Kit — 播放与录制
AudioRenderer(ArkTS)— PCM 播放
import { audio } from '@kit.AudioKit';
const rendererOptions: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
rendererInfo: {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // determines volume type & focus
rendererFlags: 0
}
};
const renderer = await audio.createAudioRenderer(rendererOptions);
renderer.on('writeData', (buffer: ArrayBuffer) => {
// Fill buffer with PCM data
return audio.AudioDataCallbackResult.VALID;
});
await renderer.start();
// ... renderer.pause() / renderer.stop() / renderer.release()
音频焦点(InterruptEvent)
系统根据 StreamUsage 自动管理音频焦点。监听焦点变化:
renderer.on('audioInterrupt', (event: audio.InterruptEvent) => {
if (event.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
switch (event.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// System paused us — update UI to paused state
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// Permanently lost focus — stop playback
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// Volume lowered to 20% — optional UI update
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// Volume restored
break;
}
} else if (event.hintType === audio.InterruptHint.INTERRUPT_HINT_RESUME) {
// Can resume playback (SHARE type — app decides)
await renderer.start();
}
});
StreamUsage → 音量类型映射
| StreamUsage | 音量类型 | 典型用途 |
|---|---|---|
MUSIC / MOVIE / AUDIOBOOK / GAME | 媒体 | 音乐、视频、有声书、游戏背景音乐 |
VOICE_COMMUNICATION | 语音通话 | VoIP 通话 |
RINGTONE / NOTIFICATION | 铃声 | 来电、通知 |
ALARM | 闹钟 | 闹钟(即使连接 BT,也会通过扬声器播放) |
NAVIGATION | — | 导航语音(降低音乐音量,但不会暂停) |
AudioSession — 自定义焦点策略
const audioManager = audio.getAudioManager();
const sessionManager = audioManager.getSessionManager();
// Activate session with custom concurrency mode
const strategy: audio.AudioSessionStrategy = {
concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_PAUSE_OTHERS
};
await sessionManager.activateAudioSession(strategy);
// Monitor deactivation
sessionManager.on('audioSessionDeactivated', (event) => {
console.info('Session deactivated:', event.reason);
});
// When done
await sessionManager.deactivateAudioSession();
并发模式:CONCURRENCY_DEFAULT、CONCURRENCY_MIX_WITH_OTHERS、CONCURRENCY_DUCK_OTHERS、CONCURRENCY_PAUSE_OTHERS。
后台播放要求
在后台播放音频的应用必须:
- 对于媒体/游戏流(
MUSIC/MOVIE/AUDIOBOOK/GAME):集成 AVSession 并申请长时任务(AUDIO_PLAYBACK) - 对于其他可听流:只需申请
AUDIO_PLAYBACK长时任务 - 如果不满足这些要求,应用进入后台时系统将静音并冻结应用
代码混淆(ArkGuard)
在 build-profile.json5 → arkOptions.obfuscation 中启用。ArkGuard 支持对 ArkTS/TS/JS 进行名称混淆、代码压缩和注释移除。
关键混淆选项
# obfuscation-rules.txt
-enable-property-obfuscation # obfuscate property names
-enable-toplevel-obfuscation # obfuscate top-level scope names
-enable-export-obfuscation # obfuscate import/export names
-enable-filename-obfuscation # obfuscate file/folder names
-enable-string-property-obfuscation # also obfuscate string literal property names
-compact # remove whitespace and newlines
-remove-log # delete console.* statements
-print-namecache ./nameCache.json # save name mapping (keep for crash analysis!)
常见白名单场景(-keep-property-name)
以下属性必须加入白名单,以避免运行时错误:
-keep-property-name
# 1. Dynamic property access
# obj['x' + i] → keep x0, x1, x2 ...
# Object.defineProperty(obj, 'y', {}) → keep y
# 2. JSON parsing fields
# JSON.parse / JSON.stringify → keep all serialized field names
# 3. Network request fields
# http.request extraData: { username, password } → keep field names
# 4. Database fields (ValuesBucket keys)
# relationalStore column names → keep
# 5. NAPI / .so interop
# import from 'libentry.so' → keep exported function names
# 6. ArkUI @Component @State properties are auto-preserved (no action needed)
# SDK API names are auto-preserved (no action needed)
文件名白名单(-keep-file-name)
-keep-file-name
# System-loaded files that must not be renamed:
# - Worker thread files
# - Ability entry files (auto-collected since DevEco 5.0.3.500)
# - Dynamic import paths: const m = await import('./SomePath')
# - System route table paths (pageSourceFile in route_map.json, auto since API 20)
保留源代码中的特定名称(@KeepSymbol)
// Since API 19: use comments to mark names as non-obfuscatable
// @KeepSymbol
class ImportantClass {
// @KeepSymbol
criticalProp: string = '';
}
注意事项
- ArkGuard 使用全局属性白名单——如果保留了
name,所有文件中名为name的属性都会被保留 - 使用属性混淆构建 HAR 时,
enum成员会自动加入白名单 - 每次发布都要保存
nameCache.json——解码混淆后的崩溃堆栈时需要用到 -compact会移除所有换行符 → 崩溃堆栈中的行号将失去作用(发布构建不会提供列信息)- 与 SDK API 常量匹配的字符串属性(例如,
'ohos.want.action.home')不会自动加入白名单——如果将其用作属性键,请手动添加
Scan Kit — 条码扫描与生成
默认 UI 扫描(无需摄像头权限)
启动系统扫描 UI。摄像头已预先授权——无需请求权限。
import { scanCore, scanBarcode } from '@kit.ScanKit';
const options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true,
enableAlbum: true
};
try {
const result = await scanBarcode.startScanForResult(
getContext(this), // or this.getUIContext().getHostContext()
options
);
// result.originalValue — decoded string
// result.scanType — code type (QR, EAN-13, etc.)
console.info('Scan result:', result.originalValue);
} catch (err) {
console.error('Scan failed:', err.code, err.message);
}
支持的码制:QR Code、Data Matrix、PDF417、Aztec、EAN-8、EAN-13、UPC-A、UPC-E、Codabar、Code 39/93/128、ITF-14。
图片解码(检测照片中的条码)
import { scanCore, scanBarcode, detectBarcode } from '@kit.ScanKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
// Pick an image from gallery
const picker = new photoAccessHelper.PhotoViewPicker();
const pickerResult = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
});
// Decode barcode from selected image
const inputImage: detectBarcode.InputImage = { uri: pickerResult.photoUris[0] };
const results = await detectBarcode.decode(inputImage, {
scanTypes: [scanCore.ScanType.ALL],
enableMultiMode: true
});
// results is Array<scanBarcode.ScanResult>
自定义 UI 扫描(需要 ohos.permission.CAMERA)
使用 @kit.ScanKit 中的 customScan,全面控制扫描 UI:
import { scanCore, scanBarcode, customScan } from '@kit.ScanKit';
// 1. Init
customScan.init({ scanTypes: [scanCore.ScanType.ALL], enableMultiMode: true });
// 2. Start with XComponent surfaceId
const viewControl: customScan.ViewControl = { width, height, surfaceId };
const results = await customScan.start(viewControl);
// 3. Control: customScan.openFlashLight() / closeFlashLight()
// customScan.setZoom(2.0) / getZoom()
// customScan.setFocusPoint({x, y}) / resetFocus()
// customScan.stop() / rescan()
// 4. Release when done
await customScan.release();
条码生成
import { scanCore, generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';
const pixelMap: image.PixelMap = await generateBarcode.createBarcode('https://example.com', {
scanType: scanCore.ScanType.QR_CODE,
height: 400,
width: 400
});
// Use pixelMap directly in Image component: Image(this.pixelMap)
支持生成:QR Code、EAN-8、EAN-13、UPC-A、UPC-E、Codabar、Code 39/93/128、ITF-14、Data Matrix、PDF417、Aztec。
Account Kit — Huawei ID 登录
在 module.json5 中配置客户端 ID
{
"module": {
"name": "entry",
"type": "entry",
"metadata": [{
"name": "client_id",
"value": "YOUR_CLIENT_ID" // from AGC console
}]
}
}
一键登录(企业开发者、非游戏应用)
一次点击即可获取电话号码和 UnionID。需要手动签名并获得 AGC 权限审批。
import { authentication } from '@kit.AccountKit';
import { util } from '@kit.ArkTS';
// Step 1: Get anonymous phone number (for display on login page)
const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
authRequest.scopes = ['quickLoginAnonymousPhone'];
authRequest.state = util.generateRandomUUID();
authRequest.forceAuthorization = false; // must be false for one-click login
const controller = new authentication.AuthenticationController();
const response = await controller.executeRequest(authRequest);
const anonymousPhone = response.data?.extraInfo?.quickLoginAnonymousPhone as string;
// Display anonymousPhone on login page, e.g. "188****1234"
// Step 2: Show LoginWithHuaweiIDButton — user taps to get Authorization Code
import { loginComponentManager, LoginWithHuaweiIDButton } from '@kit.AccountKit';
LoginWithHuaweiIDButton({
params: {
style: loginComponentManager.Style.BUTTON_RED,
borderRadius: 24,
loginType: loginComponentManager.LoginType.QUICK_LOGIN, // one-click login
supportDarkMode: true
},
controller: this.controller // LoginWithHuaweiIDButtonController
})
// In controller callback: response.authorizationCode → send to server
// Server calls /oauth2/v6/quickLogin/getPhoneNumber to get full phone + UnionID
混淆白名单——如果启用了属性混淆,请添加到 obfuscation-rules.txt:
-keep-property-name
quickLoginAnonymousPhone
Huawei Account 登录(适用于所有开发者)
通过 LoginWithHuaweiIDButton 或 API 获取 UnionID/OpenID。支持企业开发者和个人开发者。
// Using LoginWithHuaweiIDButton component (recommended)
LoginWithHuaweiIDButton({
params: {
style: loginComponentManager.Style.BUTTON_RED,
borderRadius: 24,
loginType: loginComponentManager.LoginType.ID, // standard login
supportDarkMode: true
},
controller: this.controller
})
// callback returns authorizationCode → server exchanges for UnionID/OpenID
// Using custom button with API
import { authentication } from '@kit.AccountKit';
const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
loginRequest.forceLogin = true; // true = show login page if not logged in
loginRequest.state = util.generateRandomUUID();
const controller = new authentication.AuthenticationController(getContext(this));
const response = await controller.executeRequest(loginRequest);
const authCode = response.data?.authorizationCode;
// Send authCode to server → server gets UnionID/OpenID via Access Token
静默登录
无需用户交互——为回访用户获取 UnionID(重新安装、切换设备)。
const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
loginRequest.forceLogin = false; // false = silent, no UI if not logged in
loginRequest.state = util.generateRandomUUID();
const controller = new authentication.AuthenticationController();
const response = await controller.executeRequest(loginRequest);
const authCode = response.data?.authorizationCode;
// If error.code === 1001502001 → user not logged in, show other login methods
常见错误码
| 错误码 | 含义 | 操作 |
|---|---|---|
1001502001 | 未登录 Huawei Account | 显示其他登录方式 |
1001502005 | 网络错误 | 重试或显示其他方式 |
1001502012 | 用户已取消 | 无需操作 |
1001500001 | 证书指纹校验失败 | 检查签名配置 |
1001502014 | 缺少作用域/权限 | 检查 AGC 权限审批 |
1005300001 | 用户不同意协议 | 显示协议对话框 |
关键概念
| ID 类型 | 范围 | 用途 |
|---|---|---|
| OpenID | 每个应用唯一 | 在单个应用内识别用户 |
| UnionID | 每个开发者唯一 | 在同一开发者的多个应用间识别用户 |
| GroupUnionID | 每个账户组唯一 | 在关联开发者之间识别用户 |
为确保跨平台用户数据连续性,优先使用 UnionID 而非 OpenID。
App continuation(应用接续)— 跨设备迁移
启用跨设备任务接续:将 UIAbility 状态从设备 A 迁移到设备 B。
在 module.json5 中启用接续
{
"module": {
"abilities": [{
"name": "EntryAbility",
"continuable": true // enable cross-device migration
}]
}
}
源设备 — 保存迁移数据
// In UIAbility
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
// Save data to migrate (keep under 100KB, use distributed data object for larger data)
wantParam['currentPage'] = 'DetailPage';
wantParam['articleId'] = this.articleId;
// Check target app version compatibility
const targetVersion = wantParam['version'] as number;
if (targetVersion < 2) {
return AbilityConstant.OnContinueResult.MISMATCH;
}
return AbilityConstant.OnContinueResult.AGREE;
}
目标设备 — 恢复数据
// Cold start
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// Restore migrated data
this.articleId = want.parameters?.['articleId'] as number;
// Restore page stack
this.context.restoreWindowStage(this.storage);
}
}
// Hot start (single-instance)
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
this.articleId = want.parameters?.['articleId'] as number;
this.context.restoreWindowStage(this.storage);
}
}
动态迁移控制
// Disable migration on certain pages
this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE);
// Re-enable when on a migrateable page
this.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE);
不同 Ability 名称间的跨设备迁移
使用 module.json5 中的 continueType,关联不同设备上的不同 Ability:
// Device A
{ "name": "PhoneAbility", "continueType": ["myApp_main"] }
// Device B
{ "name": "TabletAbility", "continueType": ["myApp_main"] }
前提条件
- 两台设备均已登录同一个 Huawei Account
- 已启用 Wi-Fi 和 Bluetooth(或已启用“多设备协同增强”)
- 已启用“设置 → 多设备协同 → 接续”
- 两台设备上均已安装应用
Push Kit — 推送通知
获取 Push Token
在 UIAbility 的 onCreate() 中调用。Token 用于识别推送消息对应的设备和应用。
import { pushService } from '@kit.PushKit';
// In EntryAbility.onCreate()
pushService.getToken().then((token: string) => {
console.info('Push token:', token);
// Upload token to your app server for sending push messages
}).catch((err: BusinessError) => {
console.error('Failed to get push token:', err.code, err.message);
});
前提条件:请先在 AGC 控制台中启用 Push Service,否则 getToken() 会返回错误 1000900010。
Token 会在以下情况下发生变化:重新安装应用、恢复出厂设置、调用 deleteToken() 后再次调用 getToken()。请务必在每次启动应用时调用 getToken(),以确保服务端 Token 保持最新。
接收推送消息(module.json5)
配置带有 action.ohos.push.listener 的 UIAbility,以接收 Token 更新和推送数据:
"skills": [{ "actions": ["action.ohos.push.listener"] }]
Push Kit 支持:通知消息、语音播报、卡片刷新、后台消息、实况视图、应用内通话消息。
AVSession Kit — 媒体播放控制(后台音频播放必需)
重要:所有在后台播放音频/视频的应用都必须创建 AVSession。否则,当应用进入后台时,系统会强制暂停音频。
创建并激活会话
import { avSession as AVSessionManager } from '@kit.AVSessionKit';
// Create session — type: 'audio' | 'video' | 'voice_call'
const session = await AVSessionManager.createAVSession(context, 'MyPlayer', 'audio');
// Set metadata (required — without it, playback controls won't appear)
await session.setAVMetadata({
assetId: 'song_001',
title: 'Song Title',
artist: 'Artist Name',
mediaImage: 'https://example.com/cover.jpg',
duration: 240000 // ms
});
// Set playback state
await session.setAVPlaybackState({
state: AVSessionManager.PlaybackState.PLAYBACK_STATE_PLAY,
position: { elapsedTime: 0, updateTime: Date.now() },
speed: 1.0
});
// Register control commands BEFORE activating
session.on('play', () => { /* resume playback */ });
session.on('pause', () => { /* pause playback */ });
session.on('playNext', () => { /* next track */ });
session.on('playPrevious', () => { /* previous track */ });
session.on('seek', (position: number) => { /* seek to position ms */ });
// Activate — must be called AFTER metadata + commands are set
await session.activate();
后台播放要求
对于媒体流(MUSIC/MOVIE/AUDIOBOOK/GAME):
- 创建 AVSession(如上所述)
- 通过 Background Tasks Kit 申请
AUDIO_PLAYBACK长时运行任务 - 两者都不可或缺——缺少任何一项都会导致后台音频静音
不支持的命令
使用 session.off() 注销应用不支持的命令。系统播放中心会将对应按钮显示为灰色。
session.off('playPrevious'); // no "previous" button
session.off('toggleFavorite'); // no favorite button
资源访问 — $r() 和 $rawfile()
资源目录结构
resources/
├─ base/ # default resources (always matched)
│ ├─ element/ # string.json, color.json, float.json, etc.
│ ├─ media/ # images, audio, video
│ └─ profile/ # custom JSON config files
├─ zh_CN/element/ # Chinese locale override
├─ en_US/element/ # English locale override
├─ dark/element/ # dark mode override
├─ rawfile/ # raw files (not compiled, accessed by path)
└─ resfile/ # installed to sandbox, read-only access
限定符顺序:MCC_MNC → language_script_region → orientation → device → colorMode → density。
访问资源
// App resources: $r('app.type.name')
Text($r('app.string.hello_world'))
.fontSize($r('app.float.text_size_body'))
.fontColor($r('app.color.primary'))
Image($r('app.media.app_icon'))
// With format args: $r('app.string.greeting', 'Alice', 5)
// For string "Hello, %1$s! You have %2$d messages."
Text($r('app.string.greeting', 'Alice', 5))
// Plural: $r('app.plural.item_count', count, count)
Text($r('app.plural.item_count', 2, 2)) // "2 items"
// Raw files: $rawfile('path/relative/to/rawfile/')
Image($rawfile('images/banner.png'))
// System resources: $r('sys.type.name')
Text('Hello')
.fontColor($r('sys.color.ohos_id_color_emphasize'))
.fontSize($r('sys.float.ohos_id_text_size_headline1'))
// Cross-HSP module resources: $r('[moduleName].type.name')
Text($r('[library].string.shared_text'))
通过 ResourceManager 以编程方式访问
const resMgr = getContext(this).resourceManager;
const str = resMgr.getStringByNameSync('hello_world');
const rawFd = resMgr.getRawFd('data.json'); // returns {fd, offset, length}
应用文件路径(Context 属性)
| Context 属性 | 路径 | 用途 |
|---|---|---|
filesDir | base/files/ | 持久化应用数据(应用更新后仍保留) |
cacheDir | base/cache/ | 缓存(空间不足时系统可能自动清理) |
tempDir | base/temp/ | 临时文件(应用退出时清理) |
databaseDir | database/ | 数据库文件(relationalStore 等) |
preferencesDir | base/preferences/ | 首选项 KV 存储 |
bundleCodeDir | bundle/ | 已安装的 HAP 资源(只读) |
distributedFilesDir | distributedfiles/ | 跨设备共享文件 |
Payment Kit — Huawei Pay(实物商品和服务)
仅适用于实物商品/服务(酒店、出行、账单)。虚拟商品请改用 IAP Kit。
import { paymentService } from '@kit.PaymentKit';
import { common } from '@kit.AbilityKit';
// orderStr is built by your server after calling Huawei Pay pre-order API
// Contains: app_id, merc_no, prepay_id, timestamp, noncestr, sign
const orderStr = '{"app_id":"...","merc_no":"...","prepay_id":"...","timestamp":"...","noncestr":"...","sign":"..."}';
const context = getContext(this) as common.UIAbilityContext;
paymentService.requestPayment(context, orderStr)
.then(() => { console.info('Payment succeeded'); })
.catch((err: BusinessError) => { console.error('Payment failed:', err.code, err.message); });
服务端流程:你的服务端调用 /api/v2/aggr/preorder/create/app → 获取 prepayId → 构建签名后的 orderStr → 返回给客户端。
支持:手机、平板、PC/2in1。仅限中国大陆。
Core Vision Kit — OCR、人脸检测、主体分割
来自 @kit.CoreVisionKit 的端侧 AI 功能。仅限中国大陆,不支持模拟器。
文本识别(OCR)
import { textRecognition } from '@kit.CoreVisionKit';
// Initialize once (e.g., in aboutToAppear)
await textRecognition.init();
// Recognize text from PixelMap
const visionInfo: textRecognition.VisionInfo = { pixelMap: myPixelMap };
const result = await textRecognition.recognizeText(visionInfo, {
isDirectionDetectionSupported: false
});
console.info('OCR result:', result.value); // full recognized text string
// Release when done (e.g., in aboutToDisappear)
await textRecognition.release();
支持:中文(简体/繁体)、英语、日语、韩语。输入:JPEG/PNG,建议使用 720p 或更高分辨率。
人脸检测
import { faceDetector } from '@kit.CoreVisionKit';
await faceDetector.init();
const faces: faceDetector.Face[] = await faceDetector.detect({ pixelMap: myPixelMap });
// Each face: faceRectangle, landmark positions, euler angles, confidence
await faceDetector.release();
主体分割
import { subjectSegmentation } from '@kit.CoreVisionKit';
await subjectSegmentation.init();
const result = await subjectSegmentation.doSegmentation(
{ pixelMap: myPixelMap },
{ maxCount: 5, enableSubjectDetails: true, enableSubjectForegroundImage: true }
);
// result.fullSubject.foregroundImage — PixelMap with background removed
// result.subjectCount, result.subjectDetails[i].subjectRectangle
await subjectSegmentation.release();
fileIo — 应用文件读写
通过 @kit.CoreFileKit 执行核心文件操作。所有路径都应来自 Context 属性(filesDir、cacheDir 等)。
import { fileIo as fs } from '@kit.CoreFileKit';
const context = getContext(this);
// Write a file
const filePath = context.filesDir + '/data.json';
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
fs.writeSync(file.fd, JSON.stringify({ key: 'value' }));
fs.closeSync(file);
// Read a file
const readFile = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(4096);
const readLen = fs.readSync(readFile.fd, buf);
const content = String.fromCharCode(...new Uint8Array(buf.slice(0, readLen)));
fs.closeSync(readFile);
// Check existence
const exists = fs.accessSync(filePath);
// List directory
const entries = fs.listFileSync(context.filesDir);
// Copy file
fs.copyFileSync(filePath, context.cacheDir + '/data_backup.json');
// Delete file
fs.unlinkSync(filePath);
// Stat file (size, mtime)
const stat = fs.statSync(filePath);
console.info(`size: ${stat.size}, mtime: ${stat.mtime}`);
从 Picker URI 读取文件
// After picker returns a URI (temporary read-only permission)
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(4096);
const len = fs.readSync(file.fd, buf);
fs.closeSync(file);
AVPlayer — 统一的音视频播放
@kit.MediaKit 中的 AVPlayer 可处理 mp4/mp3/mkv/mpeg-ts 等格式——只需提供源,无需手动解码。
import { media } from '@kit.MediaKit';
// Create player
const avPlayer = await media.createAVPlayer();
// Set callbacks
avPlayer.on('stateChange', (state: string) => {
switch (state) {
case 'initialized': // source set, prepare now
avPlayer.prepare();
break;
case 'prepared': // ready to play
avPlayer.play();
break;
case 'completed': // playback finished
avPlayer.release();
break;
}
});
avPlayer.on('error', (err) => {
console.error('AVPlayer error:', err.message);
avPlayer.release();
});
// Set source — local file (fd)
const file = fs.openSync(context.filesDir + '/video.mp4', fs.OpenMode.READ_ONLY);
avPlayer.fdSrc = { fd: file.fd, offset: 0, length: fs.statSync(file.fd).size };
// Or network URL
avPlayer.url = 'https://example.com/audio.mp3';
使用视频 surface(XComponent)的 AVPlayer
avPlayer.on('stateChange', (state: string) => {
if (state === 'initialized') {
avPlayer.surfaceId = xComponentSurfaceId; // from XComponent.onLoad
avPlayer.prepare();
} else if (state === 'prepared') {
avPlayer.play();
}
});
avPlayer.url = 'https://example.com/video.mp4';
AVPlayer 控制
avPlayer.pause();
avPlayer.play();
avPlayer.seek(30000); // seek to 30s (ms)
avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X);
avPlayer.setVolume(0.5); // 0.0 ~ 1.0
avPlayer.stop(); // stop → can prepare() again
avPlayer.release(); // release all resources
AVRecorder — 音视频录制
import { media } from '@kit.MediaKit';
const recorder = await media.createAVRecorder();
const config: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile: {
audioBitrate: 48000,
audioChannels: 2,
audioCodec: media.CodecMimeType.AUDIO_AAC,
audioSampleRate: 48000,
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
videoBitrate: 2000000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 1920,
videoFrameHeight: 1080,
videoFrameRate: 30
},
url: `fd://${file.fd}`, // file descriptor for output
rotation: 0
};
await recorder.prepare(config);
// For video: const surfaceId = await recorder.getInputSurface();
await recorder.start();
// ... recording ...
await recorder.stop();
await recorder.release();
后台播放时:必须创建 AVSession,并申请 AUDIO_PLAYBACK 长时运行任务(参见 AVSession Kit 部分)。
Image Kit — 解码、转换、编码
将图像解码为 PixelMap
import { image } from '@kit.ImageKit';
import { fileIo as fs } from '@kit.CoreFileKit';
// From file path
const imageSource = image.createImageSource(context.filesDir + '/photo.jpg');
const pixelMap = await imageSource.createPixelMap({
editable: true,
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
});
// From file descriptor
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const imageSource2 = image.createImageSource(file.fd);
// From ArrayBuffer (e.g., from network response)
const imageSource3 = image.createImageSource(arrayBuffer);
// From rawfile
const rawFd = context.resourceManager.getRawFd('image.png');
const imageSource4 = image.createImageSource(rawFd);
// Get image info
const info = await pixelMap.getImageInfo();
console.info(`${info.size.width} x ${info.size.height}`);
PixelMap 转换
pixelMap.crop({ x: 0, y: 0, size: { width: 400, height: 400 } }); // crop
pixelMap.scale(0.5, 0.5); // scale to 50%
pixelMap.rotate(90); // rotate 90° clockwise
pixelMap.flip(true, false); // horizontal flip
pixelMap.flip(false, true); // vertical flip
pixelMap.opacity(0.5); // set 50% opacity
pixelMap.translate(100, 100); // offset by 100px
将 PixelMap 编码为文件
import { image } from '@kit.ImageKit';
const packer = image.createImagePacker();
const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 90 };
const data: ArrayBuffer = await packer.packing(pixelMap, packOpts);
// Write data to file via fs.writeSync
packer.release();
释放资源
pixelMap.release();
imageSource.release();
App Linking — 深度链接和应用间导航
在 module.json5 中配置深度链接
{
"module": {
"abilities": [{
"name": "EntryAbility",
"skills": [{
"entities": ["entity.system.home", "entity.system.browsable"],
"actions": ["ohos.want.action.home", "ohos.want.action.viewData"],
"uris": [{ "scheme": "https", "host": "example.com", "path": "/detail" }],
"domainVerify": true // enable App Linking verification
}]
}]
}
}
处理传入链接 — 冷启动(onCreate)
import { url } from '@kit.ArkTS';
export default class EntryAbility extends UIAbility {
private targetPage: string = '';
private linkParams: Record<string, string> = {};
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.parseUri(want);
}
private parseUri(want: Want): void {
if (want?.uri) {
const urlObj = url.URL.parseURL(want.uri);
this.linkParams = Object.fromEntries(urlObj.params.entries());
this.targetPage = urlObj.pathname; // e.g. "/detail"
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
const page = this.targetPage === '/detail' ? 'pages/Detail' : 'pages/Index';
if (this.linkParams['id']) {
AppStorage.setOrCreate('linkId', this.linkParams['id']);
}
windowStage.loadContent(page);
}
}
处理链接 — 应用已在运行(onNewWant)
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.parseUri(want);
if (this.linkParams['id']) {
AppStorage.setOrCreate('linkId', this.linkParams['id']);
AppStorage.setOrCreate('newWantFlag', true); // notify UI to navigate
}
}
页面通过 @StorageLink + @Watch 监听 newWantFlag,并据此导航。
Share Kit — 跨应用内容分享
import { systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor } from '@kit.ArkData';
// Share a hyperlink
const shareData = new systemShare.SharedData({
utd: uniformTypeDescriptor.UniformDataType.HYPERLINK,
content: 'https://example.com/article/123',
title: 'Article Title',
description: 'Article preview text',
});
const controller = new systemShare.ShareController(shareData);
controller.show(this.context, {
previewMode: systemShare.SharePreviewMode.DEFAULT,
selectionMode: systemShare.SelectionMode.SINGLE,
});
支持的 UTD 类型:HYPERLINK、PLAIN_TEXT、HTML、IMAGE(传入文件的 uri)、FILE。
自定义对话框 — openCustomDialog 与 openBindSheet
openCustomDialog(通用模态/非模态对话框)
import { ComponentContent } from '@kit.ArkUI';
// 1. Define dialog content via @Builder
@Builder
function dialogContentBuilder(params: { message: string; close: () => void }) {
Column({ space: 16 }) {
Text(params.message).fontSize(16)
Button('OK').onClick(() => params.close())
}
.padding(24)
.backgroundColor(Color.White)
.borderRadius(16)
}
// 2. Open dialog
const uiContext = this.getUIContext();
const contentNode = new ComponentContent(uiContext, wrapBuilder(dialogContentBuilder), {
message: 'Hello',
close: () => uiContext.getPromptAction().closeCustomDialog(contentNode),
});
uiContext.getPromptAction().openCustomDialog(contentNode, {
alignment: DialogAlignment.Center,
isModal: true,
autoCancel: true, // tap outside to close
});
openBindSheet(底部半模态弹层)
const uiContext = this.getUIContext();
const sheetNode = new ComponentContent(uiContext, wrapBuilder(sheetBuilder), params);
uiContext.openBindSheet(sheetNode, {
title: { title: 'Select Option' },
height: SheetSize.MEDIUM,
preferType: SheetType.BOTTOM,
detents: [SheetSize.MEDIUM, SheetSize.LARGE, 200], // draggable heights
backgroundColor: '#F1F3F5',
}, targetComponentId);
bindContentCover(全屏模态覆盖层)
@State isPresented: boolean = false;
@Builder
fullScreenContent() {
Column() {
Text('Full Screen Modal').fontSize(20)
Button('Close').onClick(() => { this.isPresented = false; })
}.width('100%').height('100%').backgroundColor(Color.White)
}
// Trigger:
Button('Show').onClick(() => { this.isPresented = true; })
.bindContentCover($$this.isPresented, this.fullScreenContent(), {
modalTransition: ModalTransition.DEFAULT, // .NONE, .ALPHA
})
注意:请勿使用已弃用的
CustomDialog或@ohos.promptAction,请改用UIContext.getPromptAction().openCustomDialog()、UIContext.openBindSheet()和.bindContentCover()。
软键盘布局适配(软键盘适配)
设置键盘避让模式(在 UIAbility 中)
import { KeyboardAvoidMode } from '@kit.ArkUI';
// OFFSET = page lifts up (default); RESIZE = page compresses; NONE = keyboard overlaps
windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
防止组件随键盘移动
Row() { /* title bar — should stay fixed */ }
.expandSafeArea([SafeAreaType.KEYBOARD])
.zIndex(1)
监听键盘高度
import { window } from '@kit.ArkUI';
window.getLastWindow(this.getUIContext().getHostContext()).then(win => {
win.on('keyboardHeightChange', (height: number) => {
this.keyboardHeight = this.getUIContext().px2vp(height);
});
});
焦点控制
TextInput().defaultFocus(true) // auto-focus on page load
this.getUIContext().getFocusController().requestFocus('inputId'); // programmatic focus
this.getUIContext().getFocusController().clearFocus(); // dismiss keyboard
深色模式适配(深色模式适配)
资源限定符方案(推荐)
将浅色模式的颜色/图片放在 resources/base/ 中,将深色模式的变体(使用相同文件名)放在 resources/dark/ 中:
resources/base/element/color.json → { "color": [{ "name": "bg_color", "value": "#FFFFFF" }] }
resources/dark/element/color.json → { "color": [{ "name": "bg_color", "value": "#1A1A1A" }] }
resources/base/media/icon.png → light icon
resources/dark/media/icon.png → dark icon
用法:$r('app.color.bg_color') / $r('app.media.icon') — 会随系统主题自动切换。
检测并响应颜色模式变化
// In EntryAbility — store current mode
onCreate(): void {
AppStorage.setOrCreate('currentColorMode', this.context.config.colorMode);
}
onConfigurationUpdate(newConfig: Configuration): void {
AppStorage.setOrCreate('currentColorMode', newConfig.colorMode);
}
// In component — watch for changes
@StorageProp('currentColorMode') @Watch('onColorModeChange')
currentColorMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET;
onColorModeChange(): void {
const isDark = this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
// update status bar, custom logic, etc.
}
以编程方式切换模式
this.getUIContext().getHostContext()?.getApplicationContext()
.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK); // or COLOR_MODE_LIGHT / COLOR_MODE_NOT_SET
后台上传与下载(request.agent)
import { request } from '@kit.BasicServicesKit';
后台上传(支持暂停/恢复)
const config: request.agent.Config = {
action: request.agent.Action.UPLOAD,
url: 'https://example.com/upload',
mode: request.agent.Mode.BACKGROUND,
method: 'POST',
data: formItems, // Array of FormItem
};
const task = await request.agent.create(context, config);
task.on('progress', (progress) => { /* track */ });
task.on('completed', (progress) => { /* done */ });
await task.start();
await task.pause(); // pause
await task.resume(); // resume with breakpoint
后台下载(自动断点续传)
const config: request.agent.Config = {
action: request.agent.Action.DOWNLOAD,
url: 'https://example.com/file.zip',
mode: request.agent.Mode.BACKGROUND,
saveas: `./downloads/file.zip`,
overwrite: true,
gauge: true,
};
const task = await request.agent.create(context, config);
task.on('progress', (progress) => { /* track */ });
await task.start();
桌面快捷方式(桌面快捷方式)
1. 创建 resources/base/profile/shortcuts_config.json
{
"shortcuts": [
{
"shortcutId": "id_scan",
"label": "$string:shortcut_scan",
"icon": "$media:ic_scan",
"wants": [{
"bundleName": "com.example.myapp",
"moduleName": "entry",
"abilityName": "EntryAbility",
"parameters": { "shortcutKey": "ScanPage" }
}]
}
]
}
2. 在 module.json5 中注册
"abilities": [{
"name": "EntryAbility",
"metadata": [{
"name": "ohos.ability.shortcuts",
"resource": "$profile:shortcuts_config"
}]
}]
3. 根据快捷方式参数进行路由
// In EntryAbility — onCreate / onNewWant
const shortcutKey = want?.parameters?.shortcutKey as string;
if (shortcutKey === 'ScanPage') {
AppStorage.setOrCreate('targetPage', 'pages/Scan');
}
页面读取 @StorageProp('targetPage') 并据此导航。
跨模块资源访问(跨模块资源访问)
访问 HAR 资源(与本地资源相同)
Text($r('app.string.string_in_har'))
Image($r('app.media.image_in_har'))
// Better performance with .id for resourceManager:
this.context.resourceManager.getStringSync($r('app.string.string_in_har').id);
访问 HSP 资源(添加模块名前缀)
Text($r('[hsp1].string.string_in_hsp'))
Image($r('[hsp1].media.image_in_hsp'))
也可以通过 createModuleContext 访问:
import { common } from '@kit.AbilityKit';
const hspContext = await common.application.createModuleContext(this.context, 'hsp1');
const str = hspContext.resourceManager.getStringByNameSync('string_in_hsp');
自定义字体(自定义字体)
// 1. Register font (in EntryAbility onWindowStageCreate or component aboutToAppear)
const uiContext = windowStage.getMainWindowSync().getUIContext();
uiContext.getFont().registerFont({
familyName: 'MyCustomFont',
familySrc: $rawfile('MyCustomFont.ttf'),
});
// 2. Use in component
Text('Hello').fontFamily('MyCustomFont')
字体大小是否跟随系统设置 — 在 profile/configuration.json 中配置:
{ "configuration": { "fontSizeScale": "followSystem", "fontSizeMaxScale": "2" } }
在 app.json5 中引用:"configuration": "$profile:configuration"。
屏幕方向(横竖屏切换)
import { window } from '@kit.ArkUI';
// Get window instance
const win = await window.getLastWindow(this.context);
// Set orientation
win.setPreferredOrientation(window.Orientation.USER_ROTATION_LANDSCAPE); // enter landscape
win.setPreferredOrientation(window.Orientation.USER_ROTATION_PORTRAIT); // back to portrait
win.setPreferredOrientation(window.Orientation.AUTO_ROTATION); // follow sensor
// Monitor window size for layout adaptation
win.on('windowSizeChange', (size) => {
const orientation = display.getDefaultDisplaySync().orientation;
this.isLandscape = (orientation === display.Orientation.LANDSCAPE ||
orientation === display.Orientation.LANDSCAPE_INVERTED);
});
也可以在 module.json5 中配置:"abilities": [{ "orientation": "portrait" }]。
选项:portrait、landscape、auto_rotation、auto_rotation_landscape、follow_desktop。
剪贴板 — 读写粘贴板
import { pasteboard } from '@kit.BasicServicesKit';
// Write text to clipboard
const pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, 'Hello World');
const board = pasteboard.getSystemPasteboard();
await board.setData(pasteData);
// Read from clipboard
const data = await board.getData();
if (data.hasType(pasteboard.MIMETYPE_TEXT_PLAIN)) {
const text = data.getPrimaryText();
}
支持的 MIME 类型:MIMETYPE_TEXT_PLAIN、MIMETYPE_TEXT_HTML、MIMETYPE_TEXT_URI、MIMETYPE_PIXELMAP。
手势冲突处理(手势冲突处理)
hitTestBehavior — 控制触摸事件响应
Stack() {
BottomComponent().hitTestBehavior(HitTestMode.None) // skip self, pass to sibling
TopComponent().hitTestBehavior(HitTestMode.Transparent) // self responds AND passes to sibling
}
| 模式 | 行为 |
|---|---|
Default | 自身响应,并阻止同级组件 |
Transparent | 自身响应,但不会阻止同级组件 |
None | 跳过自身,将事件传递给同级组件 |
Block | 仅自身响应,并停止所有事件传播 |
手势绑定优先级
// Parent takes priority over child for same gesture type
ParentComponent()
.priorityGesture(TapGesture().onAction(() => { /* parent handles */ }))
// Both parent and child respond simultaneously
ParentComponent()
.parallelGesture(PanGesture().onAction(() => { /* parent also handles */ }))
// Block child gestures entirely
ParentComponent()
.gesture(TapGesture(), GestureMask.IgnoreInternal)
GestureGroup 模式
// Sequential: all must succeed in order
GestureGroup(GestureMode.Sequence, LongPressGesture(), PanGesture())
// Parallel: all run simultaneously
GestureGroup(GestureMode.Parallel, PinchGesture(), RotationGesture())
// Exclusive: first to succeed wins
GestureGroup(GestureMode.Exclusive, TapGesture(), SwipeGesture())
注意:系统手势(onClick、onTouch、drag、bindMenu)始终优先于同类型的自定义手势。
沉浸式窗口(沉浸式/全屏/避让区)
将组件延伸至状态栏和导航栏区域
// Method 1 — expandSafeArea (simplest, component-level)
Column() { /* content */ }
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
// Method 2 — window-level fullscreen (affects all pages)
const win = windowStage.getMainWindowSync();
win.setWindowLayoutFullScreen(true);
获取安全区尺寸以手动设置内边距
import { window } from '@kit.ArkUI';
const win = await window.getLastWindow(context);
const systemAvoid = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
const topHeight = px2vp(systemAvoid.topRect.height); // status bar height
const bottomHeight = px2vp(systemAvoid.bottomRect.height); // navigation bar height
// Listen for changes (e.g. split screen, fold/unfold)
win.on('avoidAreaChange', (options: window.AvoidAreaOptions) => {
if (options.type === window.AvoidAreaType.TYPE_SYSTEM) {
// update top/bottom padding
}
});
隐藏/显示系统栏
win.setSpecificSystemBarEnabled('status', false); // hide status bar
win.setSpecificSystemBarEnabled('navigationIndicator', false); // hide nav indicator
状态栏文字颜色(浅色/深色内容)
win.setWindowSystemBarProperties({
statusBarContentColor: '#FFFFFF', // white text for dark backgrounds
});
列表常用操作(列表常用操作)
左滑删除(左滑操作)
ListItem() { /* content */ }
.swipeAction({
end: {
builder: () => {
Button('Delete').backgroundColor(Color.Red)
.onClick(() => {
animateTo({ duration: 300 }, () => {
this.dataList.splice(index, 1);
});
})
},
actionAreaDistance: 56,
},
edgeEffect: SwipeEdgeEffect.Spring,
})
拖动排序
List() {
ForEach(this.dataList, (item: string, index: number) => {
ListItem() { Text(item) }
})
}
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
this.dragIndex = itemIndex;
})
.onItemDragMove((event: ItemDragInfo, itemIndex: number, insertIndex: number) => {
animateTo({ duration: 200 }, () => {
const tmp = this.dataList.splice(this.dragIndex, 1);
this.dataList.splice(insertIndex, 0, tmp[0]);
this.dragIndex = insertIndex;
});
})
下拉刷新
Refresh({ refreshing: $$this.isRefreshing }) {
List() { /* items */ }
}
.onRefreshing(() => {
// fetch new data...
this.isRefreshing = false;
})
滚动到底部(聊天样式)
const scroller = new Scroller();
List({ scroller }) { /* items */ }
// After new message:
scroller.scrollEdge(Edge.Bottom);
// Or scroll to specific index:
scroller.scrollToIndex(this.messages.length - 1);
插入数据时保持滚动位置(LazyForEach)
List() { LazyForEach(this.dataSource, ...) }
.maintainVisibleContentPosition(true) // new items at top don't shift visible content
ListItemGroup + 粘性标题(分组列表)
@Builder sectionHeader(title: string) {
Text(title).fontSize(14).fontColor('#99000000')
.width('100%').padding({ left: 16, top: 8, bottom: 8 })
.backgroundColor('#F1F3F5')
}
List() {
ForEach(this.groups, (group: GroupData) => {
ListItemGroup({ header: this.sectionHeader(group.title) }) {
ForEach(group.items, (item: ItemData) => {
ListItem() { Text(item.name) }
})
}
})
}
.sticky(StickyStyle.Header) // header sticks to top when scrolling
onReachEnd — 加载更多数据
List() { LazyForEach(this.dataSource, ...) }
.onReachEnd(() => {
if (!this.isLoading) {
this.isLoading = true;
this.loadNextPage().then(() => { this.isLoading = false; });
}
})