Install
openclaw skills install @fff-119/harmonyos-code-workshopopenclaw skills install @fff-119/harmonyos-code-workshop📌 版本信息:本Skill基于 HarmonyOS 7 (API 26 Beta1) 编写,适用 DevEco Studio 26.0.0 Beta1。
快速开始:直接描述你的鸿蒙开发需求,我会自动加载本Skill并生成高质量ArkTS代码。
鸿蒙、HarmonyOS、ArkTS、帮我写一个鸿蒙应用📌 版本信息:本专家包基于 HarmonyOS 7 (API 26 Beta1) 编写,适用 DevEco Studio 26.0.0 Beta1。 若使用其他版本,请注意 API 差异。
⚡ 本专家持续进化中,以下四条机制在每次对话中自动运行。
触发时机:用户提供代码或描述现有代码逻辑时,自动扫描其中是否包含废弃 API。
扫描规则(对照 ## ⚠️ 关键废弃 API 迁移对照表 章节):
router.pushUrl、animateTo()、console.log 等)⬆️ 废弃 API 迁移对照表(51+ 组),逐行检测⚠️ [废弃API检测] 第X行使用了 {旧API} → 建议替换为 {新API}扫描重点清单:
| 检测项 | 旧API | ✅ 现代替代 | 严重程度 |
|---|---|---|---|
| 路由 | router.pushUrl() / router.back() | UIContext.getRouter() 或 Navigation | 🔴 高 |
| 动画 | 全局 animateTo() | this.getUIContext().animateTo() | 🔴 高 |
| 提示 | promptAction.showToast() 全局调用 | this.getUIContext().getPromptAction().showToast() | 🟡 中 |
| 日志 | console.log / console.info | @ohos.hilog | 🟡 中 |
| 文件 | @ohos.fileio | @ohos.file.fs (CoreFileKit) | 🔴 高 |
| 通知 | @ohos.notification | @ohos.notificationManager | 🟡 中 |
| 相机 | 旧 camera.Camera | CameraManager + Session | 🔴 高 |
| 上下文 | 全局 getContext(this) | this.getUIContext().getHostContext() | 🟡 中 |
| 窗口 | window.getTopWindow() 回调 | windowStage.getMainWindow() Promise | 🟡 中 |
| 全局状态 | globalThis | UIContext / AppStorage / LocalStorage | 🔴 高 |
触发时机:每次输出代码之前,强制运行完整的编译自检清单。
执行流程:
发现用户需要代码 → 编写代码 → 逐项对照「编译自检清单」检查
→ 发现错误 → 立即修正 → 再次检查 → 确认全部通过 → 输出
自检范围(共 4 大类 31+ 项,详见 ## ✅ 编译自检清单):
输出格式:在代码输出末尾追加自检结果徽标
// ✅ 代码质量自检通过 | 语法6/6 | API7/7 | 组件6/6 | 泛型6/6 | 性能6/6
发现无法修复的问题时:
触发时机:输出代码示例或完整组件时,自动标注目标 API 版本。
标注规则:
| API版本 | 标签 | 适用特性 |
|---|---|---|
| API 23+ | [API 23+] | 多数通用组件、基础 API、主流兼容(覆盖 94%+ 设备) |
| API 24+ | [API 24+] | 6.1.1 新增能力 |
| API 26+ | [API 26+] | Vibe Coding、沉浸光感、3DGS、空格音频等最新特性 |
| API 22+ | [API 22+] | 最低兼容目标(覆盖 99%+ 设备) |
格式要求:
// [API 23+] 网络请求封装// API 26+触发时机:用户未提出新问题或代码需求时(等待/查阅状态),主动推送鸿蒙开发知识。
推送规则:
| 领域 | 示例话题 | 来源章节 |
|---|---|---|
| 🏗️ 架构 | Navigation vs Router 迁移策略、MVVM / Repository 最佳实践 | API 版本演进 / ArkTS 架构模式 |
| 🐛 避坑 | 编译错误 #3 AppStorage、#6 对象字面量、内存泄漏排查三步法 | 常见编译错误速查 / 真实踩坑记录 |
| ⚡ 性能 | LazyForEach 的 IDataSource 实现、闭包优化、首屏加载优化策略 | 高性能编程实践 / 性能优化清单 |
| 🆕 新特性 | Vibe Coding、沉浸光感、碰一碰精准分享(API 26+) | API 版本演进 |
| 🎯 技巧 | @Extend 扩展组件、@Builder 复用 UI、MVVM 快速脚手架 | 自定义组件与扩展 / 高级架构模式 |
推送格式:
💡 [鸿蒙知识卡片] 你知道吗?
{具体知识点:一句话精华 + 代码片段(可选)+ 来源章节}
推送时机:
触发时机:用户提出复杂的架构设计、性能调优、多端适配等高级场景需求时。
应对策略:
参考来源:references/api26-new-capabilities.md 中的高级架构模式、性能优化速查、多设备适配章节
var、禁止 any/unknown、禁止解构赋值、禁止函数表达式、禁止运行时添加/删除对象属性)// [API 23+] 功能说明.width() .height() .backgroundColor() 等)// ✅ 代码质量自检通过 | 语法6/6 | API7/7 | 组件6/6 | 泛型6/6 | 性能6/6每次对话结束后,必须执行以下自检流程:
在每次对话的空闲间隙,判断是否需要推送一条鸿蒙知识卡片:
💡 [鸿蒙知识卡片] 你知道吗?{知识点一句精华}回答完用户问题后,检查本次对话中是否遇到了以下情况:
自己搞砸了的事,不等人骂,自己先总结:
流程:
发现盲区 → 搜索华为官方文档 → 验证信息准确性
→ 更新 agents/harmonyos-dev.md 对应章节
→ 运行 register_expert.py 重新注册
→ 告知用户"已补充新知识"
## [标题] 章节机制:每次用户对话结束时,无论是否有报错,必须自动执行以下判断:
检查本次对话中是否出现以下情况:
上述任意一条命中 → 执行 5b,否则跳过。
将经验追加到文件末尾的 ## 📝 实战经验库(持续积累) 章节:
### {日期} - {简短标题}
- **场景**:本次对话的上下文简述
- **发现**:具体踩坑或学到的内容
- **根因**:为什么会出现这个问题
- **修复**:正确的做法是什么
- **来源**:实测验证 / 官方文档 / 用户反馈
注意:
2026-06-24)如果新增的经验对应的错误码未在 ## 🚫 常见编译错误速查 中列出,同时更新速查表。
let / const,无 varany / unknown 类型,所有类型明确=>)@ts-ignore / @ts-nocheckthis(this 只在类实例方法中使用)this.getUIContext() 获取(animateTo、router、prompt、px2vp)@ohos.router@ohos.file.fs,非 @ohos.fileio@ohos.notificationManager,非 @ohos.notification@ohos.hilog,非 console.logCameraManager + Session,非旧 camera.Camerautil.TextEncoder/TextDecoder,非 decodeToStringhttpClient.get<object>() 而非 httpClient.get()any / unknown 类型,全部用具体类型替代{ username: string } 这种写法)Record<string, object> 不能放 string/number/boolean 基础类型值,改用具体接口result[9999] = 0 方式)result[9999] = 0 方式)number 类型变量使用哪个关键字?@State 和 @Prop 装饰器的根本区别是什么?Text 组件如何设置字体大小和颜色?Button 组件的 onClick 事件如何绑定?Column 和 Row 的区别是什么?Image 组件如何加载本地图片?@Builder 的作用是什么?Flex 布局的 justifyContent 有哪些常用值?@Link 和 @Prop 的区别是什么?@ohos.file.fs 如何读写文件?@Observed + @ObjectLink 的场景是什么?animateTo 和 router.pushUrl,如何批量迁移到 UIContext 和 Navigation?| 类别 | 规范 | 示例 |
|---|---|---|
| 类名/枚举名/命名空间 | UpperCamelCase | class UserModel |
| 变量名/方法名/参数名 | lowerCamelCase | let userName getUserData() |
| 常量名/枚举值名 | SCREAMING_SNAKE_CASE | const MAX_COUNT |
| 布尔变量 | 肯定前缀 | isReady hasData canShow |
private、protected、public)T[] 语法(不用 Array<T>)forEach、map、filter 等)Number.isNaN()(不能用 == 或 === 比较)return/break/continue/throw0.5 而非 .5)| TS 特性 | ArkTS 支持情况 |
|---|---|
any / unknown 类型 | ❌ 禁止,必须用具体类型 |
var 声明 | ❌ 必须用 let |
| 解构赋值 | ❌ 不支持,改为逐个声明 |
| 函数表达式 | ❌ 必须用箭头函数 => |
生成器函数 (function*) | ❌ 不支持 |
| 运行时添加/删除属性 | ❌ 禁止,对象布局固定 |
| 结构类型兼容 (Structural Typing) | ❌ 必须通过继承或接口实现 |
@ts-ignore / @ts-nocheck | ❌ 禁止使用 |
函数声明中的 this 使用 | ❌ 只能在类的实例方法中使用 |
| 嵌套函数声明 | ❌ 需改用 Lambda 表达式 |
extends/implements 建立类型关系Map<Object, Type>,对象属性名必须是合法标识符map/filter 等函数式方法处理数组转换*args/**kwargs@Component struct 继承自 CustomComponent 基类,所有 ArkUI 链式方法(.width() .height() .borderRadius() 等)都是基类属性/方法。用 @Prop/@State 声明同名字段会覆盖基类签名,导致 10505001 编译错误。
| 禁止使用的属性名 | 基类来源 | ✅ 建议改名 |
|---|---|---|
width | .width() 方法 | imageWidth / boxWidth |
height | .height() 方法 | imageHeight / boxHeight |
borderRadius | .borderRadius() 方法 | imageRadius / boxRadius |
backgroundColor | .backgroundColor() 方法 | bgColor / fillColor |
padding | .padding() 方法 | boxPadding / insetPadding |
margin | .margin() 方法 | boxMargin / outerMargin |
fontSize | .fontSize() 方法 | textSize / labelSize |
fontColor | .fontColor() 方法 | textColor / labelColor |
opacity | .opacity() 方法 | alpha / shimmerOpacity |
layoutWeight | .layoutWeight() 方法 | flexWeight / weight |
id | 组件唯一标识 | itemId / dataId |
key | ForEach key 相关 | itemKey / dataKey |
onClick | 事件处理 | onTap / onPress |
dist/index.html 中 manifest 链接、Apple 标签、SW 注册脚本可能丢失index.html 中直接写死这些标签(不要通过模板变量),或配置构建插件强制保留dist/index.html 包含 rel="manifest" 和 serviceWorker.registerapp.get('*') fallback 会吞掉 manifest.json、sw.js、图标等 PWA 文件if (req.path === '/manifest.json' || req.path.startsWith('/icons/')) return next()// ❌ 不适合 SPA:Cache-First 会导致用户看到旧页面
// ✅ 推荐 Network-First(API 类)/ Stale-While-Revalidate(静态资源)
// 对于纯静态资源(CSS/JS/图片):Cache-First 合适
// 对于页面 HTML:Network-First,离线时回退缓存
safe-area-inset-* CSS 环境变量适配状态栏/导航栏display: standalone 模式下页面边距处理orientation: "portrait-primary" 在鸿蒙上兼容sw.js 必须放在域名根路径或通过 scope 控制范围beforeinstallprompt 事件触发机制与其他浏览器不同| HarmonyOS 版本 | API 版本 | 发布时间 | 使用率 | 关键里程碑 |
|---|---|---|---|---|
| 7.1.0 Beta1 | 26 | 2026-06 | — | Vibe Coding、沉浸光感、3DGS、空间音频、星盾风控 |
| 6.1.1 | 24 | 2026-05 | 0.08% | 最新稳定版,开发能力增强 |
| 6.1.0 | 23 | 2026-04 | 84.94% | 🏆 主流版本,绝大多数设备运行此版本 |
| 6.0.2 | 22 | 2026-01 | 12.61% | 移除 "NEXT" 后缀,统一全设备框架 |
| 6.0.1 | 21 | 2025-11 | 0.95% | 稳定性增强 |
| 6.0.0 | 20 | 2025-09 | 0.27% | 碰一碰连接、企业空间 Kit |
| 5.1.1 | 19 | 2025-06 | 0.28% | 相机与 AI 功能增强 |
| 5.1.0 | 18 | 2025-06 | 0.13% | Ability Kit、Account Kit |
| 5.0.5 | 17 | 2025-05 | 0.54% | NEXT 迭代,增强分布式能力 |
| 5.0.4 | 16 | 2025-03 | 0.01% | 纯单框架里程碑:完全移除 AOSP |
| 5.0.0 | 12 | 2024-10 | 0.0% | NEXT 商用发布:纯鸿蒙,ArkTS 声明式开发范式确立 |
| 4.x 及以下 | 5~9 | 2023 前 | <0.1% | 双框架(兼容 AOSP),JS/Java 时代 |
| 你的目标 | 推荐的 API 版本 |
|---|---|
| 兼容最广(覆盖 94%+ 设备) | API 23 (HarmonyOS 6.1.0) |
| 最新稳定版 | API 24 (HarmonyOS 6.1.1) |
| 尝鲜新特性 | API 26 (HarmonyOS 7.0 Beta1) |
| 最低兼容(覆盖 99%+ 设备) | API 22 (HarmonyOS 6.0.2) |
来源:华为开发者官网 2026-06-21 发布。HarmonyOS 7 正式版预计 2026 秋季向消费者开放。
| 类别 | 新能力 | 说明 | 适用场景 |
|---|---|---|---|
| 🤖 智能化 | Vibe Coding (Skill 系统) | AI 辅助编程能力,支持 Skill 开发、调测、审核、上架,帮助应用功能被系统级智能入口调用 | 元服务、AI 助手集成 |
| Agent 系统 | 系统能力和模型能力开放,支持 Agent 能力构建和已有 Agent 的 A2A(Agent-to-Agent)接入 | 自动化流程、智能对话 | |
| 视觉 AI 能力 | 提供视觉 AI 基础能力和场景化控件,低门槛高效安全构建端侧视觉 AI 处理能力 | 图像识别、OCR、人脸检测 | |
| 🌌 空间化 | 沉浸光感组件 | 新增光随指动、光线勾勒、非线性形变等动效,快速提升核心界面空间感和沉浸感 | 电商详情页、游戏、品牌展示 |
| 3DGS 端侧重建 | 空间建模、商品展示、文旅展陈等 3D 场景,更快重建速度、更高精度、更完整细节 | 3D 建模、AR/VR、商品 3D 展示 | |
| 📱 全场景 | 碰一碰·精准分享 | 手机轻触电脑或平板屏幕,识别目标窗口和触碰坐标,将素材精准插入指定位置 | 多设备协同办公、跨屏传文件 |
| 🎵 媒体 | 空间音频 | 可组合降噪、美化、变声、空间渲染等音频节点,构建多样化空间音效和立体声场 | 直播、音乐播放器、音视频编辑 |
| 🪟 多窗 | 互动卡片 | 摇一摇触发卡片静态转动态、前景元素出框等效果,提升曝光与交互转化 | 元服务卡片、桌面小组件 |
| 闪控窗 | 标准悬浮窗,常驻展示实时状态,支持自由拖动、侧边栏暂存、与闪控球一键切换 | 实时监控、播放器迷你窗、计时器 | |
| 🔒 安全 | 星盾机密风控引擎 | 设备风险因子仅在端侧本地机密空间融合计算,数据"可用不可见" | 银行转账、支付交易风控 |
| 分布式数字身份 (DID) | 系统级数字身份框架,通过 TEE 存储颁发,使用时经本人同意按需出示 | 身份认证、证件验证 | |
| 数字盾 | 可信数字签名、可信 UI 确认和可信输入服务,关键操作安全性达 TEE 级 | 银行转账、企业签章 | |
| ⚡ 性能 & 体验 | 游戏快启 / 内核应用快启 | 游戏和应用快速启动能力 | 游戏、高频启动应用 |
| 冷启网络预建链 / QUIC 长连接 | 应用启动时预建网络链路,减少首屏等待时间 | 社交、新闻类首屏加载 | |
| 弱网直播优化 | 针对弱网环境优化的直播传输协议 | 直播、视频会议 | |
| 🔋 低功耗 | LTPO 可变帧率 | 根据内容动态调整屏幕刷新率,降低功耗 | 阅读、静态画面场景 |
| 类别 | 废弃的旧 API | ✅ 现代替代方案 |
|---|---|---|
| 动画 | 全局 animateTo() | this.getUIContext().animateTo() |
| 路由 | @ohos.router(旧 router.pushUrl()) | this.getUIContext().getRouter() 或 Navigation 组件(推荐) |
| 路由返回 | router.back() | this.getUIContext().getRouter().back() |
| 提示 | promptAction.showToast() | this.getUIContext().getPromptAction().showToast() |
| UI 单位 | 全局 px2vp() | this.getUIContext().px2vp() |
| 媒体查询 | mediaquery.matchMediaSync() | this.getUIContext().getMediaQuery().matchMediaSync() |
| 上下文 | 全局 getContext(this) | this.getUIContext().getHostContext() |
| 文件 | @ohos.fileio | @ohos.file.fs(@kit.CoreFileKit) |
| 通知 | @ohos.notification | @ohos.notificationManager(@kit.NotificationKit) |
| 日志 | console.log | @ohos.hilog(@kit.PerformanceAnalysisKit) |
| 显示 | window.getDefaultDisplay() | display.getDefaultDisplaySync()(@kit.ArkGraphics) |
| FA 模型 | featureAbility | UIAbilityContext(Stage 模型) |
| 窗口 | window.getTopWindow() | windowStage.getMainWindow() |
| 窗口布局 | setWindowLayoutFullScreen(回调) | await win.setWindowLayoutFullScreen(true)(Promise) |
| 窗口状态栏 | setWindowSystemBarProperties(回调) | await win.setWindowSystemBarProperties({...})(Promise) |
| 相机 | camera.Camera 旧 API | camera.CameraManager + camera.Session(@kit.CameraKit) |
| 相机输入释放 | CameraInput.release() | CameraInput.close() |
| 图片编码 | imagePacker.packing() | imagePacker.packToFile() |
| 全局状态 | globalThis | UIContext / AppStorage / LocalStorage |
| 状态监听 | AppStorage.watch() | @StorageLink / @StorageProp 装饰器 |
| 闪信 | @ohos.faultLogger | @ohos.hiviewdfx.hiAppEvent |
| 服务连接 | connect() | UIAbilityContext.connectServiceExtensionAbility() |
| 后台任务 | @ohos.backgroundTaskManager(旧 API9) | @ohos.resourceschedule.backgroundTaskManager |
| 网络请求 | @ohos.net.http(旧用法) | @hms.collaboration.rcp(推荐) |
| 滚动事件 | Scroll.onScroll 旧签名 | Scroll.onScroll((xOffset:number, yOffset:number) => void) |
| 屏幕密度 | Configuration.densityDpi | display.getDefaultDisplaySync() |
| 触摸事件 | TouchEvent 旧 API(touchPoint) | event.touches、event.source |
| SideBar | SideBarContainer(布尔参数) | 条件布局替代 |
| XComponent 导入 | 从 @kit.ArkUI 导入 | 使用全局 XComponent,无需 import |
| WindowMode | 已从 API 移除 | 改用布尔状态或自定义枚举 |
| AvoidArea | 缺少 visible 属性 | 初始化时添加 visible: false |
| 密钥生成 | generateRandomSync(旧 crypto) | cryptoFramework(@kit.CryptoArchitectureKit) |
| 编码解码 | decodeToString 字符串工具 | util.TextDecoder(@kit.ArkTS) |
| 窗口按钮区域 | getTitleButtonRect 返回 window.Rect | 返回 window.TitleButtonRect |
函数内 this | 独立函数中使用 this | 通过参数传递 context:function foo(context: Context) |
Router 已不推荐使用,官方推荐统一使用 Navigation 组件化导航。
| 对比项 | Router(旧,不推荐) | Navigation(推荐) |
|---|---|---|
| 控制方式 | URL 路由 | 组件栈(NavPathStack) |
| 参数传递 | URL Query 字符串 | 对象直接传递 |
| 页面管理 | 需在 main_pages.json 注册 | 组件内声明 NavDestination |
| 动效过渡 | 有限 | 原生支持丰富过渡动画 |
| 栈操作 | pushUrl/back | push/pop/replace/popToName/clear |
| 嵌套路由 | 不支持 | 支持嵌套 Navigation 容器 |
// ✅ 推荐:Navigation 组件导航
@Entry @Component
struct AppRoot {
private navStack: NavPathStack = new NavPathStack();
build() {
Navigation(this.navStack) {
NavDestination('Home') { HomePage(); }
NavDestination('Detail') { DetailPage(); }
}
// 跳转:this.navStack.pushPathByName('Detail', { id: 123 });
// 替换:this.navStack.replacePathByName('Detail', { id: 456 });
// 返回:this.navStack.pop();
// 回到首页:this.navStack.popToName('Home');
// 清空:this.navStack.clear();
}
}
// ❌ 不推荐:旧 Router 方式
// import router from '@ohos.router';
// router.pushUrl({ url: 'pages/Detail' });
this.getUIContext() 获取src/
├── model/ # 数据模型定义(接口、类)
├── viewmodel/ # 视图模型(状态管理、业务逻辑)
├── view/ # UI 组件(纯渲染、事件回调)
├── service/ # 网络/数据库等基础设施服务
├── resources/ # 静态资源
└── entryability/ # 应用入口
| 层 | 职责 | 技术实现 |
|---|---|---|
| Model | 数据结构定义、数据获取(网络/DB)、业务规则 | 接口/类定义、service 层调用 |
| ViewModel | 状态管理、将 Model 数据转为视图状态、事件处理逻辑 | @Observed + @State 装饰器 |
| View | UI 渲染、交互事件向上通知 | @Component、@Prop/@ObjectLink 接收状态 |
数据变更总是单向流动:父组件持有状态 → 通过 @Prop 传给子组件 → 子组件通过事件回调通知父组件 → 父组件更新状态
// 父组件:托管状态
@Entry @Component
struct ParentPage {
@State title: string = '初始';
build() {
ChildView({ title: this.title, onUpdate: (t) => { this.title = t; } })
}
}
// 子组件:只读展示 + 回调通知
@Component
struct ChildView {
@Prop title: string;
private onUpdate?: (t: string) => void;
build() { Button('更新').onClick(() => this.onUpdate?.('新标题')); }
}
状态提升到最近的共同父组件,子组件不持有状态,只接收和展示。
| 装饰器 | 用途 | 使用场景 |
|---|---|---|
@State | 组件内部状态 | 表单输入、折叠展开 |
@Prop | 父传子(只读) | 子组件展示父组件数据 |
@Link | 父子双向绑定 | 表单控件、实时编辑 |
@ObjectLink | 跨层级对象引用 | 复杂对象跨组件传递 |
@Observed | 深度监控对象变化 | ViewModel 类定义 |
@Provide/@Consume | 跨多层传递(无需逐层 Prop) | 主题色、用户登录态等全局状态 |
@LocalStorageProp/@LocalStorageLink | 应用级全局状态 | 跨页面共享状态 |
// model/UserData.ts
interface UserData { name: string; age: number; }
// viewmodel/UserViewModel.ts
@Observed
export class UserViewModel {
@State user: UserData | null = null;
@State loading: boolean = false;
async loadUser(id: number) {
this.loading = true;
// 调用 service 层获取数据...
this.user = { name: '张三', age: 25 };
this.loading = false;
}
}
// view/UserView.ets
@Component
export struct UserView {
@ObjectLink vm: UserViewModel;
build() {
Column() {
if (this.vm.loading) { LoadingProgress(); }
else { Text(this.vm.user?.name ?? '暂无数据'); }
}
}
}
| 作用域 | 方案 | 示例 |
|---|---|---|
| 组件内 | @State | 输入框聚焦状态 |
| 页面级 | @Provide/@Consume | 搜索条件、筛选状态 |
| 全局 | 独立 GlobalState 类 + Preferences 持久化 | 主题配置、用户登录信息 |
HarmonyOS 7 (API 26) 引入的新一代状态管理装饰器,替代原有 @Observed/@ObjectLink 的嵌套对象观察方案。
| 装饰器 | 作用 | 替代旧版 | 使用方式 |
|---|---|---|---|
@ObservedV2 | 类装饰器,标记为可观察类 | @Observed | @ObservedV2 class Model {} |
@Track | 属性装饰器,标记需要追踪的字段 | 无(旧版全量追踪) | @Track name: string = '' |
@ComponentV2 | 组件装饰器,V2 组件容器 | @Component | @ComponentV2 struct MyComp {} |
@Local | 局部状态,替代 @State | @State | @Local count: number = 0 |
@Param | 父传子参数,替代 @Prop/@Link | @Prop / @Link | @Param name: string = '' |
@Event | 事件回调,子传父 | 自定义回调函数 | @Event onTap: () => void |
@Monitor | 监听指定属性变化 | 无(需手动写 aboutToAppear) | @Monitor('count') onCountChange(mon: IMonitor) {} |
@Computed | 计算属性,自动依赖追踪 | 无(需手动计算 + 更新) | @Computed get fullName() { return this.first + this.last } |
@Provider / @Consumer | 跨层级共享状态(V2 版) | @Provide / @Consume | @Provider("theme") theme: Theme = ... |
@LocalStorage | 应用级持久化存储 | @LocalStorageProp / @Link | @LocalStorage("token") token: string = '' |
| 对比维度 | V1 装饰器 | V2 装饰器 |
|---|---|---|
| 嵌套对象更新 | @Observed + @ObjectLink,全量追踪 | @ObservedV2 + @Track,按字段追踪 |
| 计算属性 | 不支持,需手动维护 | @Computed 自动依赖追踪 |
| 属性监听 | 无原生支持,需手动 watch | @Monitor 内置监听 |
| 事件回调 | 手动传函数 | @Event 类型安全的事件机制 |
| 组件声明 | @Component struct | @ComponentV2 struct |
| API 要求 | API 12+ | API 26+ |
// 1. 定义可观察模型
@ObservedV2
class UserProfile {
@Track name: string = '';
@Track age: number = 0;
@Track email: string = '';
}
// 2. V2 组件使用
@ComponentV2
struct ProfileCard {
@Param user: UserProfile = new UserProfile();
@Event onUpdate: (user: UserProfile) => void = () => {};
@Monitor('user.name') // 监听 name 变化
onNameChange(mon: IMonitor): void {
console.info(`Name changed from ${mon.value()} to ${mon.value()}`);
}
@Computed
get displayInfo(): string {
return `${this.user.name} (${this.user.age})`;
}
build() {
Column() {
Text(this.displayInfo).fontSize(16)
Button('Update Name').onClick(() => {
this.user.name = 'New Name';
this.onUpdate(this.user);
})
}
}
}
| 方案 | 作用域 | 生命周期 | 是否持久化 |
|---|---|---|---|
@State | 组件内 | 组件销毁时释放 | ❌ |
@Prop/@Link | 父子组件 | 随组件 | ❌ |
@Provide/@Consume | 组件树层级 | 随组件树 | ❌ |
LocalStorage | 页面级 | 页面销毁时释放 | ❌ |
AppStorage | 应用全局 | 应用进程存活期间 | ❌ |
PersistentStorage | 应用全局 | 永久保留 | ✅ 持久化到磁盘 |
// 在 EntryAbility 创建页面时传入
let storage = new LocalStorage({ 'count': 0 });
windowStage.loadContent('pages/Index', storage);
// 子页面使用
@Entry(storage) @Component
struct IndexPage {
@LocalStorageProp('count') count: number = 0; // 只读
@LocalStorageLink('count') countLink: number; // 双向绑定
}
// 初始化
AppStorage.setOrCreate('userName', '默认用户');
// 组件内使用(注意不能带 default 值)
@StorageProp('userName') userName: string;
@StorageLink('loginState') loginState: boolean;
// 非组件中访问
const val = AppStorage.get<string>('userName');
AppStorage.set('userName', '新用户');
// 声明持久化键(与 AppStorage 联动)
PersistentStorage.persistProp('themeMode', 'light');
// 之后就像使用 AppStorage 一样
@StorageLink('themeMode') themeMode: string;
// 或直接 AppStorage 访问
AppStorage.set('themeMode', 'dark'); // 自动持久化到磁盘
@Entry @Component
struct LoginPage {
@State username: string = '';
@State password: string = '';
@State loading: boolean = false;
build() {
Column({ space: 16 }) {
TextInput({ placeholder: '用户名', text: this.username })
.onChange(v => this.username = v)
TextInput({ placeholder: '密码', text: this.password })
.type(InputType.Password).onChange(v => this.password = v)
Button('登录').width('100%').loading(this.loading)
.onClick(() => this.handleLogin())
}.padding(24).width('100%').height('100%')
}
async handleLogin() {
this.loading = true;
try { /* 调用登录 API */ }
finally { this.loading = false; }
}
}
class ListDataSource implements IDataSource {
private dataArr: MyItem[] = [];
totalCount(): number { return this.dataArr.length; }
getData(index: number): MyItem { return this.dataArr[index]; }
registerDataChangeListener(listener: DataChangeListener): void {}
unregisterDataChangeListener(listener: DataChangeListener): void {}
}
@Entry @Component struct ListPage {
private data = new ListDataSource();
build() { List() { LazyForEach(this.data, (item: MyItem) => { ListItem() { /* 渲染项 */ } }, item => item.id) } }
}
async function apiGet<T>(url: string): Promise<T> {
const httpReq = http.createHttp();
try {
const resp = await httpReq.request(url, { method: http.RequestMethod.GET, header: { 'Content-Type': 'application/json' } });
if (resp.responseCode === 200) return JSON.parse(resp.result as string) as T;
throw new Error(`HTTP ${resp.responseCode}`);
} finally { httpReq.destroy(); }
}
@Entry @Component struct App {
private stack: NavPathStack = new NavPathStack();
build() { Navigation(this.stack) { NavDestination('Home') { HomePage(); } NavDestination('Detail') { DetailPage(); } } }
}
async function savePref(key: string, val: ValueType) { const p = preferences.getPreferencesSync(context, { name: 'store' }); p.putSync(key, val); p.flush(); }
async function loadPref<T>(key: string, def: T): Promise<T> { const p = preferences.getPreferencesSync(context, { name: 'store' }); return p.getSync(key, def) as T; }
@State list: Item[] = [];
@State page: number = 1;
async loadMore() { const newData = await apiGet<Item[]>(`/list?page=${this.page}`); this.list = [...this.list, ...newData]; this.page++; }
Refresh({ refreshing: $$this.isRefreshing }) {
List() { /* 列表内容 */ }
}.onRefresh(() => { this.page = 1; this.list = []; this.loadMore(); })
import { photoAccessHelper } from '@kit.MediaKit';
async function pickImage(): Promise<string> {
const helper = photoAccessHelper.getPhotoAccessHelper(this.context);
const uris = await helper.selectPhotoUri(1);
return uris[0];
}
Search({ value: $$this.keyword }).onSubmit(() => this.search()).onChange(v => this.keyword = v)
Button('提交').onClick(async () => {
if (!this.validate()) return;
await apiPost('/submit', this.formData);
promptAction.showToast({ message: '提交成功' });
})
@State countdown: number = 60;
startCountdown() { const t = setInterval(() => { if (this.countdown <= 0) { clearInterval(t); return; } this.countdown--; }, 1000); }
AlertDialog.show({ title: '提示', message: '确定删除吗?', primaryButton: { value: '取消' }, secondaryButton: { value: '确定', action: () => this.delete() } })
.bindSheet($$this.showSheet, { height: SheetSize.MEDIUM }) { Text('面板内容') }
Swiper() { ForEach(this.banners, (b: string) => { Image(b).width('100%').height(200) }) }.autoPlay(true).interval(3000).indicator(true)
QRCode({ value: 'https://example.com' }).width(200).height(200)
import { shareController } from '@kit.ShareKit';
shareController.share({ title: '分享', text: '内容', url: 'https://...' });
import { scanCore } from '@kit.ScanKit';
const result = await scanCore.startScan({ scanType: scanCore.ScanType.QR_CODE });
import { geoLocationManager } from '@kit.LocationKit';
const loc = await geoLocationManager.getCurrentLocation({ priority: geoLocationManager.LocationRequestPriority.FIRST_FIX });
console.info(loc.latitude, loc.longitude);
const cm = this.context.config.colorMode; this.isDark = cm === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
import { call } from '@kit.TelephonyKit';
call.makeCall('10086');
import { sms } from '@kit.TelephonyKit';
sms.sendMessage({ destination: '10086', content: 'CXLL' });
import { pasteboard } from '@kit.BasicServicesKit';
const pb = pasteboard.getSystemPasteboard(); await pb.setData({ text: '复制内容' });
const text = (await pb.getData()).getPrimaryText();
import { vibrator } from '@kit.BasicServicesKit';
vibrator.vibrate({ duration: 200 });
import { sensor } from '@kit.SensorServiceKit';
sensor.on(sensor.SensorId.ACCELEROMETER, (data) => { console.info(data.x, data.y, data.z); });
import { userIAM_userAuth } from '@kit.UserAuthenticationKit';
const auth = await userIAM_userAuth.getAuthInstance({ challenge: new Uint8Array(0), authType: userIAM_userAuth.AuthType.FINGERPRINT });
await auth.start(); auth.on('result', (r) => { if (r.result === 0) { /* 验证通过 */ } });
import { connection } from '@kit.ConnectivityKit';
connection.on('netAvailable', () => { console.info('网络可用'); });
connection.on('netLost', () => { console.info('网络断开'); });
const info = this.context.getApplicationInfo();
console.info('版本:', info.versionName, '代码:', info.versionCode);
import { workScheduler } from '@kit.BackgroundTasksKit';
workScheduler.startWork({ workId: 1, bundleName: '...', abilityName: '...', isPersisted: true, networkType: workScheduler.NetworkType.NETWORK_TYPE_WIFI });
// 在 module.json5 中配置
// "metaData": { "customizeData": [{ "name": "hwc-theme", "value": "android:windowSoftInputMode=adjustResize" }] }
// 或在页面中
this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
import { request } from '@kit.NetworkKit';
const task = await request.downloadFile(this.context, { url: 'https://...', filePath: `${this.context.filesDir}/download.zip` });
task.on('progress', (received, total) => { console.info(`${received}/${total}`); });
await task.start();
以下 5 个模式来自华为官方多设备长视频应用 Sample,覆盖断点适配、异步竞态、窗口管理、输入安全和折叠屏适配,已适配 ArkTS 严格语法。
作用:根据当前设备断点返回不同配置值,一行代码替代 5 级 if-else。
// [API 23+] BreakpointType.ets — 泛型断点配置工具
export class BreakpointType<T> {
private xs: T;
private sm: T;
private md: T;
private lg: T;
private xl: T;
constructor(xs: T, sm: T, md: T, lg: T, xl: T) {
this.xs = xs;
this.sm = sm;
this.md = md;
this.lg = lg;
this.xl = xl;
}
getValue(currentWidthBreakpoint: WidthBreakpoint): T {
if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_XS) {
return this.xs;
}
if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_MD) {
return this.md;
}
if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_SM) {
return this.sm;
}
if (currentWidthBreakpoint === WidthBreakpoint.WIDTH_LG) {
return this.lg;
}
return this.xl;
}
}
// 使用示例
const columns: BreakpointType<number> = new BreakpointType<number>(1, 2, 3, 4, 6);
// Grid({ columnsTemplate: '1fr '.repeat(columns.getValue(currentBp)).trim() })
作用:用户快速切换页面/视频时,旧的异步操作返回后可能覆盖新状态。全局递增 sessionId + 关键点守卫检查。
// [API 23+] SessionID 防竞态守卫
class AsyncManager {
private globalSessionId: number = 0;
private activeSessionId: number = -1;
/// 开始新操作时调用,返回当前 sessionId
bumpSession(): number {
this.globalSessionId++;
this.activeSessionId = this.globalSessionId;
return this.activeSessionId;
}
/// 检查 sessionId 是否仍有效
isSessionActive(sessionId: number): boolean {
return sessionId === this.activeSessionId;
}
/// 异步操作,关键步骤都检查 session 有效性
async doAsyncOperation(sessionId: number): Promise<void> {
if (!this.isSessionActive(sessionId)) {
return; // 已过期,丢弃
}
// 模拟异步:网络请求 / 文件读取 / 数据计算
await new Promise<void>((resolve) => setTimeout(() => resolve(), 500));
if (!this.isSessionActive(sessionId)) {
return; // 等待期间又有新操作,丢弃旧结果
}
// 安全更新状态
}
}
作用:一站式封装窗口全周期管理——沉浸式切换、avoidArea 5 类型追踪、断点变化监听、分屏、窗口大小限制。
// [API 23+] 沉浸式类型枚举
export enum ImmersiveType {
NORMAL, // 默认:状态栏+导航栏可见
IMMERSIVE, // 沉浸式:状态栏+导航栏透明覆盖
FULLSCREEN_IMMERSIVE // 全屏沉浸式:隐藏状态栏+导航栏
}
// 窗口信息对象(@Observed 让组件监听变化)
@Observed
export class WindowInfo {
public windowStatusType: window.WindowStatusType = window.WindowStatusType.UNDEFINED;
public isImmersive: ImmersiveType = ImmersiveType.NORMAL;
public windowSize: window.Size = { width: 0, height: 0 };
public widthBp: WidthBreakpoint = WidthBreakpoint.WIDTH_XS;
public heightBp: HeightBreakpoint = HeightBreakpoint.HEIGHT_SM;
public avoidSystem?: window.AvoidArea;
public avoidNavigationIndicator?: window.AvoidArea;
public avoidCutout?: window.AvoidArea;
public avoidSystemGesture?: window.AvoidArea;
public avoidKeyboard?: window.AvoidArea;
}
export class WindowUtil {
public mainWindow: window.Window;
public mainWindowInfo: WindowInfo = new WindowInfo();
constructor(mainWindow: window.Window) {
this.mainWindow = mainWindow;
}
/// 设置沉浸式类型
setImmersiveType(type: ImmersiveType): void {
// NORMAL: setWindowLayoutFullScreen(false)
// IMMERSIVE: setWindowLayoutFullScreen(true) + setWindowDecorVisible(false)
// FULLSCREEN_IMMERSIVE: 全屏隐藏,maximize()
}
/// 初始化窗口信息并注册监听
updateWindowInfo(): void {
// 获取初始窗口状态、尺寸、断点、5 类 avoidArea
// 注册:windowStatusChange / windowSizeChange(同时更新断点)/ avoidAreaChange
}
/// 释放监听
release(): void {
this.mainWindow.off('windowStatusChange', ...);
this.mainWindow.off('windowSizeChange', ...);
this.mainWindow.off('avoidAreaChange', ...);
}
/// 设置分屏
setSplitScreen(bundleName: string, abilityName: string, moduleName: string): void {
let want: Want = { bundleName, abilityName, moduleName };
let option: StartOptions = { windowMode: AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_PRIMARY };
(this.mainWindow.getUIContext().getHostContext() as common.UIAbilityContext)
.startAbility(want, option);
}
}
作用:清除控制字符 + 长度截断,防止 XSS 和编码注入。
// [API 23+] InputSecurityUtil.ets
export class InputSecurityUtil {
private static readonly MAX_INPUT_LENGTH: number = 50;
/// 清除 ASCII 控制字符(0-31 和 127)+ 长度截断
static sanitizeSearchInput(raw: string): string {
const normalized: string = raw.replace(/[\u0000-\u001F\u007F]/g, '');
return normalized.slice(0, InputSecurityUtil.MAX_INPUT_LENGTH);
}
}
// 使用:TextInput({ placeholder: '搜索' })
// .onChange((v: string) => { this.keyword = InputSecurityUtil.sanitizeSearchInput(v); })
作用:运行时检测折叠屏能力,非折叠设备优雅降级;监听折叠状态联动全屏和布局。
// [API 23+] 折叠屏适配
import { display } from '@kit.ArkUI';
@Component
struct FoldAwareView {
@State isHalfFolded: boolean = false;
aboutToAppear(): void {
if (canIUse('SystemCapability.Window.SessionManager')) {
try {
if (display.isFoldable()) {
// 获取折痕区域,存入共享状态供避让使用
let creaseRegion = display.getCurrentFoldCreaseRegion();
AppStorage.setOrCreate('creaseHeight',
this.getUIContext().px2vp(creaseRegion.creaseRects[0].height));
// 监听折叠状态
display.on('foldStatusChange', this.onFoldStatusChange);
}
} catch (error) { /* 非折叠屏设备静默降级 */ }
}
}
aboutToDisappear(): void {
if (canIUse('SystemCapability.Window.SessionManager')) {
try { display.off('foldStatusChange'); } catch (error) { }
}
}
private onFoldStatusChange = (data: display.FoldStatus): void => {
if (data === display.FoldStatus.FOLD_STATUS_HALF_FOLDED) {
this.isHalfFolded = true;
// 半折叠态:自动全屏播放
} else {
this.isHalfFolded = false;
// 展开/折叠态:恢复标准布局
}
};
}
基于 UIObserver 能力(API 12+),可监听点击、曝光、页面切换、渲染性能等全部用户行为。这是生产级埋点 SDK 的 ArkTS 最小实现。
核心 API 链:uiContext.getUIObserver().on(eventName, callback) — 在 Ability 的 onWindowStageCreate 中一次注册,全局生效。
// [API 23+] UIObserver 埋点注册(在 EntryAbility.onWindowStageCreate 中)
import { hiAppEvent, hilog } from '@kit.PerformanceAnalysisKit';
import { FrameNode, uiObserver } from '@kit.ArkUI';
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/HomePage', () => {
const uiContext: UIContext = windowStage.getMainWindowSync().getUIContext();
// 1. 点击埋点(组件级别精度)
uiContext.getUIObserver().on('willClick', (_event: ClickEvent, node?: FrameNode) => {
// node.getId() → 组件 ID(.id('button-1') 设置的)
// node.getUniqueId() → 全局唯一 ID
// node.getCustomProperty(key) → 自定义业务数据
// uiContext.getPageInfoByUniqueId(uniqueId) → 获取所在页面信息
let eventParams: Record<string, string | number> = {
'component_id': node?.getId() ?? '',
'pageInfo': JSON.stringify(uiContext.getPageInfoByUniqueId(node?.getUniqueId()) ?? {}),
'trackData': JSON.stringify(node?.getCustomProperty(node?.getId()) ?? {})
};
// 上报到 hiAppEvent
hiAppEvent.write({ domain: 'user_action', name: 'click',
eventType: hiAppEvent.EventType.BEHAVIOR, params: eventParams });
});
// 2. 滚动埋点(曝光时长分析)
uiContext.getUIObserver().on('scrollEvent', (info: uiObserver.ScrollEventInfo) => {
// info.scrollEvent: SCROLL_START / SCROLL_STOP / SCROLL_END
});
// 3. Navigation 页面切换埋点
uiContext.getUIObserver().on('navDestinationSwitch', (info: uiObserver.NavDestinationSwitchInfo) => {
// 可获取页面路径、入参、目标页面 name
});
// 4. Router 页面更新埋点
uiContext.getUIObserver().on('routerPageUpdate', (info: uiObserver.RouterPageInfo) => {
// 可获取 router 跳转的页面信息
});
// 5. 渲染性能埋点(页面启动耗时)
uiContext.getUIObserver().on('willDraw', () => { startTime = Date.now(); });
uiContext.getUIObserver().on('didLayout', () => { endTime = Date.now(); });
// 页面渲染耗时 = endTime - startTime
});
}
// 组件端埋点声明(在页面组件中)
Button('点击埋点')
.id('button-1') // ← 埋点用的组件 ID
.customProperty('button-1', { id: 'button-1', bizType: 'submit' }) // ← 自定义业务数据
.onClick(() => { /* 业务逻辑 */ })
组件曝光埋点(配合 TrackNode 包装组件):
// 用 onDidBuild 生命周期获取 FrameNode
@Component
export struct TrackNode {
@BuilderParam closer: VoidCallback; // 要包装的内容
track: Track;
onDidBuild(): void {
let uid: number = this.getUniqueId();
let node: FrameNode | null = this.getUIContext().getFrameNodeByUniqueId(uid);
// 注册可见区域变化回调
node?.commonEvent.setOnVisibleAreaApproximateChange(
{ ratios: [0, 0.5, 1], expectedUpdateInterval: 500 },
(ratioInc: boolean, ratio: number) => {
// ratio: 0 → 不可见, 0.5 → 半可见, 1 → 完全可见
hiAppEvent.write({ domain: 'exposure', name: 'component_visible', ... });
});
}
}
// 使用:TrackNode({ track: new Track().id('WaterFlow-1') }) { AnyComponent() }
import { emitter } from '@kit.BasicServicesKit';
// 订阅事件
emitter.on({ eventId: 'user_login' }, (event) => {
console.info('用户登录:', event.data?.userName);
});
// 发送事件
emitter.emit({ eventId: 'user_login' }, { data: { userName: 'admin' } });
// 一次性订阅
emitter.once({ eventId: 'app_init' }, () => { /* 只执行一次 */ });
// 取消订阅
emitter.off('user_login');
// 带延迟的事件
emmitter.emit({ eventId: 'data_loaded', priority: emitter.EventPriority.HIGH });
// LOW / HIGH / IMMEDIATE 三种优先级
// 已在 MVVM 章节覆盖,此处仅作对照
// @Provide 提供值,@Consume 消费值,跨任意层级传递
// 适合:主题切换、用户登录态、全局配置
| 场景 | 方案 |
|---|---|
| 组件树内跨层级(3-5层) | @Provide/@Consume |
| 跨页面/跨 Ability | Emitter |
| 全局持久化状态 | AppStorage / PersistentStorage |
| 跨进程通信 | IPCKit(@kit.IPCKit) |
源自 arkts-patterns v2.3.1 生产级模式库。TaskPool 是 API 12+ 推荐的多线程方案。
import { taskpool } from '@kit.ArkTS';
// 1. 定义任务函数(必须 @Concurrent + 全局函数)
@Concurrent
function computeHeavyTask(data: number[]): number {
// CPU 密集型计算,运行在独立 worker 线程
return data.reduce((a, b) => a + b, 0);
}
// 2. 在 UI 线程调用
async runTask(): Promise<void> {
try {
const task = new taskpool.Task(computeHeavyTask, [1, 2, 3, 4, 5]);
const result: number = await taskpool.execute(task) as number;
console.info(`Result: ${result}`);
} catch (err) {
console.error(`Task failed: ${err.message}`);
}
}
@Concurrent
function longRunningTask(data: number[], callback: taskpool.Callback): number {
let total = 0;
for (let i = 0; i < data.length; i++) {
total += data[i];
// 通知主线程进度
callback.call(i / data.length);
}
return total;
}
// 创建带优先级的 Task
const task = new taskpool.Task(longRunningTask, [dataArray]);
task.setPriority(taskpool.Priority.HIGH);
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简短计算(<1s) | TaskPool | 自动线程池管理,开销小 |
| 长时后台服务 | Worker | 常驻线程,独立生命周期 |
| 需要频繁通信 | Worker | 支持双向消息推送 |
| MindSpore 推理 | TaskPool | 内部已用线程池,避免冲突 |
| 文件 I/O | Worker | 避免阻塞 UI 和线程池 |
WaterFlow() {
LazyForEach(this.dataSource, (item: ItemData) => {
FlowItem() {
Column() {
Image(item.img).width('100%').aspectRatio(item.ratio)
Text(item.title).fontSize(14)
}
}
}, (item) => item.id)
}
.columnsTemplate('1fr 1fr') // 双列
.sections(this.sections) // 混合排列(不同列数区域)
.nestedScroll({ scrollForward: 1, scrollBackward: 1 })
.onReachEnd(() => this.loadMore()) // 无限加载
.cachedCount(8)
RichEditor({ controller: this.editorCtrl })
.onReady(() => {
// 插入文本
this.editorCtrl.insertTextSpan({ text: 'Hello HarmonyOS!' });
// 插入图片
this.editorCtrl.insertImageSpan({ uri: 'data/img.png' });
// 获取 HTML
const html = this.editorCtrl.toHtml();
})
// 显式动画
animateTo({ duration: 300, curve: Curve.EaseOut }, () => {
this.opacity = 1;
this.scale = { x: 1.2, y: 1.2 };
});
// 属性动画
Image().animation({ duration: 500, iterations: -1 })
.rotate({ angle: 360 }) // 无限旋转
来源:
AppStartUp| Gitee harmonyos_samples | 源码级
基于 @kit.AbilityKit 的 AppStartup 框架,支持同步/异步初始化任务解耦。
startup_config.json → startupManager.run → StartupTask(自动/手动) → 任务完成
entry/src/main/ets/startup/
├── StartupConfig.ets # 启动参数配置
├── FileTask.ets # 文件读写
├── ImageKinfeTask.ets # 图片框架初始化
├── KvManagerUtilTask.ets # 分布式数据库
├── KVStoreTask.ets # KV 写入
├── RdbStoreTask.ets # 关系型数据库
└── ResourceManagerTask.ets # resource 资源
// resources/base/profile/startup_config.json
{
"startupTasks": [
{ "name": "KvManagerUtilTask", "dependencies": [] },
{ "name": "KVStoreTask", "dependencies": ["KvManagerUtilTask"] },
{ "name": "RdbStoreTask", "dependencies": [] }
]
}
// [API 23+] 手动执行初始化任务
import { startupManager } from '@kit.AbilityKit'
Button('手动初始化')
.onClick(async () => {
await startupManager.run('RdbStoreTask')
console.info('Manual Task execute success')
})
来源:
component-reuse| Gitee harmonyos_samples | 源码级
| 场景 | 技术方案 | 说明 |
|---|---|---|
| 同一列表内复用 | @Reusable + aboutToReuse() | 列表项结构相同或不同时复用 |
| 多列表间复用 | NodeContainer 占位 + NodePool 全局缓存 | 不同父组件间子组件共享 |
// [API 23+] @Reusable 组件复用
@Component
@Reusable
struct ReusableItem {
@State text: string = ''
// 复用时的生命周期回调
aboutToReuse(params: Record<string, Object>) {
this.text = params.text as string
}
build() {
Row() {
Text(this.text).fontSize(16)
}
.padding(12)
}
}
// [API 23+] NodePool 全局缓存复用池
import { BuilderNode, NodeContainer, NodePool } from '@kit.ArkUI'
// 全局缓存复用池
const nodePool: NodePool = new NodePool()
// 从池中获取或创建
let node = nodePool.get() ?? new BuilderNode(null)
// 使用 node 绑定到 NodeContainer
来源:
fluent-blog| Gitee harmonyos_samples | 源码级
通过 animateTo 设置 ExpectedFrameRateRange 控制屏幕刷新率来平衡功耗:
// [API 23+] 设置期望帧率
animateTo({
duration: 300,
expectedFrameRateRange: { min: 30, max: 60, preferred: 30 }
}, () => { /* 动画内容 */ })
| 动画类型 | API | 说明 |
|---|---|---|
| 属性动画 | animateTo({duration, curve, delay}, () => { 改属性 }) | 最常用,让属性变化平滑过渡 |
| 关键帧动画 | keyframeAnimateTo([{duration, curve, event:()=>{}}, ...]) | 分段控制,多段动画串行 |
| 显式动画 | @AnimatableExtend 自定义可动画属性 | 实现复杂非标准属性动画 |
| 转场动画 | transition({ type: TransitionType, opacity, translate }) | 组件出现/消失动效 |
| 页面转场 | Navigation 自带 NavTransition | 页面跳转动效(滑入/淡入等) |
| 弹簧动画 | springMotion(响应速度, 阻尼系数) | 弹性效果,适合消息提醒、点赞 |
duration: 时长(ms),常用 200~500mscurve: 曲线类型 — Curve.Linear(匀速)、Curve.EaseIn(慢→快)、Curve.EaseOut(快→慢)、Curve.EaseInOut(慢→快→慢,最自然)delay: 延迟启动(ms)iterations: 播放次数,-1 为无限循环// 一个结束接另一个
animateTo({ duration: 300 }, () => { this.x = 100 })
.then(() => animateTo({ duration: 300 }, () => { this.rotate = 90 }));
animateTo() 已废弃 → ✅ this.getUIContext().animateTo()ModalTransition + 自定义弹窗springMotion 提升滚动手感| 类型 | 手势 | 说明 |
|---|---|---|
| 点击类 | TapGesture({ count: 1 }) | 单击/双击,count 为点击次数 |
| 长按类 | LongPressGesture({ duration: 500 }) | 长按触发,duration 为最小按压时长(ms) |
| 拖拽类 | PanGesture({ direction: PanDirection.Horizontal }) | 拖拽平移,支持方向限制 |
| 滑动类 | SwipeGesture({ speed: 100 }) | 快速滑动识别 |
| 捏合类 | PinchGesture({ distance: 5 }) | 双指缩放,distance 最小识别距离 |
| 旋转类 | RotationGesture({ angle: 1 }) | 双指旋转 |
// 单个手势
.gesture(TapGesture({ count: 1 }).onAction((e) => { /* 单击 */ }))
// 多手势并行(同时识别)
.gesture(
GestureGroup(GestureMode.Parallel,
TapGesture().onAction(() => {}),
PanGesture().onActionStart(() => {})
)
)
// 多手势互斥(只识别一个)
.gesture(
GestureGroup(GestureMode.Exclusive,
TapGesture().onAction(() => {}),
LongPressGesture().onAction(() => {})
)
)
| 回调 | 触发时机 |
|---|---|
.onAction(event) | 手势识别成功 |
.onActionStart(event) | 手势开始 |
.onActionUpdate(event) | 手势进行中(拖拽时持续触发) |
.onActionEnd(event) | 手势结束 |
.onActionCancel() | 手势取消 |
| 策略 | 说明 | 实现方式 |
|---|---|---|
| Parallel | 父子/兄弟手势同时响应 | GestureMode.Parallel |
| Exclusive | 只识别优先级最高的一个 | GestureMode.Exclusive |
| Sequence | 手势按顺序触发 | GestureMode.Sequence(如先长按再拖拽) |
| priority | 子组件优先处理 | .priorityGesture(TapGesture()) 替代 .gesture() |
// 拖拽跟手
.gesture(PanGesture()
.onActionUpdate((e: GestureEvent) => {
this.offsetX = e.offsetX; // 实时偏移量
})
.onActionEnd(() => {
animateTo({ curve: Curve.Spring }, () => {
this.offsetX = 0; // 松手回弹
});
})
)
// 点击+拖拽(滑动翻页)
.gesture(
GestureGroup(GestureMode.Parallel,
TapGesture().onAction(() => this.jumpPage()),
PanGesture({ direction: PanDirection.Horizontal })
.onActionEnd((e) => {
if (e.offsetX < -50) this.nextPage();
})
)
)
来源:
animation-collection| Gitee harmonyos_samples | 源码级
17种常见动效,按功能域组织:
| 模块 | 动效 | 核心实现 |
|---|---|---|
| 翻转动效 | pageTurningAnimation | 3D 卡片翻转 |
| 标题下拉 | pageExpandTitle | animateTo + 顶部偏移 |
| 状态栏显隐 | pageStatusBarChange | 滚动监听 + 渐隐渐现 |
| 水波纹 | pageWaterRipples | 逐层放大 + 透明度渐减 |
| 列表滑动 | pagelistslidetohistory | 滑动阻尼 + 回弹 |
| 跑马灯 | pageMarqueeView | marquee() 属性 |
| Swiper高度变化 | swipersmoothvariation | Swiper 动态高度 |
| 自定义进度 | pagePaintComponent | Canvas + 弧形路径 |
| 数字滚动 | digitalscrollanimation | 数字逐位翻转 |
| 卡片预览 | pageCardsSwiper | Swiper + Scale 放大 |
| 投票动效 | votingcomponent | 柱形图动画填充 |
| 语音录制动效 | voiceRecordDynamicEffect | 声波动画 |
| 抖动动效 | pageVibrateEffect | 弹簧曲线动画 |
| 侧边栏淡入淡出 | sidebarAnimation | opacity 过渡 |
| 多层级轮播 | swiperComponent | Swiper 嵌套 |
| 搜索 | searchComponent | Navigation + 模糊查询 |
来源:
transitions-collection| Gitee harmonyos_samples | 源码级
| 类型 | 动效 | 核心技术 |
|---|---|---|
| 多模态页面转场 | 半模态 + 全屏 + 左右切换 | bindSheet + bindContentCover + TransitionEffect.asymmetric() |
| 搜索一镜到底 | 搜索框放大过渡 | geometryTransition 绑定同一 id |
| 卡片一镜到底 | 瀑布流卡片 → 详情 | customNavContentTransition + componentSnapshot |
| 图片一镜到底 | 双指放大/查看大图/半模态 | NodeContainer 跨节点迁移 / geometryTransition |
| 视频一镜到底 | 封面 → 播放 | customNavContentTransition + NodeController |
| 列表一镜到底 | 列表项 → 详情 | geometryTransition + 显式动画 |
| 图书翻页 | 封面 → 内页 | customNavContentTransition 自定义转场 |
// [API 23+] 一镜到底过渡
@State isOpen: boolean = false
Column() {
if (!this.isOpen) {
Image(this.src)
.geometryTransition('sharedImage')
.onClick(() => { this.isOpen = true })
}
if (this.isOpen) {
Image(this.src)
.geometryTransition('sharedImage')
.width('100%').height('100%')
}
}
.animation({ duration: 300, curve: Curve.Friction })
来源:
text-effects| ⭐30 Stars | Gitee harmonyos_samples | 源码级
基于 Text 组件及通用属性实现多种文字特效。
// [API 23+] 文字特效
Text('特效文字')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#FF6B81')
.textShadow({
radius: 10,
color: '#FF6B81',
offsetX: 0,
offsetY: 0
})
.blur(0.5) // 模糊
.opacity(0.9) // 透明度
.rotate({ x: 0, y: 1, z: 0, angle: 15 }) // 3D旋转
| 实践 | 说明 | 示例 |
|---|---|---|
| 用 const 声明不变变量 | 避免不必要的运行时重绑定 | const index = 10000; |
| number 避免整浮混用 | 初始化后不要改变数据类型 | let n = 1; 不要后面赋值为 n = 1.1 |
| 避免数值溢出 | 加减乘除幂运算应避免超过 INT32_MAX/INT32_MIN | 数值保持在 ±2147483647 以内 |
| 循环中常量提取 | 将循环中不变的值提出来,减少属性访问 | const info = obj.arr[idx]; for(...) |
? 可选参数,减少非空判断开销Int8Array、Float64Array 等result[9999] = 0 会触发 hash 存储,访问变慢arrInt、arrDouble、arrString| 方案 | 适用场景 | 说明 |
|---|---|---|
| async/await | 常规异步任务(网络请求、文件读写) | 主线程执行,不阻塞 UI |
| TaskPool | CPU 密集型任务(图像处理、数据计算) | 多线程并行,推荐用于密集型计算 |
| Worker | 长时间运行的后台任务(流处理、大量数据) | 独立线程,适合持续性任务 |
totalCount、getData、registerDataChangeListener、unregisterDataChangeListener 四个方法@Reusable 装饰器进一步提升滚动性能| 工具 | 包名 | 本质 | 适用场景 |
|---|---|---|---|
| DevEco Code | @deveco/deveco-code | AI Agent 对话式工具 | 自然语言驱动编码/构建/调试全流程 |
| DevEco CLI | @deveco/deveco-cli | 命令行工具集 | 封装 hvigor/hdc/ohpm 原子能力供 AI Agent 调用 |
DevEco Code = 自带 AI 大脑的"自动驾驶"开发伙伴;DevEco CLI = 供已有 AI Agent 调用的"鸿蒙能力工具箱"
npm install -g @deveco/deveco-code
cd your-harmonyos-project
deveco-code
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Build | 默认 Agent,完整读写权限 | 日常开发、Bug 修复、功能实现 |
| Plan | 只读 Agent,代码分析+方案规划 | 探索陌代码库、设计重构方案 |
| Goal | 端到端自动驾驶:拆解任务→编码→编译→部署→自修复 | 按需求全自动交付功能 |
| 工具 | 功能 | 等价传统命令 |
|---|---|---|
build_project | 编译构建 | hvigorw assembleHap |
start_app | 模拟器/真机运行 | hdc install + hdc shell aa start |
hdc_log | 设备日志收集/清理 | hdc shell hilog |
verify_ui | 自然语言 UI 验证 | 手动操作+截图对比 |
check_ets_files | ArkTS 静态语法检查 | DevEco Studio ArkTS-Check |
arkts_knowledge_search | HarmonyOS 知识库检索 | 官网文档/API Reference |
deveco init -n AppName -t app -s default # 创建工程
deveco ohpm install # 同步依赖
deveco build --mode module --module-name entry # 构建模块
deveco build --mode release --sign-config ./sign/autoSign.json # Release 包
deveco --version # 版本检查
内置免费提供 GLM-5.1 模型,通过 Ctrl+A 支持接入 DeepSeek / OpenAI / 任意 OpenAI 兼容 Provider。
// deveco.jsonc
{
"provider": {
"deveco": {
"name": "DevEco Code",
"models": { "glm-5": { "tool_call": true, "limit": { "context": 200000, "output": 8192 } } },
"options": { "baseURL": "https://api.openbitfun.com/v1", "apiKey": "{env:DEVECO_API_KEY}" }
}
}
}
DEVECO_HOME 环境变量指向 DevEco Studio 安装目录| 测试类型 | 框架 | 说明 |
|---|---|---|
| 单元测试 | JsUnit(@ohos.unittest) | 测试函数/类逻辑,在 ohosTest 目录下编写 |
| UI 自动化测试 | Hypium(@ohos.test.uitest) | 基于 ArkUI 的 UI 操作模拟、断言 |
| 性能测试 | DevEco Testing | 内存泄漏、启动耗时、帧率检测 |
| 专项测试 | 云调试 + 远程真机 | 兼容性、稳定性、压力测试 |
// ohosTest/entry/src/test/UserModelTest.ts
import { describe, it, expect } from '@ohos/hypium';
describe('UserModelTest', () => {
it('calcAge_should_return_correct', 0, () => {
const result = calcAge(1990);
expect(result).assertEqual(36);
});
});
import { Driver, ON, Component } from '@ohos.test.uitest';
const driver = Driver.create();
await driver.findComponent(ON.text('登录')).click();
await driver.findComponent(ON.id('username')).inputText('admin');
await driver.findComponent(ON.text('确认')).click();
// 断言结果
const result = await driver.findComponent(ON.text('登录成功'));
expect(result).assertIsTrue();
基于官方 Sample
exception-handling(2,829下载)。在线上环境,自动捕获崩溃和卡死事件是质量保障的基础能力。
import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent';
export function subscribeAppEvents(): void {
hiAppEvent.addWatcher({
name: "app_crash_watcher",
appEventFilters: [
{
domain: hiAppEvent.domain.OS,
names: [
hiAppEvent.event.APP_CRASH, // 应用崩溃
hiAppEvent.event.APP_FREEZE // 应用卡死
]
}
],
onReceive: (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
// 将事件数据存入应用级状态
AppStorage.setOrCreate('appEventGroups', appEventGroups);
}
});
}
// 在组件中监听并解析
@StorageLink('appEventGroups') @Watch('parseCrashInfo')
appEventGroups: Array<hiAppEvent.AppEventGroup> = [];
async parseCrashInfo(): Promise<void> {
if (!this.appEventGroups?.length) return;
for (const group of this.appEventGroups) {
for (const event of group.appEventInfos) {
const params = event.params;
const crashInfo: string =
`领域: ${event.domain}\n` +
`事件: ${event.name}\n` +
`类型: ${event.eventType}\n` +
`时间: ${params['time']}\n` +
`崩溃类型: ${params['crash_type']}\n` +
`前台/后台: ${params['foreground']}\n` +
`版本: ${params['bundle_version']}\n` +
`包名: ${params['bundle_name']}\n` +
`异常详情: ${JSON.stringify(params['exception'])}\n`;
// 存入懒加载数据源
this.faultDataSource.pushData(crashInfo);
}
}
}
// 触发崩溃:解析非法 JSON
const result: object = JSON.parse(''); // APP_CRASH
// 触发卡死:无限循环
while (true) { } // APP_FREEZE
import { preferences } from '@kit.ArkData';
// 存储
dataPreferencesManager.put('faultMessage',
JSON.stringify(crashRecords), (err) => {
if (!err) dataPreferencesManager.flush();
});
// 读取
dataPreferencesManager.get('faultMessage', [])
.then((data) => {
const records: Array<string> = JSON.parse(data as string);
// 恢复数据到懒加载数据源
});
订阅层(addWatcher)
↓
全局状态(AppStorage)
↓
组件监听(@StorageLink + @Watch)
↓
数据解析 → 懒加载数据源(LazyForEach DataSource)
↓
持久化(Preferences)
↓
UI 渲染(List + LazyForEach)
来源:
AppLifecycleManagement| Gitee harmonyos_samples | 源码级
| 动作 | API | 说明 |
|---|---|---|
| 前后台监听 | ApplicationContext.on('applicationStateChange') | 监听应用前后台切换 |
| 关闭 Ability | UIAbilityContext.terminateSelf() | 关闭当前 UIAbility |
| 关闭应用 | ApplicationContext.killAllProcesses() | 杀死应用的所有进程 |
| 重启应用 | ApplicationContext.restartApp(restartWant) | 重启 App |
| 切到后台 | Window.minimize() | 最小化窗口到后台 |
// [API 23+] 前后台状态监听
import { common } from '@kit.AbilityKit'
const ctx = getContext(this) as common.UIAbilityContext
const appCtx = ctx.getApplicationContext()
// 监听应用前后台切换
appCtx.on('applicationStateChange', (state: number) => {
if (state === 0) { /* 前台 */ }
if (state === 1) { /* 后台 */ }
})
// 重启应用
appCtx.restartApp({
bundleName: 'com.example.app',
abilityName: 'EntryAbility'
})
以下 DFX(Design For X)能力是 API 26 Beta1 版本全新推出的诊断/调试/性能优化工具集。
将快照中的同类对象进行聚类分析,统计各泄漏对象的影响大小,定位内存泄漏问题。
import { jsLeakWatcher } from '@kit.PerformanceAnalysisKit';
// 对具有生命周期的 ArkTS 组件对象定期执行泄漏检测
const watcher = jsLeakWatcher.createWatcher();
watcher.on('leakDetected', (result) => {
console.info(`泄漏对象: ${result.className}, 引用链: ${result.referenceChain}`);
});
watcher.start({ interval: 30000 }); // 每 30s 检测一次
@kit.PerformanceAnalysisKit → jsLeakWatcherimport { hidebug } from '@kit.PerformanceAnalysisKit';
// 启动资源分配栈采集(线上运维)
hidebug.OHHiDebugStartProfiler({ type: 'globalHandle', duration: 60000 });
// 停止后通过 MemTrace 日志分析泄漏
hidebug.OHHiDebugStopProfiler();
@kit.PerformanceAnalysisKit → hidebugOHHiDebugStartProfiler() / OHHiDebugStopProfiler()(C API)import { hiAppEvent } from '@kit.PerformanceAnalysisKit';
// 订阅 APP_KILLED 事件,获取应用上一次退出原因
hiAppEvent.on('APP_KILLED', (event) => {
console.info(`退出原因: ${event.exitReason}, 时间: ${event.timestamp}`);
});
// 进一步分析故障根因:订阅 CRASH / FREEZE 并关联同一次故障
hiAppEvent.on('APP_CRASH', (event) => {
console.info(`crash info: ${event.crashStack}, uniqueId: ${event.appRunningUniqueId}`);
});
@kit.PerformanceAnalysisKit → hiAppEventmodule.json5 中开启主线程采样,订阅 APP_FREEZE 事件hiAppEvent.on('APP_FREEZE', (event) => {
// event.threadStacks: 包含多个采样点的调用栈信息
// event.cpuUsage: CPU 使用率
// event.mainThreadDuration: 主线程运行时长
console.info(`阻塞详情: ${JSON.stringify(event.threadStacks)}`);
});
运维态地址越界检测,定位释放后使用、堆溢出、重复释放、非法释放等踩内存问题。
// 在 module.json5 中配置 GWPAsan
// "gwpAsan": {
// "enabled": true,
// "samplingRate": 0.01,
// "maxSlots": 16,
// "recoverable": true
// }
// C/C++ 代码中调用
OH_JSVM_EnableLocalHandleDetection(jsvm_env env, bool enable);
napi_value 生命周期---## 🏗️ hvigorw 构建系统速查
命令行构建神器,CI/CD 和脱离 IDE 打包必备。
# ▸ 打包类型
hvigorw assembleHap # 构建 HAP(调试包,默认)
hvigorw assembleHap -p buildMode=release # 构建 release HAP
hvigorw assembleApp -p buildMode=release # 构建 APP(上架用)
hvigorw assembleHar # 构建 HAR 共享库
hvigorw assembleHsp # 构建 HSP 共享包
# ▸ 模块级构建(只编译改动的模块,加速)
hvigorw assembleHap --mode module -p module=entry@default
# ▸ 清理
hvigorw clean assembleHap # 先清理再构建
# ▸ 测试
hvigorw onDeviceTest -p module=entry # 真机测试
hvigorw test -p module=entry # 本地测试(无需设备)
# 环境变量(CI 服务器)
export NODE_HOME=/path/to/command-line-tools/tool/node
export JAVA_HOME=/path/to/jdk
export PATH=$NODE_HOME/bin:$JAVA_HOME/bin:/path/to/command-line-tools/bin:$PATH
export OHOS_SDK=/path/to/command-line-tools/sdk
# 安装依赖
ohpm install --all
# 构建(CI 推荐 --no-daemon)
hvigorw assembleHap -p buildMode=debug --no-daemon
# 签名:通过环境变量注入密码,build-profile.json5 中配置
# 或通过 hvigorfile.ts 读取 process.env.SIGNING_PASSWORD
| 参数 | 说明 |
|---|---|
--no-daemon | CI 环境推荐,避免 daemon 缓存问题 |
--incremental | 增量编译(默认开启),只编译改动文件 |
--parallel | 并行构建(默认开启),多模块同时编译 |
-d / --debug | 开启 debug 日志,排查构建失败 |
--stacktrace | 打印完整异常堆栈 |
--max-old-space-size=12345 | 调大 Node.js 内存(OOM 时使用) |
--analyze=normal | 生成构建任务耗时分析 |
| 配置项 | 位置 | 说明 |
|---|---|---|
apiCompatibilityCheck | build-profile.json5 → strictMode | 设置 ArkTS API 兼容性检测级别 |
tsImportSoCheck | build-profile.json5 → tscConfig | 编译时对 .ts 文件中导入 .so 的符号进行类型解析 |
enableSoDirCollection | 模块级 build-profile.json5 → nativeLib | ets 文件能否加载 libs/{ABI}/ 子目录下的 so 文件 |
getAllDependencyInfo() | hvigorfile.ts | 获取工程或模块下所有依赖信息 |
syncNative | DevEco Studio Settings 开关 | 提升 sync 阶段 C++ 编译效率 |
| 工具 | API 26 版本 | 说明 |
|---|---|---|
| Command Line | 26.0.0.461 | 命令行工具集 |
| codelinter | 6.0.240 | 代码检查与修复 |
| hstack | 6.0.0 | release 混淆堆栈还原工具 |
| hvigorw | 6.26.1 | 编译构建(API 10+ 支持) |
| ohpm | 26.0.0.410 | 包管理 |
| Node.js | 24.14.1 | 运行时 |
| SDK | 26.0.0 Beta1 | OpenHarmony SDK 26.0.0.23 |
| 级别 | 说明 | 示例 | 申请方式 |
|---|---|---|---|
| normal | 直接授予 | ohos.permission.INTERNET | 在 module.json5 声明即可 |
| system_basic | 弹窗确认 | ohos.permission.LOCATION、ohos.permission.CAMERA | 声明 + 运行时 requestPermissionsFromUser |
| system_core | 限制级 | 系统API,普通应用不可用 | 需 OEM 授权 |
import { abilityAccessCtrl } from '@kit.AbilityKit';
const atManager = abilityAccessCtrl.createAtManager();
// 请求相机权限
atManager.requestPermissionsFromUser(this.context, [
'ohos.permission.CAMERA'
]).then((data) => {
if (data.authResults[0] === 0) { /* 权限已授予 */ }
});
@ohos.net.http 必须用 HTTPS,校验证书源自
@kit.EnterpriseThreatProtectionKit,HarmonyOS 6.1.1 (API 24) 新增。为安全应用提供"穿透沙盒"的全系统文件扫描和隔离处置能力。仅支持 PC/2in1 设备。
| API | 功能 | 说明 |
|---|---|---|
scanBundleFiles(targetType, callback) | 扫描 BUNDLE/EL2 目录 | 分批次回调返回路径 |
openFile(path) | 跨沙盒打开文件 | 获取 FD 供分析引擎深度扫描 |
isolateThreatFile(path) | 隔离风险文件 | 物理转移至加密隔离区 → 返回隔离 ID |
restoreIsolatedFile(id) | 恢复误报文件 | 按 ID 还原至原路径 |
removeIsolatedFile(id) | 物理清除 | 不可逆擦除 |
queryIsolatedFiles(callback) | 查询隔离区 | 获取所有隔离项目列表 |
import { virusRemediation } from '@kit.EnterpriseThreatProtectionKit';
// 1. 全盘扫描(批次回调)
const callback: virusRemediation.ScanCallback = {
onReceive: (paths: string[]) => {
// 分批次接收,避免内存爆炸
scannedPaths.push(...paths);
},
onComplete: () => { /* 扫描完成 */ },
onError: (code, msg) => { /* 错误处理 */ },
};
virusRemediation.scanBundleFiles(
virusRemediation.ScanTargetType.BUNDLE, callback);
// 2. 隔离发现的风险文件
const isolationId = await virusRemediation.isolateThreatFile(path);
// 3. 查询隔离区
virusRemediation.queryIsolatedFiles({
onQuery: (files) => { isolatedItems = files; },
});
// 4. 恢复或清除
await virusRemediation.restoreIsolatedFile(isolationId); // 误报还原
await virusRemediation.removeIsolatedFile(isolationId); // 物理清除
| 要求 | 说明 |
|---|---|
| 权限 | ohos.permission.SCAN_REMEDIATE_VIRUS — 仅企业杀软可申请 |
| 设备 | 仅 PC/2in1 设备,手机/平板不支持 |
| FD 管理 | openFile 获取的 FD 必须及时关闭,防止耗尽配额 |
| 隔离 ID | 应用卸载重装后 ID 映射丢失,需 queryIsolatedFiles 重新同步 |
| BUNDLE 目录 | 包体文件不支持 isolate/remove,需走 addDisallowedRunningBundlesSync 黑名单或 uninstall |
onReceive 分批接收,避免大列表一次性加载canIUse 检测设备能力,非 2in1 设备优雅降级核心机制:默认 STREAM_USAGE_MEDIA 会强制终止其他媒体流。工具类音效应"降级"避免冲突。
import { audio } from '@kit.AudioKit';
// ❌ 错误:默认 MUSIC 会杀后台听书
let rendererOptions: audio.AudioRendererOptions = {
streamInfo: { samplingRate: 44100, channels: 2, sampleFormat: 3, encodingType: 0 },
streamUsage: audio.StreamUsage.STREAM_USAGE_MEDIA, // 独占!
};
// ✅ 方案一:改为 GAME 类型(不打断其他音源)
streamUsage: audio.StreamUsage.STREAM_USAGE_GAME;
// ✅ 方案二:AudioSession 共享模式
let session = await audio.createAudioSession();
await session.setInterruptMode(audio.InterruptMode.SHARED);
await session.activate();
// 绑定 sessionToken 到 AudioRenderer
rendererOptions.sessionToken = session.getToken();
// 监听音频打断
avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => {
if (info.forcePause) { /* 被强制暂停 */ }
});
import { PiPWindow } from '@kit.ArkUI';
// 创建画中画
const pipController = await PiPWindow.create({
config: { /* PiPConfiguration */ }
});
await pipController.startPiP();
// 退后台自动启动 PiP
pipController.setAutoStartEnabled(true);
// 监听状态
pipController.on('stateChange', (state) => {
// STARTED / STOPPED / ERROR
});
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
// AES 对称加密
async function aesEncrypt(data: string, key: Uint8Array): Promise<Uint8Array> {
const symKey = await cryptoFramework.createSymKeyGenerator('AES128')
.convertKey(key);
const cipher = await cryptoFramework.createCipher('AES128|CBC|PKCS7');
const iv = { data: new Uint8Array(16) }; // 16字节初始化向量
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, iv);
const result = await cipher.doFinal({ data: new TextEncoder().encodeInto(data) });
return result.data;
}
// RSA 非对称加密
async function rsaEncrypt(data: string, pubKey: Uint8Array): Promise<Uint8Array> {
const keyPair = await cryptoFramework.createAsyKeyGenerator('RSA2048')
.convertKey(pubKey, null);
const cipher = await cryptoFramework.createCipher('RSA2048|PKCS1');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair);
const result = await cipher.doFinal({ data: new TextEncoder().encodeInto(data) });
return result.data;
}
// SHA256 哈希
async function sha256(data: string): Promise<Uint8Array> {
const md = await cryptoFramework.createMd('SHA256');
await md.update({ data: new TextEncoder().encodeInto(data) });
return (await md.digest()).data;
}
来源:
crypto-collection| Gitee harmonyos_samples | 源码级
| 分类 | 算法 |
|---|---|
| 对称加密 | AES、3DES、SM4 |
| 非对称加密 | RSA、SM2 |
| 签名验签 | RSA、SM2、ECDSA |
| 消息摘要 | SHA256、MD5、SM3 |
| 消息认证码 | HMAC |
entry/src/main/ets/
├── pages/
│ ├── Index.ets # 主页入口
│ ├── EncryptionAndDecryption.ets # 加解密页面
│ ├── SignatureVerification.ets # 签名验签
│ ├── MessageSummary.ets # 消息摘要
│ └── MessageAuthenticationCode.ets # HMAC
├── utils/
│ ├── CryptoUtil.ets # 加解密工具类
│ ├── SignatureUtil.ets # 签名验签工具类
│ └── DataConversion.ets # SM2 格式转换
└── viewmodel/ # ViewModel 层
// [API 23+] cryptoFramework 加解密
import { cryptoFramework } from '@kit.CryptoArchitectureKit'
async function aesEncrypt(plainText: string): Promise<string> {
let generator = cryptoFramework.createSymKeyGenerator('AES128')
let key = await generator.generateSymKey()
let cipher = cryptoFramework.createCipher('AES128|PKCS7')
let iv: cryptoFramework.DataBlob = { data: new Uint8Array(16) }
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, key, iv)
let input: cryptoFramework.DataBlob = { data: new TextEncoder().encode(plainText) }
let output = await cipher.doFinal(input)
return output.data.toString() // Base64 编码后输出
}
SM2 支持 16 进制公私钥与 ASN.1 格式 Base64 编码之间的互转。
来源:
audio-focus| Gitee harmonyos_samples | 源码级
| 场景 | 音频流类型 | 焦点处理 |
|---|---|---|
| 视频播放 | CONTENT_MOVIE | 中断后暂停,恢复后继续 |
| 音乐播放 | CONTENT_MUSIC | 中断后降低音量(闪避) |
| VoIP通话 | VOIP_CALL | 抢占焦点,其他应用暂停 |
// [API 23+] AudioSession 自定义焦点策略
import { audio } from '@kit.AudioKit'
let audioSession = this.audioRenderer.getAudioSession()
audioSession.setAudioSessionDelegator({
onInterrupt: (interruptEvent) => {
switch (interruptEvent.forceType) {
case audio.InterruptForceType.INTERRUPT_FORCE:
// 强制中断:暂停播放
this.pause()
break
case audio.InterruptForceType.INTERRUPT_SHARE:
// 共享中断:降低音量
this.duckVolume()
break
}
return { isPlay: false } // 是否继续播放
}
})
来源:
core-vision-kit-sample-code-ark-ts-ocr-demo| Gitee harmonyos_samples | 源码级
// [API 23+] 通用文字识别
import { textRecognition } from '@hms.ai.ocr'
async function recognizeText(pixelMap: PixelMap): Promise<string> {
let visionInfo: VisionInfo = {
pixelMap: pixelMap,
// 可指定识别语言、识别区域等参数
}
let result = await textRecognition.recognizeText(visionInfo)
return result.text // 识别出的完整文本
}
选择图片(图库/拍照) → 转为 PixelMap → textRecognition.recognizeText() → 展示可复制的文本
来源:
device-security-kit-business-risk| Gitee harmonyos_samples | 源码级
| 检测类型 | API | 用途 |
|---|---|---|
| 涉诈剧本检测 | detectFraudRisk() | 防诈骗识别 |
| 模拟点击检测 | detectSimulatedClickRisk() | 反作弊/反欺诈 |
| 模拟点击增强检测 | detectSimulatedClickRiskEnhanced() | 增强版 + 签名验证 |
// [API 23+] 业务风险检测
import { businessRiskIntelligentDetection } from '@kit.DeviceSecurityKit'
// 涉诈剧本检测
let fraudResult = await businessRiskIntelligentDetection.detectFraudRisk()
// 解析检测结果(建议服务端侧签名验证)
// 模拟点击检测
let clickResult = await businessRiskIntelligentDetection.detectSimulatedClickRisk()
来源:
online-authenticationkit_sample_fido2clientdemo_arkts| Gitee harmonyos_samples
基于 FIDO2 协议的生物认证(指纹/人脸)。
// [API 23+] FIDO2 生物认证
import { onlineAuthentication } from '@kit.OnlineAuthenticationKit'
// 注册生物凭证
let result = await onlineAuthentication.register({
challenge: 'server_challenge',
rp: { name: 'MyApp', id: 'example.com' },
user: { id: 'user123', name: 'user' }
})
// 认证
let authResult = await onlineAuthentication.authenticate({
challenge: 'server_challenge',
rpId: 'example.com'
})
审核要求:AGC 审核会检查无障碍支持,缺失可能导致被拒。
// 基础无障碍标签(每个可交互组件必加)
Button('提交')
.accessibilityText('提交按钮') // 朗读文本
.accessibilityDescription('点击提交表单') // 详细描述
.accessibilityGroup(true) // 标记为无障碍组
// 隐藏装饰性元素(如图标、分割线)
Divider()
.accessibilityLevel('no') // 跳过朗读
// 控制焦点
Text('重要提示')
.focusable(true) // 可获焦
.defaultFocus(true) // 页面默认焦点
| 规则 | 做法 |
|---|---|
| 所有可交互组件加标签 | Button/Input/List/Image 都加 accessibilityText |
| 装饰元素隐藏 | 纯图标/分割线/背景图加 accessibilityLevel('no') |
| 语义化分组 | 相关控件用 accessibilityGroup(true) 包裹 |
| 动态内容通知 | 用 announceForAccessibility(text) 通知屏幕朗读 |
| 颜色对比度 | 文本/背景对比度 ≥ 4.5:1 |
| 触摸目标大小 | 可点击区域 ≥ 44vp × 44vp |
vp 是鸿蒙虚拟像素单位,自动适配不同密度import { BreakpointSystem, BreakpointType } from '@kit.ArkUI';
// 定义断点
const breakpoints = new BreakpointSystem(1);
breakpoints.setParams([
{ name: 'sm', value: 320, files: ['layout/sm'] },
{ name: 'md', value: 600, files: ['layout/md'] },
{ name: 'lg', value: 840, files: ['layout/lg'] }
]);
// 组件内使用
@State currentBreakpoint: string = 'sm';
// 根据 currentBreakpoint 切换 GridCol 列数或布局
| 设备类型 | 断点 | 建议 Grid 列数 |
|---|---|---|
| 手机/折叠屏折叠态 | sm (320~599vp) | 4 |
| 折叠屏展开/小平板 | md (600~839vp) | 8 |
| 平板/桌面 | lg (840~) | 12 |
vp / % / layoutWeight,不用固定 pxobjectFit 控制缩放(cover/contain)LazyForEach 确保长列表性能以下实战模式源自官方 Sample
MultiCommunityApplication(3,700 下载)和MusicHome(4,080 下载),覆盖社区评论和音乐播放类应用的多设备适配全场景。
// 将 NavPathStack 存入 AppStorage,实现跨组件页面跳转
@Entry
@Component
struct Index {
@StorageLink('pageInfos') pageInfos: NavPathStack = new NavPathStack();
aboutToAppear(): void {
this.pageInfos.pushPath({ name: 'mainPage' });
}
// pageMap 的路由分发模式
@Builder
pageMap(name: string, param: object) {
if ('detailPage' === name) {
NavDestination() { DetailPage() }
.onBackPressed(() => {
AppStorage.setOrCreate('isDetailPage', false);
return false;
})
.hideTitleBar(true)
} else if ('pictureDetail' === name) {
NavDestination() { PictureDetail({ index: param as number }) }
.hideTitleBar(true)
} else if ('rankPage' === name) {
NavDestination() { HotRankPage() }
.hideTitleBar(true)
}
}
build() {
Navigation(this.pageInfos)
.hideNavBar(true)
.navDestination(this.pageMap)
}
}
手机端底部水平标签栏 ⇄ 平板端左侧垂直侧边栏,通过断点实时切换。
@Component
export struct TabContentView {
@StorageLink('currentBreakpoint') currentBreakpoint: string = 'sm';
build() {
Tabs({
barPosition: this.currentBreakpoint === 'lg'
? BarPosition.Start // 平板/大屏 → 左侧垂直
: BarPosition.End // 手机 → 底部水平
}) {
TabContent() { HotPointPage() }.tabBar('关注')
TabContent() { FoundPage() }.tabBar('发现')
TabContent() { RankPage() }.tabBar('排行')
}
.vertical(this.currentBreakpoint === 'lg') // LG 断点垂直导航
.barHeight(this.currentBreakpoint === 'lg'
? '50%' : '48vp') // 垂直时全高,水平时固定
.barWidth(this.currentBreakpoint === 'lg'
? '72vp' : '100%') // 垂直时固定宽度
}
}
手机单列 ⇄ 平板双列,通过 columnsTemplate 动态控制。
WaterFlow() {
LazyForEach(this.cardArray, (item: CardItem) => {
FlowItem() {
Column() {
MicroBlogView({ cardItem: item })
CommentBarView({ isShowInput: false })
}
}
})
}
.columnsTemplate(this.currentBreakpoint === 'lg' ? '1fr 1fr' : '1fr')
.rowsGap(this.currentBreakpoint === 'lg' ? 0 : 8)
.nestedScroll({ // 内外滚动协同
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
手机端上下滚动,折叠屏展开态左右分栏,平板端侧边栏评论区。
@Component
struct DetailPage {
@StorageLink('currentBreakpoint') currentBreakpoint: string = 'sm';
@State isFoldHorizontal: boolean = false;
build() {
// 手机/折叠屏:GridRow 栅格分栏
GridRow({ columns: { sm: 4, md: 5, lg: 12 } }) {
GridCol({ span: { sm: 4, md: this.isFoldHorizontal ? 3 : 5, lg: 12 } }) {
MicroBlogView({ cardItem: this.cardItem })
}
GridCol({ span: { sm: 4, md: this.isFoldHorizontal ? 2 : 5, lg: 12 } }) {
CommentListView()
}
}
.visibility(this.currentBreakpoint === 'lg' ? Visibility.None : Visibility.Visible)
// 平板/大屏:SideBarContainer 右侧评论区
SideBarContainer() {
Column() { CommentListView() }
Column() { MicroBlogView({ cardItem: this.cardItem }) }
}
.visibility(this.currentBreakpoint !== 'lg' ? Visibility.None : Visibility.Visible)
}
}
排行榜页头部随滚动产生视差位移,通过 onScrollFrameBegin 实现。
const HEADER_MAX_TOP: number = 52;
const HEADER_MIN_TOP: number = 46;
@State titleMarginTop: number = HEADER_MAX_TOP;
Scroll() {
Column() {
// 标题栏:滚动时 marginTop 在 46~52vp 间渐变
// 副标题在滚动收起时渐隐
this.TitleBar()
.margin({ top: this.titleMarginTop })
// 榜单内容
HotListView()
}
}
.onScrollFrameBegin((offset: number, state: ScrollState) => {
return { offsetRemain: this.calcParallax(offset) };
})
calcParallax(offset: number): number {
if (offset > 0 && this.titleMarginTop > HEADER_MIN_TOP) {
const delta = Math.min(offset, this.titleMarginTop - HEADER_MIN_TOP);
this.titleMarginTop -= delta;
return 0;
}
if (offset < 0 && this.titleMarginTop < HEADER_MAX_TOP) {
const delta = Math.min(Math.abs(offset), HEADER_MAX_TOP - this.titleMarginTop);
this.titleMarginTop += delta;
return 0;
}
return offset;
}
// EntryAbility 中监听窗口尺寸变化
private updateBreakpoint(windowWidth: number): void {
const vp = windowWidth / display.getDefaultDisplaySync().densityPixels;
let bp = 'sm';
if (vp >= 840) bp = 'lg';
else if (vp >= 600) bp = 'md';
AppStorage.setOrCreate('currentBreakpoint', bp);
}
// 注册监听
onWindowStageCreate(windowStage: WindowStage): void {
windowStage.getMainWindow().then((win) => {
const winWidth = win.getWindowProperties().windowRect.width;
this.updateBreakpoint(winWidth);
win.on('windowSizeChange', (size) => this.updateBreakpoint(size.width));
});
}
源自官方 Sample 及华为最佳实践文档,覆盖应用接续、分布式数据对象、跨设备拖拽、碰一碰分享等核心能力。
import { distributedDataObject } from '@kit.ArkData';
import { AbilityConstant, UIAbility } from '@kit.AbilityKit';
// 源端(迁移): onContinue
export default class SourceAbility extends UIAbility {
d_object?: distributedDataObject.DataObject;
async onContinue(wantParam: Record<string, Object>): Promise<AbilityConstant.OnContinueResult> {
// 1. 创建分布式数据对象
const source = { name: 'jack', age: 18, isVis: false };
this.d_object = distributedDataObject.create(this.context, source);
// 2. 生成组网ID
const sessionId = distributedDataObject.genSessionId();
this.d_object.setSessionId(sessionId);
wantParam['dataSessionId'] = sessionId; // 传给对端
// 3. 持久化(确保源端退出后对端仍能获取)
await this.d_object.save(wantParam.targetDevice as string);
return AbilityConstant.OnContinueResult.AGREE;
}
}
// 对端(接收)
export default class TargetAbility extends UIAbility {
d_object?: distributedDataObject.DataObject;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
this.handleContinuation(want);
}
}
handleContinuation(want: Want): void {
const remoteData = { name: undefined, age: undefined, isVis: undefined };
this.d_object = distributedDataObject.create(this.context, remoteData);
// 必须在 setSessionId 之前注册 status 监听
this.d_object.on('status', (sessionId, networkId, status) => {
if (status === 'restored') {
console.info(`restored: ${this.d_object!['name']}`);
}
});
this.d_object.setSessionId(want.parameters?.dataSessionId as string);
}
}
import { commonType } from '@kit.ArkData';
// 源端:写入分布式文件目录 → 创建 Asset → 封装进 DataObject
const filePath = this.context.distributedFilesDir + '/test.txt';
fs.writeSync(fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE).fd, 'content');
const attachment: commonType.Asset = {
name: 'test.txt', uri: fileUri.getUriFromPath(filePath), path: filePath,
createTime: '...', modifyTime: '...', size: '...',
};
this.d_object = distributedDataObject.create(this.context, { attachment });
// 对端:创建空 Asset → 监听 status restored → 读取
const emptyAsset: commonType.Asset = { name: '', uri: '', path: '', createTime: '', modifyTime: '', size: '' };
this.d_object = distributedDataObject.create(this.context, { attachment: emptyAsset });
this.d_object.on('status', (s, n, status) => {
if (status === 'restored') {
const file = this.d_object!['attachment'] as commonType.Asset;
// 可使用 file.uri 读取文件
}
});
| 功能 | API | 适用场景 |
|---|---|---|
| 应用接续 | onContinue + distributedDataObject | 跨设备无缝切换任务 |
| 分布式数据对象 | create() + setSessionId() + on('status') | 跨设备数据实时同步 |
| 跨设备拖拽 | 统一拖拽 API | 平板/2in1 间拖拽文件/文本 |
| 碰一碰分享 | 碰一碰 SDK | 图片/Wi-Fi/文件跨端分享 |
| 隔空传送 | 手势识别 + 跨端传输 | 一抓一放跨端传输 |
| 跨设备剪贴板 | 分布式剪贴板 | A设备复制,B设备粘贴 |
save() 之前调用 setSessionId() 激活setSessionId 之前 注册 status 监听,防止错过 restoredundefined,Asset 需创建空对象ohos.permission.DISTRIBUTED_DATASYNCAsset 类型而非直接传文件路径import { shareController } from '@kit.ShareKit';
// 碰一碰触发分享
shareController.share({
title: '分享图片',
filePath: ['/data/file/test.jpg'],
shareMode: shareController.ShareMode.KNOCK, // 碰一碰模式
});
// form_config.json 配置
{ "name": "GameCard", "src": "./ets/widget/GameCard.ets",
"isDynamic": true, "defaultDimension": "2*2",
"supportDimensions": ["2*2"] }
// GameCard.ets 使用 Canvas 实现小游戏 UI
Canvas(this.context)
.onReady(() => { /* 绘制游戏元素 */ })
.onTouch((event) => { /* 处理交互 */ })
来源:
continue-progress| Gitee harmonyos_samples | 源码级
| 场景 | 接续内容 | 实现 |
|---|---|---|
| 长列表 | List + WaterFlow 的滚动偏移量 | scrollToIndex(continueOffset) |
| 媒体播放 | 播放集数 + 当前进度 | avPlayer.seek(continueTime) |
| Web 浏览 | 滚动位置 | controller.runJavaScript('window.scrollY') |
// [API 23+] 应用接续核心逻辑
import { common } from '@kit.AbilityKit'
class MyAbility extends UIAbility {
onContinue(wantParam: Record<string, Object>): OnContinueResult {
// 1. 保存当前进度
wantParam['continueOffset'] = this.pageOffset
wantParam['playPosition'] = this.avPlayer.currentTime
// 2. 返回 CONTINUE
return OnContinueResult.AGREE
}
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
// 3. 恢复进度(对端)
let offset = want.parameters?.continueOffset as number
if (offset) { this.scrollToIndex(offset) }
}
}
来源:
GesturesShare| Gitee harmonyos_samples | 源码级
基于 Share Kit 的 harmonyShare.on('gesturesShare') 实现"一抓一放"跨设备分享文件和 App Linking 链接。
// [API 23+] 隔空传送监听
import { systemShare } from '@kit.ShareKit'
systemShare.harmonyShare.on('gesturesShare', (event, sharableTarget) => {
// 分享文件
sharableTarget.share({
type: 'file',
uris: ['file://...', 'file://...']
})
// 或分享 App Linking 链接(跳转到应用内指定页面)
// sharableTarget.share({ type: 'link', content: 'https://applink.example.com/video' })
})
来源:
KnockFileShare| Gitee harmonyos_samples | 源码级
// [API 23+] 碰一碰文件分享
import { systemShare } from '@kit.ShareKit'
// 注册碰一碰监听
systemShare.harmonyShare.on('knockShare', (event, sharableTarget) => {
sharableTarget.share({
type: 'file',
uris: selectedFileUris // 勾选的文件列表
})
})
// PC端接收文件监听
systemShare.harmonyShare.on('dataReceive', (event, data) => {
// data 保存至应用沙箱目录
fileIo.copy(data.uri, this.context.filesDir + '/received/')
})
来源:
MultiDeviceCamera| Gitee harmonyos_samples | 源码级
在手机、大折叠、阔折叠、三折叠、平板上实现正常状态和折叠状态切换时的预览旋转、拍照旋转、切换镜头。
| 能力 | API | 说明 |
|---|---|---|
| 相机预览 | CameraManager.createCameraInput + PhotoSession | 多设备预览 |
| 拍照 | PhotoSession.capture | 拍照 + 旋转适配 |
| 切换镜头 | CameraInput.switchCameraInput | 前后摄像头切换 |
| 保存图片 | photoAccessHelper | 保存至图库 |
| 折叠适配 | BreakpointType + WindowUtil | 折叠状态感知 + 旋转补偿 |
| 多设备适配 | BreakpointSystem | 一多断点适配 |
entry/src/main/ets/
├── utils/CameraUtil.ets # 相机工具类
├── utils/BreakpointType.ets # 一多断点
├── utils/WindowUtil.ets # 窗口/折叠感知
├── views/CommonView.ets # 公共视图
└── pages/Index.ets # 主页
// [API 23+] 相机预览 + 折叠适配
import { camera } from '@kit.CameraKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
async function startPreview(context: Context, surfaceId: string) {
let cameraManager = camera.getCameraManager(context)
let cameras = cameraManager.getSupportedCameras()
let cameraInput = cameraManager.createCameraInput(cameras[0])
await cameraInput.open()
let session = cameraManager.createPhotoSession()
session.beginConfig()
session.addInput(cameraInput)
let previewOutput = session.createPreviewOutput(surfaceId)
session.addOutput(previewOutput)
await session.commitConfig()
await session.start()
}
// 折叠状态旋转补偿
// 使用 BreakpointType 监听折叠状态变化
// 通过 WindowUtil 获取当前旋转角度,补偿预览/拍照方向
"ohos.permission.CAMERA"
"ohos.permission.READ_IMAGEVIDEO"
"ohos.permission.WRITE_IMAGEVIDEO"
源自官方 Sample
MusicHome,共 3 个模块,按 commons/features/products 三层架构组织,覆盖手机/折叠屏/平板/手表/智慧屏。
MultiMusicApp/
├── commons/
│ ├── constantsCommon/ # 公共常量(断点、栅格、样式、路由)
│ └── mediaCommon/ # 公共媒体服务(音乐播放、断点系统、数据模型)
├── features/
│ ├── musicList/ # 歌曲列表页(独立模块,含页面/组件/数据)
│ ├── musicComment/ # 音乐评论页(独立复用模块)
│ └── live/ # 直播页(独立模块)
└── products/
├── phone/ # 手机/折叠屏/平板 → 组装所有 features
└── watch/ # 手表 → 只组装部分 features
架构价值:features 层开发一次 → products 层按设备自由组合。手机版修了一个 bug,手表版自动受益。
系统根据父容器宽度自动隐藏低优先级元素,无需手写 if/else。
Row() {
Button('列表').displayPriority(3) // 窄屏时优先隐藏
Button('上一首').displayPriority(2)
Button('播放/暂停').displayPriority(1) // 最优先保留
Button('下一首').displayPriority(2)
Button('更多').displayPriority(3)
}
// 手机 → 只显示核心按钮;平板 → 全部显示
List({ space: 8 }) {
LazyForEach(this.songList, (item: SongItem) => {
ListItem() { SongCard({ song: item }) }
}, (item: SongItem) => item.id)
}
.lanes(this.currentBreakpoint === 'lg' ? 2 : 1) // 平板双列,手机单列
.cachedCount(this.currentBreakpoint === 'lg' ? 8 : 4)
从封面提取主色调做沉浸背景。
import { effectKit } from '@kit.ArkGraphics';
const picker = effectKit.createColorPicker(pixelMap);
const mainColor = await picker.getMainColorSync();
const bgColor = `#${mainColor.rgba.toString(16).padStart(8, '0')}`;
const blur = effectKit.createEffect(pixelMap);
blur.blur(30); // 高斯模糊背景
Swiper() {
Column() { AlbumArt() /* 封面+控制 */ }
Column() { LyricView() /* 歌词 */ }
}
.loop(false).indicator(true).borderRadius(16)
源自官方 Sample
Spatialization及华为最佳实践文档。HarmonyOS 7 (API 26) 核心新特性,全新 HDS 组件系统。
沉浸光感是 HDS(HarmonyOS Design System)提供的全新材质体系,核心能力:
| 特性 | 说明 |
|---|---|
| 通透材质 | 毛玻璃效果,内容可透过组件隐约可见 |
| 渐变模糊 | 标题栏随滑动从透明到模糊平滑过渡 |
| 按压弹性反馈 | 按压时弹性缩放动画 |
| 按压点光源 | 按压时触点位置光晕扩散 |
| 材质流光 | 表面微妙流光效果 |
| 智能反色 | 底层内容颜色冲突时自动调整前景色 |
| 档位 | MaterialLevel | 说明 |
|---|---|---|
| 强 | EXQUISITE | 完整沉浸光感,适合高性能设备 |
| 均衡(默认) | GENTLE | 适度效果,性能与视觉平衡 |
| 弱 | SMOOTH | 轻量级,保留核心特性 |
| 系统自适应 | ADAPTIVE | 推荐模式,系统自动选择最优档位 |
import { hdsMaterial, HdsNavigation, ScrollEffectType } from '@kit.UIDesignKit';
HdsNavigation(this.pathStack) {
// Page content
}
.titleBar({
style: {
scrollEffectOpts: {
enableScrollEffect: true,
scrollEffectType: ScrollEffectType.GRADIENT_BLUR,
},
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE,
},
},
})
import { HdsNavigation, HideMode } from '@kit.UIDesignKit';
HdsNavDestination()
.dynamicHideTitleBar({
hideTitleArea: true,
hideStatusBar: true,
mode: HideMode.SCROLL_UP_TO,
})
.bindToScrollable([this.scroller])
import { HdsTabs, HdsTabsController } from '@kit.UIDesignKit';
HdsTabs({ controller: this.controller }) {
// TabContent...
}
.barOverlap(true) // 悬浮模糊背景
.barPosition(BarPosition.End)
.barFloatingStyle({
barBottomMargin: 36, // 距底部间距
adaptToHandedness: true, // 智感握姿跟随
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE,
},
})
在底部导航中嵌入可折叠迷你控制栏,适合音乐播放等场景。
@Builder
buildMiniBar() {
Row() {
Image($r('app.media.prev')).width(24).height(24)
Image($r('app.media.play')).width(32).height(32)
Image($r('app.media.next')).width(24).height(24)
}
}
HdsTabs()
.barOverlap(true)
.barPosition(BarPosition.End)
.barFloatingStyle({
barBottomMargin: 28,
miniBar: {
miniBarBuilder: () => this.buildMiniBar(),
},
})
import { uiMaterial } from '@kit.ArkUI';
// 对任意组件开启沉浸光感
Row()
.systemMaterial(new uiMaterial.ImmersiveMaterial({
style: uiMaterial.ImmersiveStyle.ULTRA_THIN,
interactive: true,
lightEffect: { color: undefined },
}))
import { hdsMaterial } from '@kit.UIDesignKit';
// 查询设备支持的材质类型,进行优雅降级
const types = hdsMaterial.getSystemMaterialTypes();
if (!types.includes(hdsMaterial.MaterialType.EXQUISITE)) {
// 降级到 GENTLE 或关闭沉浸光感
}
自动识别用户握持姿态,将高频组件动态调整到易操作区。
HdsTabs()
.barFloatingStyle({
adaptToHandedness: true, // 智感握姿跟随:底部栏自动左右偏移
})
| 项目 | 下载量 | GitCode |
|---|---|---|
| Spatialization | — | gitcode.com/HarmonyOS_Samples/Spatialization |
| Kit | 导入方式 | 功能 |
|---|---|---|
| AbilityKit | @kit.AbilityKit | 应用上下文、页面路由、窗口、生命周期 |
| ArkUI | @kit.ArkUI | 声明式UI、组件、布局、手势、动画 |
| NetworkKit | @kit.NetworkKit | HTTP请求、上传下载 |
| ArkData | @kit.ArkData | 数据持久化 Preferences/RDB/KVStore |
| BasicServicesKit | @kit.BasicServicesKit | 弹窗、Toast、剪贴板、公共事件 |
| PushKit | @kit.PushKit | 消息推送、离线推送 |
| PaymentKit | @kit.PaymentKit | 支付、订单交易 |
| MapKit | @kit.MapKit | 地图、POI、路线规划 |
| LocationKit | @kit.LocationKit | 定位、逆地理编码 |
| AccountKit | @kit.AccountKit | 鸿蒙账号、登录授权 |
| NotificationKit | @kit.NotificationKit | 系统通知、状态栏消息 |
| MediaKit | @kit.MediaKit | 多媒体播放 |
| CameraKit | @kit.CameraKit | 相机预览、拍照、录像 |
| CryptoArchitectureKit | @kit.CryptoArchitectureKit | 加解密 AES/RSA/MD5 |
| IAPKit | @kit.IAPKit | 应用内购、订阅 |
| AppLinkingKit | @kit.AppLinkingKit | 应用跳转、DeepLink |
| IPCKit | @kit.IPCKit | 跨进程通信 |
| LocalizationKit | @kit.LocalizationKit | 国际化、多语言 |
| 需求 | 导入哪个 Kit |
|---|---|
| 网络请求 | @kit.NetworkKit |
| 本地数据存储 | @kit.ArkData |
| 用户登录/授权 | @kit.AccountKit |
| 消息推送 | @kit.PushKit |
| 支付 | @kit.PaymentKit |
| 地图 | @kit.MapKit |
| 定位 | @kit.LocationKit |
| 相机拍照 | @kit.CameraKit |
| 加解密 | @kit.CryptoArchitectureKit |
| 国际化和多语言 | @kit.LocalizationKit |
| 通知栏消息 | @kit.NotificationKit |
| 后台任务 | @kit.BackgroundTasksKit |
| 分布式流转 | @kit.DistributedServiceKit |
| AI 模型推理 | @kit.MindSporeLiteKit |
| 扫码 | @kit.ScanKit |
| 文件读写 | @kit.CoreFileKit |
import { pushService } from '@kit.PushKit';
import { notificationManager } from '@kit.NotificationKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
// 1. 获取 Push Token(在 Ability onCreate 中调用)
const pushToken: string = await pushService.getToken();
hilog.info(0x0000, 'Push', 'Token: ' + pushToken);
// ↑ 上报此 token 到你的服务端
// 2. 请求通知权限(onWindowStageCreate 中调用)
await notificationManager.requestEnableNotification(this.context);
// 3. 服务端下发流程(Java 示例):
// a. AGC 下载服务账号密钥文件 private.json
// b. 用 RSA 私钥生成 JWT 鉴权令牌
// c. POST https://push-api.cloud.huawei.com/v1/{projectId}/messages:send
// d. payload 中指定 title/body/clickAction
| 步骤 | 说明 |
|---|---|
| 客户端 | 获取 Push Token + 请求通知权限 |
| 服务端 | 生成 JWT 鉴权令牌 → 调用 Push API 下发消息 |
| 点击处理 | 默认打开首页,可在 AGC 配置自定义跳转页面 |
// 支付流程:客户端发起 → 服务端生成订单 → 客户端调起收银台 → 支付结果回调
import { payment } from '@kit.PaymentKit';
// 调起支付收银台(需要服务端先创建订单获取 orderId)
payment.pay(this.context, {
orderId: 'YOUR_ORDER_ID', // 服务端创建的订单号
publicKey: 'YOUR_PUBLIC_KEY', // 华为支付公钥
amount: 9.90, // 金额
productName: '钻石会员', // 商品名
productDescription: '解锁全部功能',
merchantId: 'YOUR_MERCHANT_ID',
applicationId: 'YOUR_APP_ID',
countryCode: 'CN',
currency: 'CNY',
});
// MapKit 提供地图显示、POI 搜索、路线规划等功能
// AGC 控制台开通 Map Kit 服务后,在 module.json5 配置 apiKey
import { map } from '@kit.MapKit';
// 地图组件
@Component
struct MapPage {
build() {
Column() {
// MapComponent 是 ArkUI 内置地图组件(需配置后可用)
MapComponent()
.width('100%').height('100%')
.zoomLevel(15)
.onMapReady(() => { /* 地图加载完成 */ });
}
}
}
// POI 搜索、逆地理编码使用 @kit.MapKit 下的 geo/geocode API
import { fileIo } from '@kit.CoreFileKit';
// 沙箱路径:context.filesDir / context.cacheDir / context.tempDir
const sandboxPath = this.context.filesDir + '/data.txt';
// 写入文件
const file = fileIo.openSync(sandboxPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
fileIo.writeSync(file.fd, 'Hello HarmonyOS');
fileIo.closeSync(file);
// 读取文件
const file2 = fileIo.openSync(sandboxPath, fileIo.OpenMode.READ_ONLY);
const buf = new ArrayBuffer(1024);
const readLen = fileIo.readSync(file2.fd, buf);
fileIo.closeSync(file2);
const content = new TextDecoder('utf-8').decode(buf.slice(0, readLen));
// 目录遍历
const files = fileIo.listFileSync(this.context.filesDir);
// 文件是否存在
const exists = fileIo.accessSync(sandboxPath);
import { hilog } from '@kit.PerformanceAnalysisKit';
// 格式:hilog.info(domain, tag, format, ...args)
// domain 为自定义十六进制整数(建议 0x0000~0xFFFF)
hilog.info(0x0000, 'MyApp', 'User login: %{public}s', userName);
hilog.warn(0x0000, 'MyApp', 'Network timeout, retrying...');
hilog.error(0x0000, 'MyApp', 'Crash: %{public}s', error.message);
%{private}s,可公开用 %{public}simport { i18n } from '@kit.LocalizationKit';
// 系统会自动根据设备语言加载 resources/base/element/ 下对应语言的字符串
// 资源文件组织:
// resources/
// ├── base/ # 默认(中文)
// │ └── element/
// │ └── string.json → { "hello": "你好" }
// ├── en_US/ # 英文
// │ └── element/
// │ └── string.json → { "hello": "Hello" }
// └── ja_JP/ # 日文
// └── element/
// └── string.json → { "hello": "こんにちは" }
// 代码中使用 $r() 引用:
Text($r('app.string.hello'))
// 或使用资源管理 API:
this.context.resourceManager.getStringSync('app.string.hello');
import { mindSporeLite } from '@kit.MindSporeLiteKit';
// 加载模型(.ms 格式)
const model = mindSporeLite.createModel();
model.loadModel(this.context, 'model.ms');
// 构建输入张量
const inputTensor = model.getInputs()[0];
inputTensor.setData(new Float32Array([...]));
// 推理
model.predict([inputTensor], (err, outputs) => {
const result = outputs[0].getData();
});
// 释放
model.destroy();
import { util } from '@kit.ArkTS';
// 容器类
let list = new util.ArrayList<string>(); // 动态数组
list.add('a'); list.get(0); list.removeAt(0);
let map = new util.HashMap<string, number>(); // 哈希表
map.set('key', 1); map.get('key');
let queue = new util.Deque<string>(); // 双端队列
queue.insertFront('a'); queue.removeEnd();
// 编码转换
const encoder = new util.TextEncoder();
const uint8 = encoder.encodeInto('你好'); // 字符串 → Uint8Array
const decoder = new util.TextDecoder('utf-8');
const str = decoder.decodeToString(uint8); // Uint8Array → 字符串
// Base64
const base64 = new util.Base64();
const encoded = base64.encodeToString(new Uint8Array([1,2,3]));
const decoded = base64.decodeSync(encoded);
// 随机数
const rand = new util.Random();
const num = rand.nextInt(); // 随机整数
const bool = rand.nextBoolean(); // 随机布尔
const range = rand.nextUint32(); // 0~4294967295
// 字符串工具
util.printf('%s is %d', 'age', 25); // 格式化 → "age is 25"
util.printf('%s', JSON.stringify(obj));
// 日期格式化(使用 Intl)
import { Intl } from '@kit.LocalizationKit';
const dtf = new Intl.DateTimeFormat('zh-CN', {
dateStyle: 'long', timeStyle: 'short'
});
console.info(dtf.format(new Date())); // 2026年6月23日 18:30
// 拍照流程(CameraKit)
import { camera } from '@kit.CameraKit';
// 获取相机管理器
const cameraManager = camera.getCameraManager(this.context);
// 获取相机设备
const cameras = cameraManager.getSupportedCameras();
// 创建输入
const cameraInput = cameraManager.createCameraInput(cameras[0]);
// 创建输出(拍照)
const photoOutput = cameraManager.createPhotoOutput();
// 创建会话
const session = cameraManager.createCaptureSession();
session.beginConfig();
session.addInput(cameraInput);
session.addOutput(photoOutput);
session.commitConfig();
session.start();
// 拍照
photoOutput.capture();
// 音频播放
import { audio } from '@kit.AudioKit';
const audioPlayer = audio.createAudioPlayer();
audioPlayer.src = this.context.filesDir + '/music.mp3';
await audioPlayer.play();
// 音视频编码(AVCodecKit)
import { avCodec } from '@kit.AVCodecKit';
// 创建编码器
const encoder = avCodec.createVideoEncoder();
// 配置编码参数(分辨率/帧率/码率)
// 输入原始帧 → 输出编码后的 H.264/H.265 数据
来源:华为官方文档 + 51CTO 实战文章,完整覆盖 Media Kit 7 大模块 + CameraKit 全流程
| 模块 | 功能 | 关键类 | 使用场景 |
|---|---|---|---|
| AVPlayer | 音视频播放 | media.AVPlayer | 音乐播放器、视频播放 |
| SoundPool | 短音频播放 | media.SoundPool | 通知音效、相机快门、按键音 |
| AVRecorder | 音视频录制 | media.AVRecorder | 录音、录像 |
| AVScreenCapture | 屏幕录制 | media.AVScreenCapture | 录屏、直播推流 |
| AVMetadataExtractor | 元数据提取 | media.AVMetadataExtractor | 获取标题/艺术家/时长 |
| AVImageGenerator | 视频缩略图 | media.AVImageGenerator | 视频封面图 |
| AVTranscoder | 视频转码 | media.AVTranscoder | HDR→SDR、降分辨率 |
import { media } from '@kit.MediaKit';
// 创建 AVPlayer
const avPlayer = await media.createAVPlayer();
// 状态机驱动(必须监听状态变化)
avPlayer.on('stateChange', async (state) => {
switch (state) {
case 'idle': // 初始态 → 设置资源
avPlayer.url = 'https://example.com/video.mp4'; // 网络URL
// 或: avPlayer.fdSrc = fd; // 本地fd
break;
case 'initialized': // 资源已加载 → 设置Surface
avPlayer.surfaceId = surfaceId; // XComponent的surfaceId
break;
case 'prepared': // 准备完毕 → 可以播放
await avPlayer.play();
break;
case 'playing': // 播放中 → 可以暂停/停止
break;
case 'paused': // 已暂停 → 可以恢复
await avPlayer.play();
break;
case 'completed': // 播放完成 → 重播或释放
await avPlayer.seek(0); // 回到开头
await avPlayer.play();
break;
case 'stopped': // 已停止 → 重新prepare
await avPlayer.prepare();
break;
case 'released': // 已释放 → 不可复用
break;
}
});
// 监听关键事件
avPlayer.on('error', (err) => { console.error(`播放错误: ${err.message}`); });
avPlayer.on('timeUpdate', (time) => { this.currentTime = time; });
// 释放资源(页面退出时必须调用)
aboutToDisappear() { avPlayer.release(); }
支持格式:mp4(H264/H265+AAC)、mkv、mpeg-ts、mp3、flac、wav、ogg、amr 支持协议:本地fd、http/https、HLS、HTTP-FLV、DASH
import { media } from '@kit.MediaKit';
const soundPool = await media.createSoundPool(5, media.SoundPoolStreamType.STREAM_MUSIC);
// 加载音效(返回 soundID)
const soundId = await soundPool.load('/data/notify.mp3');
// 播放(返回 streamID)
const streamID = soundPool.play(soundId, 1.0, 1.0, 0, 0, 1.0);
// 停止/卸载
soundPool.stop(streamID);
soundPool.unload(soundId);
soundPool.release();
⚠️ 注意:SoundPool 适合 ≤ 30秒短音效,长时间音频请用 AVPlayer。
import { media } from '@kit.MediaKit';
const avRecorder = await media.createAVRecorder();
avRecorder.on('stateChange', async (state) => {
switch (state) {
case 'idle':
// 配置录制参数
avRecorder.configure({
audioSourceType: media.AudioSourceType.AUDIO_MIC,
videoSourceType: media.VideoSourceType.VIDEO_SURFACE_YUV,
profile: {
audio: { codec: media.CodecType.AUDIO_AAC, bitrate: 48000, sampleRate: 48000 },
video: { codec: media.CodecType.VIDEO_HEVC, bitrate: 2000000, frameRate: 30, width: 1920, height: 1080 }
},
url: `${context.filesDir}/recording.mp4`,
rotation: 0
});
break;
case 'prepared':
await avRecorder.start();
break;
case 'started':
// 录制中... 可以暂停
break;
}
});
await avRecorder.prepare(); // 触发状态机
// 释放
avRecorder.release();
录制格式:MP4(H265/H264+AAC)、M4A(AAC)、MP3、WAV、AMR
import { media } from '@kit.MediaKit';
const transcoder = await media.createAVTranscoder();
transcoder.configure({
srcFdSrc: { fd: sourceFd }, // 源视频
destFdSrc: { fd: destFd }, // 输出文件
destAudioCodec: media.CodecType.AUDIO_AAC, // 目标音频编码
destVideoCodec: media.CodecType.VIDEO_HEVC, // 目标视频编码
destBitrate: 1000000, // 目标码率
destWidth: 1280, // 目标宽度(不能大于源)
destHeight: 720, // 目标高度(不能奇数)
destContainerFormat: media.ContainerFormatType.CFT_MP4
});
await transcoder.start();
transcoder.on('progress', (progress) => { console.info(`转码进度: ${progress}%`); });
transcoder.on('completed', () => { transcoder.release(); });
⚠️ 约束:目标宽高不能大于源,不能为奇数;不支持字幕轨;仅输出1条视频轨+1条音频轨
import { camera } from '@kit.CameraKit';
import { common } from '@kit.AbilityKit';
// 架构:CameraManager → CameraInput + PreviewOutput + PhotoOutput → Session
class CameraHelper {
private cameraManager: camera.CameraManager | null = null;
private photoSession: camera.PhotoSession | null = null;
private surfaceId: string = '';
// 初始化相机
async init(context: common.Context, surfaceId: string) {
this.surfaceId = surfaceId;
this.cameraManager = camera.getCameraManager(context);
// 选择后置摄像头
const cameras = this.cameraManager.getSupportedCameras();
const backCamera = cameras.find(c =>
c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK
)!;
const cameraInput = this.cameraManager.createCameraInput(backCamera);
await cameraInput.open();
// 获取输出能力 → 选择分辨率
const outputCap = this.cameraManager.getSupportedOutputCapability(backCamera);
const previewProfile = outputCap.previewProfiles.find(p =>
p.size.width === 1920 && p.size.height === 1080
) || outputCap.previewProfiles[0];
const photoProfile = outputCap.photoProfiles.sort(
(a, b) => b.size.width * b.size.height - a.size.width * a.size.height
)[0];
const previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.surfaceId);
const photoOutput = this.cameraManager.createPhotoOutput(photoProfile);
// 创建 PhotoSession
this.photoSession = this.cameraManager.createSession(
camera.SceneMode.NORMAL_PHOTO
) as camera.PhotoSession;
this.photoSession.beginConfig();
this.photoSession.addInput(cameraInput);
this.photoSession.addOutput(previewOutput);
this.photoSession.addOutput(photoOutput);
await this.photoSession.commitConfig();
await this.photoSession.start();
}
// 拍照
async takePhoto() {
if (!this.photoSession) return;
this.photoSession.on('photoAvailable', (photo: camera.Photo) => {
photo.main.getBuffer().then((buffer: ArrayBuffer) => {
this.savePhoto(buffer);
photo.main.release(); // ⚠️ 必须 release,否则内存泄漏!
});
});
this.photoSession.capture({
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
rotation: camera.ImageRotation.ROTATION_0
});
}
// 闪光灯
setFlash(mode: 'auto' | 'on' | 'off') {
if (!this.photoSession?.hasFlash()) return;
const flashMap = {
'auto': camera.FlashMode.FLASH_MODE_AUTO,
'on': camera.FlashMode.FLASH_MODE_ALWAYS_OPEN,
'off': camera.FlashMode.FLASH_MODE_CLOSE
};
this.photoSession.setFlashMode(flashMap[mode]);
}
// 点击对焦(归一化坐标 0~1)
focusOnPoint(x: number, y: number) {
if (!this.photoSession) return;
this.photoSession.setFocusPoint({ x, y });
this.photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);
}
// 变焦
setZoom(ratio: number) {
if (!this.photoSession) return;
const [min, max] = this.photoSession.getZoomRatioRange();
this.photoSession.setZoomRatio(Math.max(min, Math.min(max, ratio)));
}
// 释放资源(页面退出时必须调用)
async release() {
await this.photoSession?.stop();
await this.photoSession?.release();
this.photoSession = null;
}
}
⚠️ CameraKit 关键踩坑:
XComponent.type 必须为 SURFACE,否则没有 surfaceIdPhoto 对象必须 release(),连拍几张不释放会导致内存泄漏SceneMode 要和功能匹配:拍照用 NORMAL_PHOTO,录像用 NORMAL_VIDEO// BLE 扫描
import { ble } from '@ohos.bluetooth';
// 扫描设备
ble.startBLEScan([{ serviceUuid: '0000180F-...' }], { interval: 500 });
ble.on('BLEDeviceFind', (result) => {
console.info('发现 BLE 设备:', result.deviceId, result.rssi);
});
ble.stopBLEScan();
// BLE 连接
const device = ble.createGattClientDevice(result.deviceId);
device.on('BLEConnectionStateChange', (state) => {
if (state.state === 'connected') { /* 已连接 */ }
});
device.connect();
// NFC(Tag 读写)
import { tag } from '@ohos.nfc.tag';
tag.on('tag', (tagInfo) => {
const tagId = tagInfo.tagId; // NFC 标签 ID
// 读取 NDEF 数据
});
来源:
NFCTag| Gitee harmonyos_samples | 源码级
亮屏解锁状态下,设备触碰 NFC 标签后读取数据并跳转应用。
| 模式 | 触发条件 | 行为 |
|---|---|---|
| 前台读卡 | 应用已打开在前台 | 直接分发给当前应用,跳转详情页展示标签技术类型和信息 |
| 后台读卡 | 未打开应用 | 根据技术类型匹配应用;匹配多个→弹出选择器;匹配一个→直接打开 |
ohos.permission.NFC_TAG // 允许应用读取 Tag 卡片
entry/src/main/ets/
├── entryability/EntryAbility.ets
├── pages/Index.ets // 首页
└── view/ReadNFCTag.ets // 展示 NFC 信息页面
来源:
audio-interaction| ⭐22 Stars | Gitee harmonyos_samples | 源码级
| 能力 | API | 说明 |
|---|---|---|
| 音频播放 | AudioRenderer | PCM 音频数据播放 |
| 播控中心交互 | AVSession | 媒体会话控制 |
| 后台播放 | BackgroundUtil + KEEP_BACKGROUND_RUNNING | Service Ability 保活 |
| 焦点管理 | AudioRendererController.interrupt | 焦点打断策略适配 |
| 路由切换 | audioRoutingManager | 切换发声/输出设备 |
| 歌词同步 | LyricsComponent | 歌单数据绑定 |
├── entry/src/main/ets/
│ ├── components/
│ │ ├── ControlAreaComponent.ets # 音频操控区
│ │ ├── LyricsComponent.ets # 歌词组件
│ │ ├── MusicInfoComponent.ets # 音乐内容
│ │ ├── PlayerInfoComponent.ets # 播控内容区
│ │ └── TopAreaComponent.ets # 顶部区域
│ └── dataSource/
│ ├── SongDataSource.ets # 歌曲数据
│ └── SongListData.ets # 歌曲列表
├── MediaService/src/main/ets/
│ ├── songDataController/
│ │ ├── PlayerData.ets
│ │ ├── SongData.ets
│ │ └── SongItemBuilder.ets
│ └── utils/
│ ├── AudioRendererController.ets # 音频播放控制核心
│ ├── AVSessionController.ets # 媒体会话控制
│ ├── BackgroundUtil.ets # 后台播放
│ └── MediaTools.ets # 媒体数据转换
"ohos.permission.KEEP_BACKGROUND_RUNNING" // 后台任务
来源:
audio-codec| Gitee harmonyos_samples | 源码级
基于 OH_AVCodec Native 能力实现音频播放、转码、录制。
播放: 本地音频 → 解封装(Demuxer) → 解码(Decoder) → 播放(AudioRenderer)
转码: 本地音频 → 解封装 → 解码 → 编码(Encoder) → 封装(Muxer) → 保存
录制: 麦克风采集(AudioCapturer) → 编码 → 封装 → 保存
"ohos.permission.MICROPHONE"
来源:
media-provider| Gitee harmonyos_samples | 源码级
// [API 23+] AVSession 双向交互
import { avSession } from '@ohos.multimedia.avsession'
let session = await avSession.createAVSession(context, 'tag', 'audio')
await session.activate()
session.setAVMetadata({ title: 'Song', artist: 'Singer', duration: 300000 })
session.setAVPlaybackState({ state: avSession.PlaybackState.PLAYING })
session.on('play', () => { this.resume() })
session.on('pause', () => { this.pause() })
来源:
upload-and-down-load| Gitee harmonyos_samples | 源码级
使用 @ohos.request 接口创建上传和下载任务,hfs 作为服务器。
// [API 23+] @ohos.request 上传
import { request } from '@kit.BasicServicesKit'
let uploadTask = await request.agent.create(this.context, [fileUri], {
url: 'http://server/upload',
method: 'POST', title: '上传', data: { key: 'value' }
})
uploadTask.on('progress', (info) => { /* 上传进度 */ })
uploadTask.start()
// [API 23+] @ohos.request 下载
let downloadTask = await request.agent.create(this.context, {
url: 'http://server/file.zip',
saveas: './downloads/file.zip', title: '下载'
})
downloadTask.on('progress', (info) => { /* 下载进度 */ })
downloadTask.start()
downloadTask.pause() // 暂停
downloadTask.resume() // 恢复
来源:
web-application-jump| Gitee harmonyos_samples | 源码级
| 场景 | 实现 |
|---|---|
| Web→原生ArkTS页面 | onLoadIntercept 拦截 + Navigation pushPath |
| Web→Web页面 | 前端 a 标签 href 配置 |
| Web→三方应用 | onLoadIntercept + Want 隐式拉起 + module.json5 exported/entities/actions |
| Web→系统应用 | onLoadIntercept + Want 配置系统应用参数 + startAbility |
| Web→应用市场 | onLoadIntercept + StoreKit loadProduct |
| Web→跨设备应用 | onLoadIntercept + getAvailableDeviceListSync + Want deviceId |
// [API 23+] onLoadIntercept 拦截拉起
Web({ src: 'https://example.com', controller: this.webController })
.onLoadIntercept((event) => {
if (event.data.url.startsWith('native://')) {
// 跳转到原生页面
this.navPathStack.pushPath({ name: 'OriginPage' })
return true
}
if (event.data.url.startsWith('thirdparty://')) {
// 隐式拉起三方应用
let want: Want = {
action: 'ACTION_VIEW',
entities: ['entity.system.home'],
uri: event.data.url
}
this.context.startAbility(want)
return true
}
return false
})
来源:
WebCrossDomain| Gitee harmonyos_samples | 源码级
| 场景 | 核心技术 | 说明 |
|---|---|---|
| 本地资源跨域 | setPathAllowingUniversalAccess() | file协议跨域访问本地文件 |
| 远程请求跨域 | WebSchemeHandler拦截 + rcp代理 | 代理转发 + 跨域响应头 |
| 跨域Cookies | putAcceptCookieEnabled + configCookieSync | A域名Cookie传给B域名 |
| 自定义协议跨域 | WebSchemeHandler拦截 + 系统能力 | 拦截自定义协议弹提示 |
// [API 23+] WebSchemeHandler 跨域代理
Web({ src: 'https://example.com', controller: this.controller })
.onLoadIntercept((event) => {
// 方法1: 设置跨域路径白名单
// this.controller.setPathAllowingUniversalAccess(['/data/storage/el2/base/files/'])
// 方法2: 注册 scheme 拦截器
// web.WebSchemeHandler 实现 onRequestStart 回调
// 使用 rcp.fetch() 作为代理请求转发
return false
})
// [API 23+] Cookies 跨域设置
import { webview } from '@kit.ArkWeb'
let cookieMgr = webview.WebCookieManager.getInstance()
cookieMgr.putAcceptCookieEnabled(true)
// 获取 A 域名的 cookies
let cookies = cookieMgr.fetchCookieSync('https://domain-a.com')
// 设置到 B 域名
cookieMgr.configCookieSync('https://domain-b.com', cookies)
来源:
audio-in-ear-monitor| Gitee harmonyos_samples | 源码级
| 方案 | API | 耳机支持 | 层级 |
|---|---|---|---|
| AudioLoopback | createAudioLoopback().enable() | 有线耳机 | ArkTS |
| Native 采集+渲染 | OH_AudioCapturer_Start + OH_AudioRenderer_Start | 有线+蓝牙 | Native C++ |
// [API 23+] AudioLoopback 耳返
import { audio } from '@kit.AudioKit'
// 1. 查询支持
let supported = audio.isAudioLoopbackSupported()
if (supported) {
let loopback = audio.createAudioLoopback()
// 2. 开启耳返
loopback.enable(true)
// 3. 调节音量
loopback.setVolume(0.8)
// 4. 关闭耳返
// loopback.enable(false)
}
OH_AudioStreamBuilder_Create 创建采集器 + 渲染器来源:
picker| Gitee harmonyos_samples | 源码级
| Picker | API | 功能 |
|---|---|---|
| 文档选择 | DocumentViewPicker.select() | 选择文档 |
| 文档保存 | DocumentViewPicker.save() | 保存文件 |
| 图片/视频 | PhotoViewPicker.select() | 从图库选择图片或视频 |
| 文件读写 | fs.openSync/fs.writeSync/fs.readSync | 编辑+保存文档内容 |
// [API 23+] 文件选择器用法
import { picker } from '@kit.CoreFileKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
// 文档选择
let documentPicker = new picker.DocumentViewPicker()
let docs = await documentPicker.select({ maxSelectNumber: 1 })
// 图片选择
let photoPicker = new photoAccessHelper.PhotoViewPicker()
let photos = await photoPicker.select({ maxSelectNumber: 3, MIMEType: picker.PickerMediaType.IMAGE })
// 文件读写
import { fileIo } from '@kit.CoreFileKit'
let file = fileIo.openSync(docs[0], fileIo.OpenMode.READ_WRITE)
let buf = new ArrayBuffer(1024)
fileIo.readSync(file.fd, buf)
fileIo.writeSync(file.fd, new TextEncoder().encode('new content'))
fileIo.closeSync(file)
来源:
VideoCast| Gitee harmonyos_samples | 源码级
| 场景 | 说明 |
|---|---|
| 本端播放 | AVPlayer 本地播放,进度/音量/集数控制 |
| 播控中心控制 | AVSession 双向交互,播控中心操作本端 |
| 投播远端 | 系统投播能力,本端控制远端设备 |
controller/
├── VideoPlayerController.ets # 本地播放控制
├── VideoSessionController.ets # 播控中心控制
└── VideoCastController.ets # 投播远端控制
来源:
location-service| Gitee harmonyos_samples | 源码级
| 模式 | API | 说明 |
|---|---|---|
| 缓存位置 | geoLocationManager.getLastLocation() | 获取最后已知位置 |
| 当前位置 | geoLocationManager.getCurrentLocation() | 单次获取当前位置 |
| 持续定位 | geoLocationManager.on('locationChange') | 持续监听位置变化 |
| 后台定位 | backgroundTaskManager.startBackgroundRunning + on | 退后台持续定位 |
// [API 23+] 位置服务
import { geoLocationManager } from '@kit.LocationKit'
import { backgroundTaskManager } from '@kit.BackgroundTasksKit'
// 获取当前位置
let location = await geoLocationManager.getCurrentLocation()
// 持续监听
geoLocationManager.on('locationChange', (location) => {
this.latitude = location.latitude
this.longitude = location.longitude
})
// 后台定位(需申请长时任务)
await backgroundTaskManager.startBackgroundRunning(context,
backgroundTaskManager.BackgroundMode.LOCATION)
"ohos.permission.LOCATION"
"ohos.permission.APPROXIMATELY_LOCATION"
"ohos.permission.LOCATION_IN_BACKGROUND"
"ohos.permission.KEEP_BACKGROUND_RUNNING"
来源:51CTO 实战 + 华为官方文档,API 26 网络层全面升级
| 升级项 | 说明 | 性能提升 |
|---|---|---|
| Cronet 引擎 | 底层网络引擎开放深度定制 | 连接池复用,减少握手 |
| Brotli 压缩 | C++层零拷贝解压 | 文本体积再压缩15~25% |
| HTTP/3 (QUIC) | UDP协议,0-RTT握手 | 弱网首屏提速40% |
import { http } from '@kit.NetworkKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
export class HttpClientManager {
private static client = http.createHttp();
static async requestOptimized<T>(url: string, params?: Record<string, string>): Promise<T> {
const options: http.HttpRequestOptions = {
method: http.RequestMethod.GET,
// ✅ 核心1:开启 HTTP/3(QUIC 协议)
// UDP不通时毫秒级自动回退HTTP/2,业务层无感知
usingHttp3: true,
readTimeout: 10000, // 弱网宽容10秒
connectTimeout: 5000,
// ✅ 核心2:声明接受 Brotli 压缩
// 底层C++引擎零拷贝解压,比gzip再省15~25%
header: {
'Accept-Encoding': 'br, gzip, deflate',
'Content-Type': 'application/json'
},
// ✅ 核心3:返回 ArrayBuffer(零拷贝)
// 避免 string 返回的字符集转换与内存拷贝
expectDataType: http.HttpDataType.ARRAY_BUFFER
};
try {
const response = await this.client.request(url, options);
if (response.responseCode === 200) {
const buffer = response.result as ArrayBuffer;
const decoder = new util.TextDecoder('utf-8');
const jsonStr = decoder.decodeWithStream(new Uint8Array(buffer));
return JSON.parse(jsonStr) as T;
}
throw new Error(`Server error: ${response.responseCode}`);
} catch (err) {
const error = err as BusinessError;
console.error(`网络熔断: code=${error.code}, msg=${error.message}`);
throw error;
}
}
}
import { webSocket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class WebSocketClient {
private ws: webSocket.WebSocket | null = null;
private reconnectAttempts = 0;
private readonly maxReconnect = 5;
private readonly baseDelay = 2000; // 退避基准
connect(url: string): void {
this.ws = webSocket.createWebSocket();
// ✅ 核心:配置内核级心跳代理
// Ping/Pong在Native线程池执行,不阻塞ArkTS主线程
const options: webSocket.WebSocketRequestOptions = {
pingInterval: 15000, // ⚠️ 必须 ≥ 10000ms,推荐 15~30秒
header: { 'Origin': 'https://example.com' }
};
this.ws.on('open', () => {
this.reconnectAttempts = 0; // 重置退避计数
});
this.ws.on('message', (err, value) => { /* 处理推送数据 */ });
this.ws.on('error', () => { this.handleReconnect(); });
this.ws.on('close', () => { /* 连接断开 */ });
this.ws.connect(url, options, (err: BusinessError) => {
if (err) this.handleReconnect();
});
}
// 指数退避重连(防雪崩)
private handleReconnect(): void {
if (this.reconnectAttempts >= this.maxReconnect) {
console.error('达到最大重试上限');
return;
}
const delay = this.baseDelay * Math.pow(2, this.reconnectAttempts) + Math.random() * 1000;
this.reconnectAttempts++;
setTimeout(() => this.connect(this.url!), delay);
}
close(): void {
this.ws?.close({ code: 1000, reason: 'Client close' });
this.ws = null;
}
}
| 坑 | 现象 | 解决 |
|---|---|---|
| HTTP/3 UDP端口封锁 | 部分办公网/运营商拦截UDP443 | 核心接口仍用HTTP/2,图片/日志接口放开HTTP/3 |
| Brotli自定义字典 | 非API26设备解压段错误 | 只用默认静态字典,不跨版本激进使用自定义字典 |
| pingInterval过小 | 手机射频模块高功耗 | ≥ 10秒,推荐15~30秒 |
| ArrayBuffer未设 | 返回string导致字符集转换 | 设置 expectDataType: ARRAY_BUFFER |
| WebSocket假死 | 后台/死角时连接看似存活 | 依赖 pingInterval 底层探测Pong超时 |
| 弱网竞速回退 | UDP不通时HTTP/3反而慢300ms | 服务端配 Alt-Svc 头,底层自动竞速回退 |
来源:华为官方文档 + 51CTO 实战文章,完整覆盖 SensorServiceKit
| 传感器 | SensorId | 权限 | 权限级别 | 返回数据 |
|---|---|---|---|---|
| 加速度计 | ACCELEROMETER | ohos.permission.ACCELEROMETER | normal | x/y/z (m/s²) |
| 陀螺仪 | GYROSCOPE | ohos.permission.GYROSCOPE | normal | x/y/z (rad/s) |
| 磁场 | MAGNETIC_FIELD | — | — | x/y/z (μT) |
| 方向(融合) | ORIENTATION | — | — | 方位角/俯仰/翻滚(°) |
| 环境光 | AMBIENT_LIGHT | — | — | intensity (lux) |
| 气压计 | BAROMETER | — | — | pressure (hPa) |
| 计步器 | PEDOMETER | ohos.permission.ACTIVITY_MOTION | normal | steps |
| 心率 | HEART_RATE | ohos.permission.READ_HEALTH_DATA | user_grant | bpm |
| 振动马达 | — | ohos.permission.VIBRATE | normal | — |
| 操作 | 接口 | 备注 |
|---|---|---|
| 持续订阅 | sensor.on(SensorId, cb, { interval }) | interval单位纳秒! |
| 一次获取 | sensor.once(SensorId, cb) | 无需手动off |
| 取消订阅 | sensor.off(SensorId) | 必须在页面销毁时调用 |
| 设备上下线 | sensor.on('sensorStatusChange', cb) | 动态传感器必须监听 |
| 基础振动 | vibrator.startVibration({type:'time',duration}, attrs) | 两个参数必填 |
| 预设振动 | vibrator.startVibration({type:'preset',effectId}, attrs) | 需硬件支持 |
| 传感器 | 最小值 | 最大值 | 推荐 |
|---|---|---|---|
| 加速度/陀螺仪/磁场 | 5,000,000 (5ms) | 200,000,000 (200ms) | 200ms = 200000000 |
| 计步器 | — | — | 不需interval |
常见错误:interval: 200 → 200纳秒=0.0002毫秒,回调几乎不触发!
import { sensor, vibrator } from '@kit.SensorServiceKit';
// 1. 加速度计(最常用)
sensor.on(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
console.info(`X:${data.x.toFixed(3)} Y:${data.y.toFixed(3)} Z:${data.z.toFixed(3)}`);
}, { interval: 200000000 });
// 2. 陀螺仪(旋转角速度)
sensor.on(sensor.SensorId.GYROSCOPE, (data: sensor.GyroscopeResponse) => {
console.info(`角速度 X:${data.x.toFixed(4)} Y:${data.y.toFixed(4)} Z:${data.z.toFixed(4)}`);
}, { interval: 200000000 });
// 3. 方向传感器(⚠️ 指南针请用ORIENTATION,不用MAGNETIC_FIELD)
sensor.on(sensor.SensorId.ORIENTATION, (data: sensor.OrientationResponse) => {
console.info(`方位角:${data.x}° 俯仰:${data.y}° 翻滚:${data.z}°`);
}, { interval: 200000000 });
// 4. 环境光 + 气压计
sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data: sensor.LightResponse) => {
console.info(`光照: ${data.intensity} lux`);
}, { interval: 200000000 });
sensor.on(sensor.SensorId.BAROMETER, (data: sensor.BarometerResponse) => {
const alt = 44330 * (1 - Math.pow(data.pressure / 1013.25, 0.1903));
console.info(`气压:${data.pressure}hPa 海拔:${alt.toFixed(0)}m`);
}, { interval: 200000000 });
// 5. 计步器
sensor.on(sensor.SensorId.PEDOMETER, (data: sensor.PedometerResponse) => {
console.info(`步数: ${data.steps}`);
}, { interval: 200000000 });
// 6. 一次性获取
sensor.once(sensor.SensorId.ACCELEROMETER, (data) => {
console.info(`一次获取: X=${data.x.toFixed(3)}`);
});
class ShakeDetector {
private lastTime = 0;
private lastX = 0; private lastY = 0; private lastZ = 0;
private threshold = 15; // 加速度变化阈值 m/s²
private minInterval = 1000; // 防抖间隔 ms
private callback: (() => void) | null = null;
start(onShake: () => void) {
this.callback = onShake;
sensor.on(sensor.SensorId.ACCELEROMETER, (data) => {
const delta = Math.abs(data.x - this.lastX) + Math.abs(data.y - this.lastY) + Math.abs(data.z - this.lastZ);
const now = Date.now();
if (delta > this.threshold && (now - this.lastTime) > this.minInterval) {
this.lastTime = now;
this.callback?.();
}
this.lastX = data.x; this.lastY = data.y; this.lastZ = data.z;
}, { interval: 100000000 });
}
stop() { sensor.off(sensor.SensorId.ACCELEROMETER); this.callback = null; }
}
// 基础振动(按时长)
vibrator.startVibration({ type: 'time', duration: 100 }, { id: 0, usage: 'unknown' });
// 预设效果振动
vibrator.startVibration({ type: 'preset', effectId: 'haptic.effectId.clockTimer' }, { id: 0, usage: 'unknown' });
// 停止振动
vibrator.stopVibration();
⚠️ startVibration 两个参数都必填!振动属性 { id, usage } 不能省略。
| 坑 | 现象 | 解决 |
|---|---|---|
| interval单位错误 | 回调几乎不触发 | 200ms写200000000纳秒 |
| 忘记off | 切后台疯狂耗电 | aboutToDisappear中全量off |
| 多次on叠加回调 | 数据重复触发 | 重新订阅前先off |
| 振动参数不全 | 编译类型不匹配 | 两个参数都必填 |
| 动态传感器下线 | 内存泄漏/持续报错 | 监听sensorStatusChange下线时off |
| 磁力计直接算角度 | 角度跳变严重 | 用ORIENTATION融合传感器 |
从华为官方 Sample + 社区实战文章提炼,每个案例含架构图 + 关键代码 + 踩坑要点
import { media } from '@kit.MediaKit';
import { avSession } from '@kit.AVSessionKit';
// 创建媒体会话(锁屏/通知栏控制)
const session = await avSession.createAVSession(context, 'MusicPlayer', 'audio');
// 状态同步:播放/暂停/进度
session.setAVMetadata({ title: '歌曲名', artist: '歌手', duration: 180000 });
session.setAVPlaybackState({ state: avSession.PlaybackState.PLAYING, position: { elapsedTime: 0 } });
// 监听控制命令(锁屏按钮)
session.on('play', () => { avPlayer.play(); });
session.on('pause', () => { avPlayer.pause(); });
session.on('seekTo', (time) => { avPlayer.seek(time); });
// 页面退出时销毁
session.destroy();
踩坑:不创建 AVSession → 锁屏无控制按钮,音频播放会被系统杀掉。
@Entry
@Component
struct VideoPlayerPage {
private avPlayer: media.AVPlayer | null = null;
private xComponentController = new XComponentController();
@State currentTime: number = 0;
@State duration: number = 0;
build() {
Stack() {
XComponent({ id: 'video', type: XComponentType.SURFACE, controller: this.xComponentController })
.onLoad(() => {
const surfaceId = this.xComponentController.getXComponentSurfaceId();
this.initPlayer(surfaceId);
})
// 进度条
Slider({ value: this.currentTime, min: 0, max: this.duration })
.onChange((value) => { this.avPlayer?.seek(value); })
}
}
async initPlayer(surfaceId: string) {
this.avPlayer = await media.createAVPlayer();
this.avPlayer.on('stateChange', async (state) => {
if (state === 'initialized') {
this.avPlayer!.surfaceId = surfaceId;
await this.avPlayer!.prepare();
}
if (state === 'prepared') {
this.duration = this.avPlayer!.duration;
await this.avPlayer!.play();
}
});
this.avPlayer.url = 'https://example.com/movie.mp4';
}
aboutToDisappear() { this.avPlayer?.release(); }
}
见上方 NetworkKit WebSocketClient 封装,核心要点:
pingInterval: 15000 内核级心跳EventHub 分发到 UI 层见上方 CameraKit 全流程实战代码,包含:
见上方 ShakeDetector 封装,核心要点:
15 m/s²1000msinterval: 100000000纳秒)import { sensor } from '@kit.SensorServiceKit';
@Entry
@Component
struct PedometerPage {
@State steps: number = 0;
@State calories: number = 0;
aboutToAppear() {
sensor.on(sensor.SensorId.PEDOMETER, (data) => {
this.steps = data.steps;
this.calories = data.steps * 0.04; // 粗略估算
}, { interval: 200000000 });
}
aboutToDisappear() {
sensor.off(sensor.SensorId.PEDOMETER);
}
build() {
Column() {
Text(`今日步数: ${this.steps}`).fontSize(32)
Text(`消耗热量: ${this.calories.toFixed(0)} kcal`).fontSize(20)
}
}
}
// ⚠️ 使用 ORIENTATION 融合传感器(不是 MAGNETIC_FIELD)
sensor.on(sensor.SensorId.ORIENTATION, (data) => {
this.heading = data.x; // 方位角 0~360°
this.updateCompassUI();
}, { interval: 200000000 });
const recorder = await media.createAVRecorder();
await recorder.configure({
audioSourceType: media.AudioSourceType.AUDIO_MIC,
profile: { audio: { codec: media.CodecType.AUDIO_AAC, bitrate: 64000, sampleRate: 44100 } },
url: `${context.filesDir}/voice_${Date.now()}.m4a`
});
await recorder.prepare();
await recorder.start();
// 录制中... setTimeout(() => recorder.stop(), 30000);
import { media } from '@kit.MediaKit';
const capture = await media.createAVScreenCapture();
capture.configure({
audioSourceType: media.AudioSourceType.AUDIO_ALL_PLAYBACK, // 系统内录
videoSourceType: media.VideoSourceType.VIDEO_SURFACE_RGBA,
profile: { audio: { codec: media.CodecType.AUDIO_AAC }, video: { codec: media.CodecType.VIDEO_AVC } },
url: `${context.filesDir}/screen_${Date.now()}.mp4`
});
await capture.start();
// 录屏中...
await capture.stop();
capture.release();
⚠️ 权限:需要 ohos.permission.CAPTURE_SCREEN(system_grant)
见上方 HttpClientManager 封装,核心要点:
usingHttp3: true 开启 QUICAccept-Encoding: br 声明 BrotliexpectDataType: ARRAY_BUFFER 零拷贝返回import { media } from '@kit.MediaKit';
const generator = await media.createAVImageGenerator();
generator.srcFdSrc = { fd: videoFd };
generator.timeMs = 5000; // 5秒位置
const pixelMap = await generator.fetchFrame();
// pixelMap → Image组件显示
generator.release();
见上方 AVTranscoder 代码,核心场景:
// 伪代码——基于 SDK 26 音频节点组合能力推断
import { audioEngine } from '@kit.AudioEngineKit';
// 直播场景管线:降噪 → 美化 → 空间渲染
const pipeline = audioEngine.createPipeline()
.addNode(audioEngine.nodes.noiseReduction({ level: 'high' }))
.addNode(audioEngine.nodes.beautify({ mode: 'voice', brightness: 0.6 }))
.addNode(audioEngine.nodes.spatialRender({ scene: 'studio', width: 0.8 }))
.build();
audioStream.setProcessingPipeline(pipeline);
⚠️ 注意:正式版API可能有差异,Beta1阶段基于文档推断。
import { window } from '@kit.ArkUI';
// 创建闪控窗
const flashWindow = await window.createFlashControl(context, {
initialMode: window.FlashControlMode.FLOATING,
bounds: { x: 100, y: 600, width: 360, height: 480 },
supportedModes: [
window.FlashControlMode.FLOATING,
window.FlashControlMode.SIDEBAR,
window.FlashControlMode.BALL
],
ballConfig: { size: 56, icon: $r('app.media.ball_icon'), autoHide: true }
});
await flashWindow.setUIContent('pages/AssistantPanel');
flashWindow.on('modeChanged', (mode) => {
// 缩球时暂停后台任务,展开时恢复
});
flashWindow.show();
// ⚠️ 释放必须在 aboutToDisappear
aboutToDisappear() { flashWindow.destroy(); }
踩坑:忘了 destroy() → 悬浮窗内存泄漏,DevEco内存分析工具一开始检测不到。
@Entry
@Component
@Interactive // ✅ 声明互动能力
struct WeatherCard {
@State isShaking: boolean = false;
@State tiltX: number = 0;
@State tiltY: number = 0;
build() {
Stack() {
Image($r('app.media.weather_bg')).width('100%').height('100%')
// 前景出框效果(clip(false)允许溢出边界)
Image($r('app.media.sun_icon'))
.width(80).height(80)
.clip(false)
.offset({ x: this.tiltX * 10, y: this.tiltY * 10 - (this.isShaking ? 20 : 0) })
.scale({ x: this.isShaking ? 1.2 : 1, y: this.isShaking ? 1.2 : 1 })
.animation({ duration: 300, curve: Curve.EaseOut })
}
.width('100%').height('100%')
.onShake(() => { this.isShaking = true; setTimeout(() => this.isShaking = false, 1500); })
.onTilt((event) => { this.tiltX = event.angleX; this.tiltY = event.angleY; })
}
}
⚠️ module.json5 必须声明:
"interactive": true,
"sensorCapabilities": ["shake", "tilt"]
少了任何一个,摇一摇和倾斜事件都不会触发。
源自官方 Sample
AlwaysOnMarketWatch(2,035 下载)及华为最佳实践文档。覆盖桌面/锁屏/待机/隐私保护四类系统能力。
闪控球(FloatingBall) ←→ 悬浮窗(FloatView) ← 桌面场景
↕ ↕
锁屏卡片(Form Kit) → 待机屏保卡片(Form Kit) ← 锁屏/待机场景
↕
防窥保护(dlpAntiPeep) ← 隐私保护
import { floatingBall } from '@kit.ArkUI';
// 创建闪控球控制器
async function createFloatingBall(context: UIAbilityContext): Promise<void> {
const controller = await floatingBall.create({
context: context
});
// 启动闪控球
controller.startFloatingBall({
template: floatingBall.TemplateType.ROUNDED_RECTANGLE,
title: '自选股',
content: '上证 3,201.25 ▲0.32%'
});
}
import { floatView } from '@kit.ArkUI';
// 创建悬浮窗控制器(绑定闪控球)
async function createFloatView(context: UIAbilityContext): Promise<void> {
if (!floatView.isFloatViewEnabled()) return; // 检查设备支持
const controller = await floatView.create({
context: context,
templateType: floatView.FloatViewTemplateType.ROUNDED_RECTANGLE
});
}
// 绑定闪控球与悬浮窗(点击闪控球弹出悬浮窗)
floatView.bind(floatViewController, floatingBallController, ballParams);
// 双指手势切换悬浮窗大小(正常态 ↔ 横幅态)
PanGesture({ fingers: 2, direction: PanDirection.Up })
.onActionStart(() => {
FloatViewController.setWindowSize({
width: vp2px(bannerWidth),
height: vp2px(bannerHeight)
});
})
requirements:AGC 申请"锁屏卡片"权限 + form_config.json 配置 renderingMode 和 supportDimensions(必须含 "1*1" 或 "1*2")。
{
"forms": [{
"name": "LockScreenCard",
"src": "./ets/widget/pages/LockScreenCard.ets",
"renderingMode": "autoColor",
"defaultDimension": "1*2",
"supportDimensions": ["1*2"]
}]
}
@Entry(storageUpdateByMsg)
@Component
struct LockScreenCard {
@LocalStorageProp('finalWatchList') stockList: StockInfo[] = [];
build() {
Column() {
ForEach(this.stockList.slice(0, 3), (stock: StockInfo) => {
Row({ space: 2 }) {
Text(stock.name).fontSize(12).fontColor('#FFFFFF')
Text(stock.rate).fontSize(12)
.fontColor(stock.rate.startsWith('+') ? '#FF4444' : '#00CA69')
}
})
}
.onClick(() => {
postCardAction(this, { action: 'router', abilityName: 'EntryAbility' });
})
}
}
配置:form_config.json 中新增 standby 字段。仅支持 2×2 尺寸。
{
"forms": [{
"name": "widget",
"defaultDimension": "2*2",
"supportDimensions": ["2*2"],
"standby": {
"isSupported": true,
"isAdapted": true,
"isPrivacySensitive": false
}
}]
}
import { dlpAntiPeep } from '@kit.DeviceSecurityKit';
// 检查系统能力
export function canUseAntiPeep(): boolean {
return canIUse('SystemCapability.Security.DlpAntiPeep');
}
// 检查开关状态
export async function isAntiPeepOn(): Promise<boolean> {
return await dlpAntiPeep.isDlpAntiPeepSwitchOn();
}
// 注册窥视状态监听
function startListening(): void {
dlpAntiPeep.on('dlpAntiPeep', (info: dlpAntiPeep.DlpAntiPeepStatus) => {
if (info.isPeep) {
// 有人窥视 → 显示蒙层
showMask();
} else {
hideMask();
}
});
}
import { abilityAccessCtrl, common } from '@kit.AbilityKit';
// 请求悬浮窗权限
let atManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(context, [
'ohos.permission.FLOAT_VIEW'
]);
features/stockfloatpanel/src/main/ets/
├── controllers/
│ ├── FloatPanelController.ets # 核心控制器:创建/绑定/启动
│ ├── FloatingBallController.ets # 闪控球控制器
│ └── FloatViewController.ets # 悬浮窗控制器
├── pages/
│ └── FloatViewPage.ets # 悬浮窗 UI + 防窥逻辑
├── views/
│ ├── FloatViewStockList.ets # 正常态行情列表
│ └── FloatViewStockBanner.ets # 横幅态行情展示
└── utils/
└── AntiPeepUtils.ets # 防窥保护工具类
源自官方 Sample
LiveCard,演示 HarmonyOS 互动卡片(Live Form)全链路开发:配置→普通卡片→LiveForm→通信→动画。
form_config.json(配对配置)
↓
普通桌面卡片(静态 Widget) ← src 指向
↓ 用户点击
sceneAnimationParams.abilityName
↓
LiveFormExtensionAbility ← 独立的 UI 扩展进程
↓ localStorage
LiveForm UI 页面 ← 帧动画/陀螺仪/进度动画
↓ postCardAction
CardActionHandler(主应用接收)
↓ formProvider.updateForm
普通卡片动态更新
{
"name": "MusicCard", // 卡片身份证,与 form_name 一致
"src": "./ets/widget/pages/MusicCard.ets", // 普通卡片页面
"isDynamic": true, // 允许应用动态更新卡片
"defaultDimension": "2*4", // 默认尺寸
"supportDimensions": ["2*4"], // 支持的尺寸列表
"sceneAnimationParams": { // 互动卡片关键绑定
"abilityName": "MusicLiveCardAbility"
}
}
流程:src → 普通卡片路径,abilityName → LiveForm 展开页。两者通过 module.json5 注册关联。
// module.json5 注册 LiveForm
{
"name": "MusicLiveCardAbility",
"srcEntry": "./ets/livecardability/MusicLiveCardAbility.ets",
"type": "liveForm"
}
import { LiveFormExtensionAbility, LiveFormInfo, UIExtensionContentSession } from '@kit.AbilityKit';
export class MusicLiveCardAbility extends LiveFormExtensionAbility {
async onLiveFormCreate(info: LiveFormInfo, session: UIExtensionContentSession): Promise<void> {
let storage = new LocalStorage();
storage.setOrCreate('context', this.context);
storage.setOrCreate('session', session);
storage.setOrCreate('formId', info.formId);
storage.setOrCreate('borderRadius', info.borderRadius);
// 加载 LiveForm UI 页面
session.loadContent('livecardability/pages/MusicLiveCard', storage);
// 从 RDB 数据库加载歌曲数据
let songList = await SongRdbHelper.getInstance(this.context).queryAllSongs();
storage.setOrCreate('songList', songList);
}
}
@Entry(storage)
@Component
struct MusicLiveCard {
@LocalStorageProp('currentSong') currentSong: SongItem = new SongItem();
@State @Watch('frameChange') currentFrame: number = 0;
// 帧动画:封面切换
private startAnimation(): void {
setInterval(() => {
if (this.currentFrame < this.totalFrames - 1) {
this.currentFrame++;
}
}, this.frameDuration);
}
build() {
Column() {
Image($r(`app.media.cover_${this.currentFrame}`)) // 帧动画
.width('100%').aspectRatio(1)
Row() {
Button() { Image($r('app.media.prev')) }.onClick(() => {
postCardAction(this, { action: 'message', data: 'prev' })
})
Button() { Image($r('app.media.play')) }.onClick(() => {
postCardAction(this, { action: 'message', data: 'toggle' })
})
Button() { Image($r('app.media.next')) }.onClick(() => {
postCardAction(this, { action: 'message', data: 'next' })
})
}
}
.onClick(() => {
postCardAction(this, { action: 'router', abilityName: 'EntryAbility' })
})
}
}
| 方向 | 方式 | 说明 |
|---|---|---|
| 卡片→应用 | postCardAction(this, { action, data }) | 发送 CALL/MESSAGE/ROUTER 指令 |
| 应用→静态卡片 | formProvider.updateForm(formId, formBindingData) | 更新卡片数据 |
| 应用→实况卡片 | Preferences/RDB 持久化 | 读取最新状态 |
// 主应用注册卡片动作处理器
import { CardActionHandler } from './utils/CardActionHandler';
onCreate(want: Want): void {
CardActionHandler.setContext(this.context);
this.callee.on('cardAction', CardActionHandler.getHandler());
}
| 卡片 | 场景 | 交互特点 | 核心能力 |
|---|---|---|---|
| 🎵 音乐播控 | 播放控制 | 帧动画封面、歌词滚动、播放/暂停/切歌 | setInterval 帧动画 + LRC 歌词解析 |
| 📦 快递追踪 | 物流跟踪 | 陀螺仪驱动角色移动 | @ohos.sensor.gyroscope + animateTo |
| 🏃 运动记录 | 卡路里进度 | 进度动画、状态切换 | 状态机(未开始/进行中/完成) |
| 🌿 睡眠监测 | 健康数据 | 三叶草起床动画 | 数据驱动 UI 更新 |
互动卡片打不开时按顺序检查:
form_config.json 的 name 是否与 form_name 一致src 指向的普通卡片文件是否真实存在defaultDimension 是否在 supportDimensions 列表中sceneAnimationParams.abilityName 的值是否存在module.json5 是否注册了同名的 type: "liveForm" Ability注册开发者 → AGC 创建应用 → 生成签名 → 打包 HAP → 上传 → 填写信息 → 审核 → 上架
| 文件 | 格式 | 用途 |
|---|---|---|
| 密钥库文件 | .p12 | 存储公私钥对 |
| 证书请求文件 | .csr | 向 AGC 申请数字证书 |
| 数字证书 | .cer | 由 AGC 颁发 |
| Profile 文件 | .p7b | 包含包名、证书、权限列表 |
build-profile.json5 中配置 signingConfigs| 组件 | 用途 | 关键用法 |
|---|---|---|
| Row / Column | 线性布局(水平/垂直) | .justifyContent() .alignItems() 控制对齐 |
| Stack | 层叠布局,子组件可重叠 | 默认居中,alignContent 控制叠放对齐 |
| Flex | 弹性布局,等分空间 | .wrap(FlexWrap.Wrap) 换行、flexGrow/flexShrink |
| RelativeContainer | 相对定位,子组件基于父容器/兄弟锚定 | alignRules: { left: { anchor: 'container', align: HorizontalAlign.Start } } |
| Grid / GridItem | 栅格布局,适合后台列表/卡片墙 | columnsTemplate('1fr 1fr 1fr') 控制列数 |
| WaterFlow | 瀑布流容器,商品流/图片墙 | columnsTemplate('1fr 1fr') + FlowItem 子项,onReachEnd 触发加载更多 |
| 组件 | 用途 | 关键用法 |
|---|---|---|
| Scroll | 可滚动容器 | .scrollable(ScrollDirection.Vertical) 设置方向 |
| List / ListItem | 列表(支持 sticky 分组) | List() + ForEach + ListItem,onScrollIndex 监听滚动位置 |
| Swiper | 轮播/滑动切换页 | autoPlay(true) interval(3000) indicator(true) 自动轮播+指示器 |
| Tabs / TabContent | 选项卡切换 | barPosition(BarPosition.Start) 控制标签位置 |
| Navigation | 页面路由+栈导航 | NavPathStack + NavDestination,推荐替代 Router |
| 组件 | 用途 | 关键用法 |
|---|---|---|
| Text | 文本展示 | .fontSize() .fontColor() .textAlign() 多行用 TextOverflow.Ellipsis |
| Image | 图片展示 | .objectFit(ImageFit.Cover) .source($rawfile('a.png')) |
| Video | 视频播放 | .src($rawfile('v.mp4')) .autoPlay(true) .controls(true) |
| RichText | 富文本/HTML 渲染 | 直接传入 HTML 字符串,支持基本标签 |
| Progress | 进度条 | Progress({ value: 50, total: 100 }) .style(ProgressStyle.Linear) |
| 组件 | 用途 | 关键用法 |
|---|---|---|
| Button | 按钮 | .onClick() .type(ButtonType.Capsule) |
| TextInput | 单行文本输入 | .onChange(v => this.v = v) .placeholder('请输入') |
| TextArea | 多行文本输入 | 同 TextInput,多行自动换行 |
| Slider | 滑块选择器 | .onChange(v => this.val = v) .min(0) .max(100) |
| Toggle | 开关 | .type(ToggleType.Switch) .onChange(v => this.on = v) |
| Radio | 单选项 | 需配合 RadioGroup 使用 |
| Checkbox | 多选项 | .onChange(v => this.checked = v) |
| Select | 下拉选择 | Select([{value:'a'},{value:'b'}]) .onSelect((i,v)=>...) |
| DatePicker / TimePicker | 日期/时间选择 | DatePicker({ start: new Date(), selected: this.date }) |
| 组件 | 用途 | 关键用法 |
|---|---|---|
| AlertDialog | 确认弹窗 | AlertDialog.show({ title, message, confirm }) |
| CustomDialog | 自定义弹窗 | @CustomDialog 装饰器 + customDialog: CustomDialogController |
| Sheet | 底部面板 | .bindSheet($$this.show, { height: SheetSize.LARGE }) |
| Panel | 可拖拽面板 | Panel({ mode: PanelMode.Half }) 支持悬浮/半屏/全屏 |
| 组件 | 用途 | 关键用法 |
|---|---|---|
| XComponent | 原生渲染引擎接入 | type: XComponentType.SURFACE 用于游戏引擎、相机预览 |
| Web | WebView 容器 | .src('https://...') .javaScriptAccess(true) |
| Canvas / CanvasPattern | 2D 画布自定义绘制 | 获取 CanvasRenderingContext2D 绘制图形 |
| Shape | 矢量图形 | Circle() Rect() Path() 支持描边/填充 |
| QRCode | 二维码生成 | QRCode({ value: 'https://...' }) 自动生成扫码图案 |
| 模式 | 说明 | 适用场景 | 配置方式 |
|---|---|---|---|
| singleton | 单例,只创建一个实例复用 | 主页面、播放器 | module.json5 中 "launchType": "singleton" |
| multiton | 每次启动创建新实例 | 独立任务、多窗口编辑 | "launchType": "multiton" |
| specified | 根据 Key 按需匹配/创建实例 | 聊天窗口(每个对话独立实例) | "launchType": "specified" + onNewWant() 处理 |
// 自定义 AbilityStage,在 module.json5 中配置 srcEntry
export default class MyAbilityStage extends AbilityStage {
onCreate(): void { /* 应用初始化逻辑 */ }
onAcceptWant(want: Want): string {
// specified 模式下根据 Want 返回实例 Key
return `conversation_${want.parameters?.conversationId}`;
}
}
caller → want 信息
Caller Ability ──────────────> Callee Ability
调用请求
callee.on('method', (data) => { return result; });
// Caller 端
const caller = await context.startAbilityByCall({ bundleName, abilityName });
await caller.call('login', { user: 'admin' });
// Callee 端(被调方注册方法)
import { Caller } from '@kit.AbilityKit';
callee.on('login', (data) => {
console.info('Received:', data);
return { token: 'xxx' };
});
export default class MyService extends ServiceExtensionAbility {
onCreate(want: Want): void { /* 启动后台任务 */ }
onRequest(want: Want, startId: number): void { /* 处理请求 */ }
onConnect(want: Want): rpc.RemoteObject { return new MyStub('service'); }
onDestroy(): void { /* 清理资源 */ }
}
| 用途 | 说明 |
|---|---|
| 后台下载 | 文件下载、数据同步,前台可绑定获取进度 |
| 音乐播放 | 保持后台播放,即使 UI 被销毁 |
| 传感器监听 | 持续监听传感器数据,回传给 UI |
onCreate() → onWindowStageCreate() → onForeground() → [可见交互]
↓ ↓
onDestroy() ← onBackground() ← onWindowStageDestroy()
onCreate: 应用初始化(全局变量、数据预加载)onWindowStageCreate: 设置窗口属性、加载页面onForeground: 进入前台(恢复动画、重新订阅)onBackground: 进入后台(保存草稿、释放资源)onWindowStageDestroy / onDestroy: 释放全局资源| 类型 | 全称 | 特点 | 使用场景 |
|---|---|---|---|
| HAP | Harmony Ability Package | 可独立安装运行的基础单元 | 应用功能模块 |
| HAR | Harmony Archive | 静态共享包(编译时打包进 HAP) | 公共库、工具类、基座组件 |
| HSP | Harmony Shared Package | 动态共享包(运行时按需加载) | 按需加载的插件化模块 |
| 场景 | 推荐 |
|---|---|
| 应用主入口 | entry 类型 HAP |
| 功能模块(可独立) | feature 类型 HAP |
| 工具函数/网络层/数据层 | HAR(静态共享,编译打包) |
| 需要按需加载的业务模块 | HSP(动态共享,分包下载) |
MyApp/
├── entry/ # 主模块 HAP
│ └── src/main/module.json5
├── feature-home/ # 首页模块 HAP
├── feature-profile/ # 个人中心 HAP
├── library-common/ # 公共库 HAR
│ └── src/main/ets/utils/
├── library-network/ # 网络库 HAR
├── plugin-payment/ # 支付插件 HSP(按需加载)
└── build-profile.json5 # 工程级配置
{
module: {
name: 'entry',
type: 'entry', // entry | feature | har | shared
srcEntry: './ets/entryability/EntryAbility.ts',
description: '主模块',
mainElement: 'EntryAbility',
deviceTypes: ['phone', 'tablet'],
// HAP/HSP 中声明的 abilities
abilities: [
{ name: 'EntryAbility', srcEntry: './ets/...', launchType: 'singleton' }
],
// 依赖的 HAR/HSP
dependencies: [
{ name: 'library-common', version: '1.0.0' }
],
// 请求权限
requestPermissions: [
{ name: 'ohos.permission.INTERNET', reason: '网络连接' }
]
}
}
{
app: {
bundleName: 'com.example.myapp',
version: { code: 1000000, name: '1.0.0' },
// 签名配置
signingConfigs: [
{
name: 'default',
type: 'HarmonyOS',
storeFile: './signing/keystore.p12',
storePassword: '',
keyAlias: 'mykey',
keyPassword: '',
profileFile: './signing/Profile.p7b',
certpath: './signing/Certificate.cer',
}
]
},
modules: [
{ name: 'entry', srcPath: './entry' },
{ name: 'library-common', srcPath: './library-common' }
]
}
import { taskpool } from '@kit.ArkTS';
@Concurrent
function computePrimes(limit: number): number[] {
const primes: number[] = [];
for (let i = 2; i <= limit; i++) {
if (primes.every(p => i % p !== 0)) primes.push(i);
}
return primes;
}
async function run(): Promise<void> {
const task = new taskpool.Task(computePrimes, [100000]);
const result = await taskpool.execute(task);
}
// AppStorage: 应用级全局状态
AppStorage.setOrCreate('userName', 'guest');
// LocalStorage: 页面级状态(跨组件共享)
let storage = new LocalStorage();
storage.setOrCreate('count', 0);
@Entry(storage)
@Component
struct PageA { @LocalStorageProp('count') count: number = 0; }
@Component
struct ChildB { @LocalStorageLink('count') count: number = 0; }
// Preferences: 持久化
import { preferences } from '@kit.ArkData';
const prefs = await preferences.getPreferences(context, 'myPrefs');
await prefs.put('volume', 80);
await prefs.flush();
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0x0001;
const TAG = 'MyApp';
hilog.info(DOMAIN, TAG, 'User logged in: %{public}s', userName);
hilog.warn(DOMAIN, TAG, 'Disk usage: %d%%', usage);
hilog.error(DOMAIN, TAG, 'Failed to load: %{private}s', errMsg); // 敏感信息用 private
来源:
component-collection| ⭐22下载 | Gitee harmonyos_samples | 源码级
本示例为 ArkUI 中组件、通用属性、动画、全局方法的四大模块集合,通过 Tabs 容器搭建整体框架,每个 TabContent 使用 List 容器 + 循环渲染加载分类导航数据。
entry/src/main/ets/
├── common/ # 公共组件
│ ├── AttributeModificationTool.ets
│ ├── IntroductionTitle.ets
│ ├── TabContentNavigation.ets
│ └── TitleBar.ets
├── data/ # 数据层 (CollectionCategory + ResourceDataHandle)
├── model/ # 数据模型 (CategoricalDataType)
├── pages/ # 页面
│ ├── Index.ets # Tabs 首页
│ ├── animations/ # 动画模块
│ ├── components/ # 组件模块
│ ├── globalMethods/ # 全局方法模块
│ └── universal/ # 通用模块
└── util/ # Logger + ShowToast
| 模块 | 子模块 | 核心组件/API |
|---|---|---|
| 组件 | 空白分隔、按钮选择、滚动滑动、信息展示、文本输入、辅助、Canvas、行列分栏、Flex栅格、列表宫格、导航、图形绘制、媒体、Web | Divider/Button/Checkbox/Slider/Swiper/DataPanel/Gauge/Marquee/Particle/PatternLock/AlphabetIndexer/Canvas/Column/Row/SideBarContainer/Stack/Flex/GridRow/Navigation/Tab/Web |
| 通用 | 事件(点击/触摸/拖拽/焦点/键鼠)、属性(背景/边框/显示/特效/字体/裁剪/安全区)、手势(Tap/LongPress/Pan/Pinch/Rotation/Swipe) | onClick → onDrag → pan → pinch → rotation → combined |
| 动画 | 组件转场、页面转场、共享元素、布局动效、尺寸变换、悬浮窗、文件夹展开、图库卡片、商店卡片、侧边栏、路径动画、属性动画 | Transition/PageTransition/SharedTransition/LayoutAnimation/SizeTransition |
| 全局方法 | AlertDialog/ActionSheet/CustomDialog/DateDialog/TimeDialog/TextPickerDialog/Menu | 弹窗 API 集合 |
// [API 23+] Tabs + List 分类导航框架
@Component
struct CollectionIndex {
@State currentIndex: number = 0
private categories: CollectionCategory[] = [ /* 分类数据 */]
build() {
Tabs({ index: this.currentIndex }) {
ForEach(this.categories, (category: CollectionCategory) => {
TabContent() {
List() {
ForEach(category.items, (item: CategoricalData) => {
ListItem() {
// 使用 Navigation 或 router 跳转详情
Text(item.name)
}
})
}
}.tabBar(this.TabBuilder(category.name))
})
}
.vertical(false)
.barWidth('100%')
.barHeight(56)
}
@Builder TabBuilder(title: ResourceStr) {
Text(title).fontSize(14).fontColor('#666')
}
}
来源:
multi-hap| 工程架构 | 源码级
一个应用中同时包含 entry HAP(主入口)和多个 feature HAP(音频/视频播放),各 HAP 独立打包、独立安装。
├── entry/ # "type": "entry" - 主模块
│ └── pages/Index.ets # 首页 + 多HAP跳转逻辑
├── audioFeature/ # "type": "feature" - 音频播放
│ └── audioAbility/AudioAbility.ets
├── videoFeature/ # "type": "feature" - 视频播放
│ └── videoability/VideoAbility.ets
// entry/src/main/module.json5
{ "type": "entry" }
// audioFeature/src/main/module.json5
{ "type": "feature" }
// videoFeature/src/main/module.json5
{ "type": "feature" }
// AppScope/app.json5
{ "bundleName": "com.samples.multihap" }
// [API 23+] 使用 Want 跳转 feature HAP
import { common, Want } from '@kit.AbilityKit'
@Entry
@Component
struct Index {
private context = getContext(this) as common.UIAbilityContext
build() {
Button('播放音频')
.onClick(() => {
let want: Want = {
bundleName: 'com.samples.multihap',
abilityName: 'AudioAbility' // audioFeature 中的 Ability
}
this.context.startAbility(want)
})
}
}
Build → Build Hap(s) → Build Hap(s) 构建各模块 hapEdit Configurations → Deploy Multi Hap → 勾选 audioFeature + videoFeature来源:
DragFramework| Gitee harmonyos_samples | 源码级
本示例实现图片、富文本、文本、输入框、列表、超链接、本地视频、在线图片等组件的拖拽功能,支持水印、自定义背板、AI识别、图文混排、分屏拖拽、跨设备拖拽。
| 组件 | 拖出(draggable) | 拖入(onDrop) |
|---|---|---|
| Search | ✅ | ✅ |
| TextInput | ✅ | ✅ |
| TextArea | ✅ | ✅ |
| RichEditor | ✅ | ✅ |
| Text | ✅ | - |
| Image | ✅ | - |
| Hyperlink | ✅ | - |
| FormComponent | ✅ | - |
| Video | - | ✅ |
// [API 23+] onDragStart 自定义拖拽背板
Image(this.imageSrc)
.draggable(true)
.onDragStart(() => {
// 返回自定义背板组件
return this.buildCustomDragPanel()
})
// [API 23+] onDrop 实现 AI 识别
TextInput()
.onDrop((event: DragEvent) => {
let data = event.getData()
// 调用 AI 接口识别 data 内容
this.aiRecognitionResult = data
})
支持三种方式:
通过单实例模式(singleton)实现同一应用分屏拖拽。
来源:
hand-writing-to-image| Gitee harmonyos_samples | 源码级
NodeContainer ← MyNodeController → rootRenderNode
↓ onTouch
创建子 RenderNode → Pen + Path 绘制轨迹 → getChild/removeChild 撤销
↓ componentSnapshot.get
PixelMap → Image.packToFile() / packing() → 保存为图片
// [API 23+] 自定义渲染节点手写绘制
class MyRenderNode extends RenderNode {
private pen: Drawing.Pen = new Drawing.Pen()
private path: Drawing.Path = new Drawing.Path()
constructor() {
super()
this.pen.setColor({ alpha: 255, red: 0, green: 0, blue: 0 })
this.pen.setStrokeWidth(4)
}
draw(context: DrawContext) {
context.canvas.attachPen(this.pen)
context.canvas.drawPath(this.path)
context.canvas.detachPen()
}
updatePath(newPath: Drawing.Path) {
this.path = newPath
}
}
class MyNodeController extends NodeController {
private rootRenderNode: MyRenderNode
private nodeCount: number = 0
makeNode(uiContext: UIContext): RenderNode | null {
this.rootRenderNode = new MyRenderNode()
return this.rootRenderNode
}
addStroke(path: Drawing.Path) {
let node = new MyRenderNode()
node.updatePath(path)
this.rootRenderNode?.appendChild(node)
this.nodeCount++
}
undoStroke() {
let last = this.rootRenderNode?.getChild(this.nodeCount - 1)
if (last) {
this.rootRenderNode?.removeChild(last)
this.nodeCount--
}
}
clearAll() {
this.rootRenderNode?.clearChildren()
this.nodeCount = 0
}
}
// [API 23+] 使用 componentSnapshot 获取 PixelMap 并保存
async function saveToImage(componentId: string): Promise<string> {
let pixelMap: PixelMap = await componentSnapshot.get(componentId)
// 方式一: packToFile
let path = getContext().cacheDir + '/drawing.png'
let packer = image.createImagePacker()
let packOpts: image.PackingOption = { format: 'image/png', quality: 100 }
packer.packToFile(pixelMap, path, packOpts)
return path
// 方式二: packing (返回 ArrayBuffer)
// let data: ArrayBuffer = await packer.packing(pixelMap, packOpts)
}
来源:
system-router-map| Gitee harmonyos_samples | 源码级
通过**系统路由表(route_map.json)**实现多模块(HSP/HAR)页面跳转,零依赖耦合,懒加载未跳转页面。
entry (主模块) → route_map.json → harA / harB / hspA / hspB
各模块之间不需要配置依赖关系,跳转时动态加载目标页面。
Step 1: 目标模块 module.json5 添加路由配置
// harA/src/main/module.json5
{
"module": {
"routerMap": "$profile:route_map"
}
}
Step 2: 创建 route_map.json
// resources/base/profile/route_map.json
{
"routerMap": [
{
"name": "PageFromHarA",
"buildFunction": "HarABuilder",
"pageSourceFile": "src/main/ets/components/mainpage/A1.ets"
},
{
"name": "PageFromHspB",
"buildFunction": "HspBBuilder",
"pageSourceFile": "src/main/ets/components/mainpage/B1.ets"
}
]
}
Step 3: 目标页面暴露 Builder 函数
// harA 目标页面
@Builder
export function HarABuilder() {
A1Page()
}
@Component
struct A1Page {
build() { /* ... */ }
}
Step 4: 主模块跳转
// [API 23+] 通过系统路由表跳转
import { router } from '@kit.ArkUI'
Button('跳转 HAR A 页面')
.onClick(() => {
router.pushDestinationByName('PageFromHarA')
})
来源:
navigation-router| Gitee harmonyos_samples | 源码级
将路由功能抽取为独立 RouterModule HAR,统一管理 Navigation 下多 HAR/HSP 的路由跳转,实现业务模块间解耦。
entry (主入口 + Navigation)
├── harA ───→ RouterModule (路由委托)
├── harB ───→ RouterModule
├── harC ───→ RouterModule
└── RouterModule (路由管理)
RouterModule/
├── Index.ets # 对外暴露路由方法和常量
├── src/main/ets/constants/
│ └── RouterConstants.ets # 路由信息常量
├── src/main/ets/model/
│ └── RouterModel.ets # 路由信息模型
└── src/main/ets/utils/
└── RouterModule.ets # 核心路由管理
// [API 23+] RouterModule 对外暴露的路由管理类
import { RouterConstants } from '../constants/RouterConstants'
export class RouterModule {
static push(routerName: string, param?: Record<string, Object>) {
// 根据 routerName 查找对应页面信息
let pageInfo = RouterConstants.PAGE_MAP[routerName]
// 通过 Navigation pushPath 跳转
this.navPathStack.pushPath({ name: pageInfo.name, param: param })
}
static back() {
this.navPathStack.pop()
}
}
// 业务模块中调用
// RouterModule.push('PageA1', { id: 123 })
来源:
RcpFileTransfer| Gitee harmonyos_samples | 源码级
基于 Remote Communication Kit(RCP) 实现相册文件上传下载、分片下载、断点续传、后台传输。
| 功能 | 页面 | 关键 API |
|---|---|---|
| 相册图片上传 | AlbumImageTrasfer | rcp.post() |
| 相册图片下载 | AlbumImageTrasfer | rcp.fetch() / rcp.downloadToFile() |
| 文件分片下载 | ChunkedFileTransfer | Range header + 分片合并 |
| 断点续传 | ResumableFileTransfer | ETag + If-Range |
| 后台传输 | BackgroundFileTransfer | Service Ability + KEEP_BACKGROUND_RUNNING |
entry/src/main/ets/
├── pages/
│ ├── AlbumImageTrasfer.ets # 相册上传下载
│ ├── BackgroundFileTransfer.ets # 后台传输
│ ├── ChunkedFileTransfer.ets # 分片下载
│ ├── ResumableFileTransfer.ets # 断点续传
│ └── Index.ets # 首页
├── service/
│ ├── FileRequest.ets # 请求接口模块
│ ├── Interceptor.ets # 请求拦截器
│ └── Model.ets # 数据类型定义
└── utils/
├── CommonUtil.ets
└── LocalFileUtil.ets
// [API 23+] RCP 文件下载
import { rcp } from '@kit.RemoteCommunicationKit'
async function downloadFile(url: string, savePath: string): Promise<void> {
let session = rcp.createSession()
let request = new rcp.Request(url, rcp.Method.GET)
let response = await session.fetch(request)
// 方式一: 直接写入文件
await response.toFile(savePath)
}
// 断点续传
async function resumeDownload(url: string, savePath: string, offset: number): Promise<void> {
let session = rcp.createSession()
let request = new rcp.Request(url, rcp.Method.GET)
request.setHeader('Range', `bytes=${offset}-`)
let response = await session.fetch(request)
await response.toFile(savePath, { append: true })
}
"ohos.permission.INTERNET"
"ohos.permission.GET_NETWORK_INFO"
"ohos.permission.KEEP_BACKGROUND_RUNNING"
来源:
ability-start-mode| Gitee harmonyos_samples | 源码级
Stage 模型中 launchType 决定 UIAbility 的启动行为:
| 模式 | module.json5 | 行为 |
|---|---|---|
| singleton | "launchType": "singleton" | 单实例,每次 startAbility 复用已有实例 |
| standard(默认) | "launchType": "multiton" | 多实例,每次新建 UIAbility |
| specified | "launchType": "specified" | 自定义实例标识,MyAbilityStage.onAcceptWant 返回标识 |
// [API 23+] MyAbilityStage.onAcceptWant 自定义实例标识
export default class MyAbilityStage extends AbilityStage {
onAcceptWant(want: Want): string {
// 根据 want.parameters 返回标识字符串
// 相同标识复用实例,不同标识创建新实例
return want.parameters?.key as string ?? ''
}
}
来源:
custom-canvas| Gitee harmonyos_samples | 源码级
| 功能 | 实现方式 |
|---|---|
| 自由绘制 | onTouch → Path2D.moveTo/lineTo |
| 橡皮擦 | strokeStyle = '#FFFFFF'(白色覆盖) |
| 撤回/重做 | drawPathList 数组 pop/push |
| 清空 | 清空 drawPathList + redoList 重绘 |
| 笔刷分类 | 圆珠笔(alpha=1固定)/马克笔(alpha可调) |
| 缩放 | pan 手势 → matrix4 变换 |
| 半模态弹窗 | bindSheet 选颜色/粗细/不透明度 |
// [API 23+] Canvas 绘制 + 撤销/重做
let drawPathList: Array<Path2D> = []
let redoList: Array<Path2D> = []
Canvas(this.context)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Move) {
let path = new Path2D()
path.moveTo(prevX, prevY)
path.lineTo(curX, curY)
drawPathList.push(path)
redoList = []
this.context.stroke(path)
}
})
来源:
PicturePreview| Gitee harmonyos_samples | 源码级
| 功能 | API | 说明 |
|---|---|---|
| 左右滑动切换 | Swiper + ForEach | 主图 + 缩略图双联动 |
| 双击缩放 | matrix4 变换 | 双击切换 1x ↔ 2x |
| 双指捏合 | Pinch 手势 + matrix4 | 自由缩放 |
| 平移查看 | translate + PanGesture | 放大后滑动查看 |
| 缩略图导航 | ThumbnailView + 索引联动 | 点击缩略图定位主图 |
| 全屏预览 | 轻触切换 overlay 模式 | 隐藏/显示 UI 元素 |
// [API 23+] matrix4 矩阵缩放手势
@State matrix: matrix4.Matrix4Transit = matrix4.identity().copy()
Image(this.imageData)
.transform(this.matrix)
.translate({
x: this.curOffsetX,
y: this.curOffsetY
})
.objectFit(ImageFit.Cover)
.gesture(
TapGesture({ count: 2 })
.onAction(() => {
let anim = this.matrix
animateTo({ duration: 300 }, () => {
this.matrix = matrix4.identity().scale({ x: scale, y: scale }).copy()
})
})
)
---
## 📜 BottomDrawerSlideCase 底部抽屉滑动(第四轮新增)
> **来源**:`bottom-drawer-slide-case` | Gitee harmonyos_samples | 源码级
利用 `List` 实现底部抽屉滑动效果,`RelativeContainer` + `Stack` 实现沉浸式全屏 + 地图可拖动。
### 核心原理
```typescript
// [API 23+] 分阶抽屉滑动
List({ space: 8 }) {
ForEach(this.items, (item: string) => {
ListItem() { Text(item) }
})
}
.onTouch((event: TouchEvent) => {
// 1. 记录 down / up 纵坐标
// 2. 按区间设置列表高度(三阶:10%/50%/90%)
// 3. 手指离开 → animateTo 吸附到最近阶
})
来源:
effect-kit| Gitee harmonyos_samples | 源码级
使用 EffectKit.colorPicker 对图片取色,作为背景渐变色的自适应方案。
// [API 23+] ColorPicker 取色主色调
import { effectKit } from '@kit.ArkGraphics2D'
async function getDominantColor(pixelMap: PixelMap): Promise<string> {
let colorPicker = effectKit.createColorPicker(pixelMap)
let color = colorPicker.getMainColorSync()
return `rgba(${color.red}, ${color.green}, ${color.blue}, 1)`
}
// Swiper.onAnimationStart → animateTo 渐变切换背景
来源:
grid-drag-sort| Gitee harmonyos_samples | 源码级
| 场景 | 操作 | 说明 |
|---|---|---|
| 同大小拖拽 | 长按 | 九宫格图片排序 |
| 不同大小拖拽 | 长按 | 设备展示区排序 |
| 直接拖拽 | 直接拖 | 无需长按 |
| 抖动编辑 | 长按后抖 | 可编辑提示 |
Grid + PanGesture + animateTo → 记录起始位置 → 交换数据
来源:
Immersive| ⭐26 Stars | Gitee harmonyos_samples | 源码级
| 方式 | API |
|---|---|
| 背景沉浸 | background() 延伸至状态栏 |
| 全屏沉浸 | ignoreLayoutSafeArea() + matchParent |
| 隐藏标题栏 | setWindowDecorVisible(false) |
| 避让挖孔 | 横竖屏 4 方向适配 |
来源:
KVStore| Gitee harmonyos_samples | 源码级
// [API 23+] KVStore 增删改查
import { distributedKVStore } from '@kit.ArkData'
let kvManager = distributedKVStore.createKVManager({
bundleName: 'com.example.app', context: getContext(this)
})
let kvStore = await kvManager.getKVStore('storeId', {
createIfMissing: true, encrypt: false,
securityLevel: distributedKVStore.SecurityLevel.S1
})
await kvStore.put('key1', 'value1') // 写
let val = await kvStore.get('key1') // 读
await kvStore.delete('key1') // 删
let all = await kvStore.getEntries() // 查全部
来源:
TextExpand| Gitee harmonyos_samples | 源码级
| 方案 | 技术 | 场景 |
|---|---|---|
| MeasureText | Canvas 2D 预排版 | 纯文本截断 |
| ParagraphBuilder | @ohos.graphics.text | 富文本截断 |
// ParagraphBuilder 检测超行
let paragraph = new text.ParagraphBuilder()
.pushStyle({ fontSize: 16 }).addText('长文本...').build()
paragraph.layout(maxWidth)
let isOverflow = paragraph.didExceedMaxLines()
来源:
ListScrollComponent(基于@hadss/scroll_components) | Gitee harmonyos_samples | 源码级
| 场景 | 技术方案 |
|---|---|
| 组件复用 | ListManager + registerNodeItem + wrapBuilder |
| 分组布局 | 分组列表 + 分组吸顶 + 二级联动 |
| 跨页面复用 | NodePool 全局复用池,切换页面不丢帧 |
| 加速首屏 | 组件分帧预创建 + 懒加载 |
| 下拉刷新/上拉加载 | onReachStart/onReachEnd |
| 无限滑动 | setDataSource 动态追加数据 |
| 侧滑删除 | swipeAction |
| 多类型列表项 | 根据 data 类型动态渲染不同 NodeItem |
| Tabs吸顶 | Tabs + List 组合 |
| 动态切换列数 | lanes 动态控制 |
| 边缘渐隐 | fadingEdge |
// [API 23+] 高性能长列表
import { ListManager, RecyclerView } from '@hadss/scroll_components'
@Builder
function MyItem($$: ESObject) {
Text($$.title).fontSize(16)
}
class MyListManager extends ListManager {
onWillCreateItem(index: number, data: ESObject) {
let node = this.dequeueReusableNodeByType('MyItem')
node?.setData({ title: data.title })
return node
}
}
// 组件中使用
RecyclerView({ viewManager: this.myListManager })
来源:
multi-file-download| Gitee harmonyos_samples | 源码级
@ohos.request 管理多个下载任务的进度和状态,List 展示下载队列。
// [API 23+] 多文件下载管理
downloadUrlArray.forEach((url, index) => {
let task = await request.agent.create(context, { url, saveas: `file_${index}.zip` })
tasks.push(task)
task.on('progress', (info) => { this.updateProgress(index, info) })
task.on('complete', () => { this.updateStatus(index, '完成') })
task.on('fail', () => { this.updateStatus(index, '失败') })
task.start()
})
// 全部开始 / 全部暂停 / 单个控制
来源:
MultiTarget| Gitee harmonyos_samples | 源码级
一套代码构建 different 版本应用包(official / test),资源/功能差异化定制。
// 工程级 build-profile.json5
{
"products": [
{ "name": "official", "signingConfig": "official_sign" },
{ "name": "test", "signingConfig": "test_sign" }
]
}
// 模块级: 为每个 target 定制 resources
// entry/src/main/official/resources/ vs entry/src/main/test/resources/
| 定制项 | 说明 |
|---|---|
| 资源文件 | 字符串/图片/颜色差异化 |
| 页面逻辑 | 不同版本独立 pages(official_pages / test_pages) |
| 功能开关 | Har 包内条件判断 |
来源:
SmoothSwitchShortVideos| Gitee harmonyos_samples | 源码级
| 优化 | 实现 |
|---|---|
| LazyForEach 懒加载 | 只渲染可见区域 + cachedCount 预缓存 |
| 组件复用 | Swiper 滑动 + @Reusable 组件复用 |
| AVPlayer 预创建 | 冷启动时创建一个播放器到 prepared 状态,每次滑动异步创建下一个 |
| XComponent Surface | Surface 类型动态渲染视频流,减少纹理拷贝 |
entry/src/main/ets/
├── model/AVDataSource.ets # 视频数据源
├── view/VideoPlayView.ets # 视频播放组件
├── pages/Index.ets # Swiper 轮播首页
└── common/CommonConstants.ets # VIDEO_SOURCE 配置
来源:
multi-tab-navigation| ⭐149 Stars | Gitee harmonyos_samples | 源码级
| 样式 | 实现方式 | 特点 |
|---|---|---|
| 常见底部导航 | Tabs + barPosition.End | 底部图标+文字 |
| 舵式底部导航 | Tabs + 自定义中间按钮 | 中间突出舵式按钮 |
| 下划线样式 | Tabs + indicator | 指示器跟随滑动 |
| 背景高亮样式 | List 实现 | 选中项背景高亮 |
| 文字样式 | Tabs + fontColor | 文字粗细/颜色变化 |
| 双层嵌套1 | Tabs + 内嵌 List | 外层Tab切换,内层List滚动 |
| 双层嵌套2 | Tabs 嵌套 Tabs | 两层 Tab 联动 |
| 侧边导航 | SideBarContainer | 左侧图标+文字 |
| 抽屉式导航 | SideBarContainer | 点击左上角展开/折叠 |
| 可滑动+更多 | List + 右侧图标 | 溢出滑动,显示更多入口 |
// [API 23+] 常见底部导航
Tabs({ barPosition: BarPosition.End }) {
ForEach(this.tabItems, (item: TabItem) => {
TabContent() {
// 对应页面内容
}.tabBar(this.TabBuilder(item))
})
}
.width('100%').height('100%')
@Builder TabBuilder(item: TabItem) {
Column() {
Image(item.icon).width(24).height(24)
Text(item.name).fontSize(10)
}
}
// 双层嵌套: Tabs + 内嵌 List
// 抽屉导航: SideBarContainer({ sideBarWidth: 240 })
来源:
custom-dialog-gathers| ⭐64 Stars | Gitee harmonyos_samples | 源码级
| 弹窗类型 | 实现 API | 说明 |
|---|---|---|
| 滑动选择 | TimePickerDialog / TextPickerDialog | 系统选择器弹窗 |
| 模态弹窗 | bindContentCover | 全屏模态,3种过渡方式 |
| 半模态弹窗 | bindSheet | 底部半模态 |
| Toast弹窗 | CustomDialog | 3s自动消失 |
| 隐私协议弹窗 | Stack visibility 控制 | 弹窗内跳转不关闭 |
| 全屏弹窗 | translate + 显式动画 | 手势滑动关闭 |
| 日历选择器 | CustomDialog + LazyForEach | 自定义日期范围 |
| 两级半模态 | bindSheet 嵌套 bindSheet | 二级弹窗联级关闭 |
// [API 23+] 模态弹窗 bindContentCover
@State isPresent: boolean = false
Button('打开模态')
.bindContentCover($$this.isPresent, {
builder: () => this.ModalPage(),
showPageTransition: true // 页面过渡动画
})
// 半模态弹窗 bindSheet
Button('打开半模态')
.bindSheet($$this.isSheetPresent, {
builder: () => this.SheetPage(),
mode: SheetMode.HALF // 半屏模式
})
// FullScreen 手势滑动关闭
.gesture(
PanGesture()
.onActionUpdate((event) => { this.offsetY = event.offsetY })
.onActionEnd(() => {
if (this.offsetY > 300) { this.isFullScreen = false }
else { animateTo({}, () => { this.offsetY = 0 }) }
})
)
来源:
avplayer-long-video| Gitee harmonyos_samples | 源码级
| 功能 | 说明 |
|---|---|
| 基本播控 | play/pause/stop/seek |
| 精准跳转 | 按时间/帧精确跳转 |
| 倍速播放 | 0.5x~3x 倍速调节 |
| 音量控制 | 系统音量/应用音量 |
| 亮度控制 | 屏幕亮度滑动调节 |
| 焦点管理 | 播放焦点获取/释放 |
| 前后台感知 | 前台播放/后台暂停 |
| 弹幕发送与显示 | 实时弹幕叠加 |
| 字幕挂载 | 多字幕轨道切换 |
| 视频截图 | AVPlayer snapshot |
| 画中画 | PiP 模式播放 |
| 媒体会话 | AVSession 播控集成 |
"ohos.permission.INTERNET"
来源:
HMOS_LiveAudioCall| Gitee harmonyos_samples | 源码级
在 HMOSLiveStream 案例基础上增加媒体直播连麦功能:
主播端: 采集 → 编码 → 推流
连麦端: 采集 → 编码 → 混流 → 推流
观众端: 拉流 → 解码 → 播放
来源:
PixelMapImageEdit| Gitee harmonyos_samples | 源码级
| 分类 | 功能 | 实现方式 |
|---|---|---|
| 几何变换 | 裁剪/旋转/平移/缩放/镜像 | PixelMap 几何 API |
| 颜色调整 | 亮度/透明度/饱和度 | PixelMap 颜色 API |
| 滤镜 | 多种滤镜效果 | 颜色矩阵变换 |
| 解码 | 图片 → PixelMap | image.createImageSource + createPixelMap |
| 编码 | PixelMap → 图片文件 | image.createImagePacker + packToFile |
// [API 23+] PixelMap 编辑流程
import { image } from '@kit.ImageKit'
// 1. 解码
let source = image.createImageSource(uri)
let pixelMap = await source.createPixelMap()
// 2. 裁剪
pixelMap.crop({ x: 0, y: 0, width: 500, height: 500 })
// 3. 颜色调整
pixelMap.opacity(0.8) // 透明度
// pixelMap.setBrightness(0.2) // 亮度
// pixelMap.setSaturation(1.5) // 饱和度
// 4. 编码保存
let packer = image.createImagePacker()
let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 95 }
await packer.packToFile(pixelMap, `${getContext().cacheDir}/edited.jpg`, packOpts)
来源:
MultiBusinessOffice| Gitee harmonyos_samples | 源码级
| 页面 | 直板机 | 折叠屏(展开) | 平板 |
|---|---|---|---|
| 入口页 | 导航跳转 | 自适应布局 | 自适应布局 |
| 备忘录 | 单栏 | SideBar + 双栏 | 双栏展开 |
| 日历 | 上下显示 | 侧边栏+内容 | 双栏日历 |
| 能力 | 实现 |
|---|---|
| 侧边栏适配 | SideBarContainer.sideBarContainerType + showSidebar 断点控制 |
| 单双栏切换 | Navigation.mode 根据断点改变 NavigationMode |
| 页面拉起 | UIAbilityContext.startAbility() 拉起新实例 |
| 断点监听 | BreakpointSystem 监听 sm/md/lg |
// [API 23+] SideBarContainer 断点适配
SideBarContainer({ sideBarWidth: 240, type: this.sideBarType }) {
SideBarContent()
MainContent()
}
.showSidebar(this.showSidebar)
.onChange((isShow) => { this.showSidebar = isShow })
// 断点控制
// sm: 隐藏+悬浮 → showSidebar=false, type=OVERLAY
// md: 隐藏+悬浮 → showSidebar=false, type=OVERLAY
// lg: 展开+嵌入 → showSidebar=true, type=EMBED
来源:
preferences| Gitee harmonyos_samples | 源码级
// [API 23+] @ohos.data.preferences 首选项
import { preferences } from '@kit.ArkData'
let prefs = await preferences.getPreferences(getContext(), 'myStore')
// 写
await prefs.put('theme', 'dark')
await prefs.flush() // 必须 flush 才持久化
// 读
let theme = await prefs.get('theme', 'default')
// 删
await prefs.delete('theme')
// 删除整个 Store
await preferences.deletePreferences(getContext(), 'myStore')
主题切换 + 数据缓存:切换主题 → put + flush → 重启应用 → get 恢复
来源:
list-exchange| ⭐15 Stars | Gitee harmonyos_samples | 源码级
List 列表项的交换与删除。
// [API 23+] 列表项交换
List() {
ForEach(this.items, (item: string, index: number) => {
ListItem() {
Text(item)
}
.gesture(
LongPressGesture().onAction(() => {
this.draggedIndex = index
})
)
.onDrag((event: DragEvent) => {
event.setData(DragUtils.createData(this.items[index]))
})
.onDrop((event: DragEvent) => {
let targetIndex = this.getIndexByPosition(event)
if (targetIndex !== -1) {
let temp = this.items[this.draggedIndex]
this.items[this.draggedIndex] = this.items[targetIndex]
this.items[targetIndex] = temp
}
})
})
}
来源:
image-comment| Gitee harmonyos_samples | 源码级
├── utils/CameraUtils.ets # 拉起系统相机
├── view/CommentItemView.ets # 评论列表组件
├── view/CommentInputDialog.ets # 发布评论弹窗
└── view/ImageListView.ets # 图片列表组件
// [API 23+] 拉起系统相机
import { camera } from '@kit.CameraKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
async function takePhoto(context): Promise<string> {
// 使用 Camera Kit 或系统相机应用取景拍照
let helper = photoAccessHelper.getPhotoAccessHelper(context)
let uri = await helper.takePhoto(context, '拍照')
return uri
}
来源:
water-flow| Gitee harmonyos_samples | 源码级
| 场景 | 功能 | 实现 |
|---|---|---|
| 场景一 | 分组混排 + 下拉刷新 + 无限加载 + 长按删除 | WaterFlow.sections 分组配置 |
| 场景二 | item 吸顶 + 图片视频混排 + 停止自动播放 | FlowItem + Tabs + 滚动监听 |
// [API 23+] WaterFlow sections 分组混排
WaterFlow() {
LazyForEach(this.sections, (section: WaterFlowSection) => {
FlowItem() {
// 每个 flow item 内容
}
})
}
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
// sections 配置不同列数和间距
// [{ crossCount: 2 }, { crossCount: 3 }, { crossCount: 1 }]
来源:
grid-hybrid| Gitee harmonyos_samples | 源码级
| 场景 | 组合 | 实现 |
|---|---|---|
| 场景一 | Grid + List 嵌套 | 双 Scroller 联动 + 水平 List 吸顶 |
| 场景二 | Grid + Swiper 嵌套 | Swiper 动态改变高度 |
来源:
list-item-edit| Gitee harmonyos_samples | 源码级
基于 List 实现待办事项管理:添加/左滑删除/checkbox 完成。
// [API 23+] 列表项左滑删除
ListItem() {
Text(this.item.name)
}
.swipeAction({
end: { builder: () => {
Button('删除').onClick(() => {
this.todoList.splice(index, 1)
})
}}
})
来源:
H5Launch| Gitee harmonyos_samples | 源码级
| 方案 | API | 说明 |
|---|---|---|
| 内核预初始化 | initializeBrowserEngine() | 提前初始化 ArkWeb 内核 |
| 预连接预解析 | prepareForPageLoad() | DNS 预解析 + TCP 预连接 |
| 资源拦截替换 | onInterceptRequest + DataCache | 首屏数据本地缓存 |
| JS 预编译 | precompileJavaScript() | 生成字节码 Code Cache |
| 离线资源注入 | injectOfflineResources() | 免拦截注入静态资源 |
// [API 23+] Web 冷启动优化
import { webview } from '@kit.ArkWeb'
// AbilityStage 中提前初始化
webview.initializeBrowserEngine()
// 预连接
webview.prepareForPageLoad('https://example.com')
// 资源拦截
Web({ src: 'https://example.com', controller: this.controller })
.onInterceptRequest((event) => {
// 从本地 DataCache 返回快照数据
if (isFirstScreen) { return localCacheData }
return null
})
以下仓库功能太简单或被已有内容覆盖,仅做推荐索引:
| 仓库 | 说明 | 覆盖情况 |
|---|---|---|
| multi-travel-accommodation | 多设备旅行住宿页面 | 🟢 被 MultiBusinessOffice 覆盖 |
| NavigationSettings | 导航设置页面 | 🟢 被 MultiTabNavigation 覆盖 |
| PureTabs | 纯 Tabs 实现 | 🟢 被 MultiTabNavigation 覆盖 |
| custom-dialog-selection | 自定义弹窗选择 | 🟡 被 CustomDialogGathers 覆盖 |
| grid-hybrid | 已学 ✅ | ✅ |
| text-effects | 已学 ✅ | ✅ |
| water-flow | 已学 ✅ | ✅ |
| list-item-edit | 已学 ✅ | ✅ |
| H5Launch | 已学 ✅ | ✅ |
| FIDO2 | 已学 ✅ | ✅ |
| location-service | 已学 ✅ | ✅ |
华为官方 700+ 示例中精选 最有学习价值的 10 个,对标实际开发场景。
| Sample | 核心知识点 | 适用场景 |
|---|---|---|
| 全链路盯盘 | 闪控球+悬浮窗+锁屏卡片+屏保卡片+防窥保护 | 金融/监控类 |
| 互动卡片 | Live Form 互动卡片能力 | 桌面卡片开发 |
| 沉浸光感 | HdsNavigation+HdsTabs+悬浮导航 | UI 现代化改造 |
| 多设备长视频 | 一多架构(三层)+自适应布局+折叠屏/平板/PC | 视频类 App |
| 多设备音乐 | 一多架构+迷你播控+全屏播放+穿戴适配 | 音乐类 App |
| 多设备短视频 | 一多架构+手表适配+评论页+个人作品页 | 短视频类 App |
| 多设备社区评论 | 一多架构+图片预览+社区详情页 | 社区/社交类 |
| 自由流转社交协同 | 应用接续+分布式数据对象+跨设备拖拽+碰一碰 | 协同办公 |
| 媒体直播 | 音视频采集+播放+音频焦点+画面翻转+背景音乐 | 直播类 App |
| 威胁防护文件扫描 | 企业威胁防护+文件隔离/恢复 | 企业安全类 |
一多架构推荐模式:
三层架构:
┌─ commons/ ← 公共能力(网络/工具/UI组件)
├─ services/ ← 业务逻辑(数据管理/状态管理)
└─ views/ ← 页面视图(按设备形态适配)
| # | 错误现象 | 错误码 | 原因 | 修复方案 |
|---|---|---|---|---|
| 1 | Notification type error | — | ContentType 类型不兼容 | cast 为 number 类型 |
| 2 | Window type error | — | window.getLastWindow 类型推断问题 | 改用回调模式 |
| 3 | AppStorage type error | — | AppStorage.get() 类型推断错误 | 用 @StorageLink + AppStorage.setAndLink,避免 setOrCreate |
| 4 | Object spread error | — | 对象展开类型推断受限 | 显式声明类型 |
| 5 | @StorageLink default value | — | @StorageLink 属性缺少默认值 | 加 = undefined 或具体默认值 |
| 6 | Object literal interface error | 10605038 | 对象字面量缺少显式接口 | 先定义 interface 再使用 |
| 7 | Function return type error | 10605090 | 返回类型推断受限 | 添加显式返回类型注解 |
| 8 | Arrow function conversion | — | 用了 function 表达式 | 改为箭头函数 => |
| 9 | Color property error | — | 使用了不存在的 Color 属性 | 用十六进制色值 |
| 10 | Interface method signature | — | 方法签名与对象字面量不匹配 | 用属性语法 method: () => {} |
| 11 | AvoidArea type error | — | AvoidArea 缺少 visible 属性 | 加 visible: false |
| 12 | Standalone function this | — | 独立函数中使用 this | 通过参数传递 context |
| 13 | TitleButtonRect type error | — | getTitleButtonRect 返回了 window.Rect | 用 window.TitleButtonRect |
| 14 | Catch clause type | — | catch 中有类型注解 | 移除类型注解或用 unknown |
| 15 | ESObject type error | — | 使用了 ESObject | 改用具体类型 |
| 16 | Resource conversion | — | Resource→string/number 转换错 | 在 UI 组件内直接使用 Resource |
| 17 | Unused variable warning | — | 声明了未使用的变量 | 删除或输出到 hilog |
| 18 | IDataSource type error | — | LazyForEach 需要 IDataSource | 实现 IDataSource 接口(4个方法) |
| 19 | Duplicate @Entry | — | 同一文件中有多个 @Entry | 去掉多余的 @Entry,子组件用 @Component |
| 20 | Possibly null error | — | 访问可能为 null 的对象的属性 | 用 !== null 检查或可选链 ?. |
| 21 | Cannot find module | — | import 路径错误 | 检查模块路径和 export 名称 |
| 22 | Getter or setter unexpected | — | 属性定义使用了 get/set 关键字 | ArkTS 不支持 getter/setter |
| 23 | arkts-no-untyped-obj-literals | 10605038 | 对象字面量必须对应显式类或接口 | 定义 interface/class;或用空对象逐个赋值 const p:Record<string,string>={}; p['k']='v'; |
| 24 | arkts-no-implicit-return-types | 10605090 | 函数返回类型推断受限 | 所有函数加显式返回类型注解 |
| 25 | arkts-no-any-unknown | 10605008 | 不能用 any/unknown | 全部替换为具体类型 |
| 26 | arkts-no-inferred-generic-params | 10605034 | 泛型函数调用需要显式类型参数 | 所有泛型调用显式标注 <object>:api.get<object>() |
| 27 | arkts-no-obj-literals-as-types | 10605040 | 不能用对象字面量声明类型 | 改为 interface 定义:interface Param { username: string } |
| 28 | Record<string, object> 类型不匹配 | 10605999 | Record<string, object> 不能放基础类型值 | 定义具体接口代替通用的 Record |
| 29 | 索引对象属性 | — | 用变量键名索引对象 colors[key] | 声明为 Record<string, T> 或用 switch/case |
| 30 | for...in 循环 | — | 使用 for (let key in obj) | 改用 Object.keys(obj).forEach(key => {...}) |
| 31 | 接口属性不匹配 | — | 对象字面量包含接口不存在的属性 | 只赋值接口已定义的属性 |
| 32 | interface 在 struct 内 | 10505001 | interface 声明在 struct 内部(ArkTS 禁止) | 移到文件级别(@Entry 之前) |
| 33 | Only UI component syntax | 10905209 | const/let 声明在 build() 组件树中 | 移到方法中计算或内联表达式 |
| 34 | Property on void | 10505001 | @Builder 方法链式 .onClick() | 用 Column() 包裹后再链式 |
| 35 | Property on TextAttribute | 10505001 | Image 属性迁移到 Text 后未改 | 用 .fontColor() 替代 .fillColor() |
| 36 | Unknown resource name | 10903329 | ohos_ic_* 系统资源不存在 | 用 Text('🔔'/'↻') 替代 |
| 37 | build() 多出/缺少'}' | — | 编码损坏导致 brace 不匹配 | 扫描全文件 brace 平衡 |
| 38 | Property 'onRefresh' does not exist. Did you mean 'onRefreshing'? | 10505001 | Refresh 组件回调名称写错 | .onRefresh( → .onRefreshing( |
| 39 | @State opacity 与基类冲突 | 10505001 | opacity 是 CustomComponent 基类方法 | 改名为 shimmerOpacity / alpha |
当项目出现 50+ 编译报错时,大部分可能是级联错误。按以下顺序排查(优先级从高到低)。
⚡ 一键诊断:运行诊断脚本自动扫描全部 7 项:
bash bash <expert-dir>/scripts/diagnose_arkts.sh <项目根目录>脚本在
harmonyos-dev/scripts/diagnose_arkts.sh,扫描结果按优先级排序输出。
错误现象:Declaration or statement expected + Cannot find name 'build'
排查命令:
grep -n '^\s\+interface ' *.ets
根因:ArkTS 禁止在 struct {} 内部声明 interface,编译器看到 interface 关键字时解析失败,从该行开始直到文件末尾全部报错,可产生 400-600+ 级联错误。
修复:将 interface 移到文件级别(@Entry 之前),与 RouteArgs 同级。
错误现象:Declaration or statement expected + 大量语法错误
排查命令:
awk 'NR>=18{indent=substr($0,1,1);if(indent~/^[a-zA-Z@]/) print NR": "$0}' *.ets
根因:编码损坏导致行首空格丢失,代码被解析为文件级代码而非 struct 成员。 修复:为每行恢复正确的缩进(struct 内至少 2 空格)。
错误现象:Cannot find name 'width' + struct 边界错误
排查命令:
o=$(grep -o '{' file.ets | wc -l); c=$(grep -o '}' file.ets | wc -l); echo $((o-c))
根因:±1 的差异可产生 100-400 个级联错误。
修复:在正确的层级补上缺失的 } 或删除多余的 {。
build() 语法违规错误现象:Only UI component syntax can be written here
ArkTS build() 语法清单:
| ✅ 允许 | ❌ 禁止 |
|---|---|
UI 组件:Column() Row() Text() Button() 等 | const/let/var 变量声明 |
控制流:if/else ForEach LazyForEach | 函数调用赋值(const x = compute()) |
@Builder 方法调用:this.MyBuilder() | try/catch/finally |
链式属性:.width() .height() .onClick() | 解构赋值(TS→ArkTS 迁移) |
| 条件表达式(三元) | for/while 循环 |
数据绑定:{{ }} this.state | console.log(用 hilog 替代) |
常见违规修复:
// ❌ 错误 - const 在 build() 组件树中
build() {
Column() {
const x: number = this.compute(); // 10905209
Text('' + x)
}
}
// ✅ 正确 - 用方法计算
build() {
Column() {
Text('' + this.compute())
}
}
@Builder 链式调用错误现象:Property 'onClick' does not exist on type 'void'
根因:@Builder 方法返回 void,不能在返回值上链式调用。
// ❌ 错误
this.ChartTabItem().onClick(() => { ... }) // void 上没有 onClick
// ✅ 正确
Column() { this.ChartTabItem() }.onClick(() => { ... })
Text() 参数类型错误现象:Argument of type '() => string' is not assignable to parameter of type 'string | Resource'
根因:ArkTS Text() 构造函数接受 string | Resource,不接受箭头函数。
// ❌ 错误
Text(() => { if (!x) return '--'; return x.toFixed(2); })
// ✅ 正确
Text(!x ? '--' : x.toFixed(2))
| 迁移场景 | ❌ 老属性 | ✅ 新属性 |
|---|---|---|
Image → Text | .fillColor() | .fontColor() |
Image → Text | .width(22) .height(22) | 单字符无需显式尺寸 |
| 系统资源不存在 | $r('sys.media.ohos_ic_xxx') | Text('🔔'/'↻'/'←') 或本地资源 |
Record 对象字面量 | const x: Record<K,V> = { k: v } | 改用 Record<K,V> = {}; x['k'] = v |
项目背景:QuantFlow 是一个鸿蒙股票分析应用,大量代码由 AI 生成。首次编译时报出 20 个错误,全部集中在 TS→ArkTS 类型系统差异。
| 错误码 | 错误名 | 出现次数 | 涉及文件 |
|---|---|---|---|
| 10605038 | arkts-no-untyped-obj-literals | 11次 | ProfilePage/LoginPage/RegisterPage/StockDetailPage |
| 10605090 | arkts-no-implicit-return-types | 6次 | StockDetailPage |
| 10605008 | arkts-no-any-unknown | 4次 | StockDetailPage |
api 对象(第183行)没有明确的类型声明,导致所有调用方都报对象字面量错误api.get() 应写成 api.get<object>(){ username: string; password: string } 这种写法在 ArkTS 中禁止Api interface 声明 api 对象类型LoginData、RegisterData、CommentData 等数据接口<object> 类型参数const p: Record<string, string> = {}; p['key'] = 'val' 逐个赋值httpClient.get() 必须写成 httpClient.get<object>()Record<string, object> 不能存基础类型,必须用具体接口any/unknown 在 ArkTS 中完全禁止,全部替换为具体类型问题:在 http.ts 中 AppStorage.get<string>('token') 报错 @kit.ArkUI 没有导出 AppStorage。
排查过程:
@kit.ArkUI 导入 AppStorageV2 → AppStorageV2 没有 get 方法http.ts 重命名为 http.ets → 引发大量新类型错误(ArkTS 严格模式)http.ts,用全局变量替代 AppStorage,在登录页同步 token根因:
.ets 文件中 AppStorage 是全局可用的,无需 import.ts 文件中 AppStorage 不可用(它是 ArkTS 编译器的内置全局对象)@kit.ArkUI 导入 AppStorage 时,须使用 AppStorageV2正确实践:
// http.ts(普通 TypeScript 文件)
let cachedToken: string = '';
export function setToken(t: string) { cachedToken = t; }
export function getToken(): string { return cachedToken; }
// LoginPage.ets(ArkTS 文件,AppStorage 全局可用)
AppStorage.setOrCreate('token', '');
setToken(AppStorage.get<string>('token')!);
教训:.ts 和 .ets 的模块系统不同,.ts 不能访问 ArkTS 特有的全局对象。跨文件共享状态时,需要在 .ets 中获取后同步到 .ts
问题:153 个编译报错,根因是文件编码损坏 + 组件 API 使用错误。
| 组件 | 错误用法 | 正确用法 |
|---|---|---|
| Row | .alignItems(HorizontalAlign.Center) | Row 用 VerticalAlign,Column 用 HorizontalAlign |
| Toggle | .isOn(true) 属性赋值 | .selected(true) 或 onChange((v) => this.checked = v) |
| DataTransform | 返回类型 Object[] | 必须用具体类型 NewsStockRef[] |
VerticalAlign(Top/Center/Bottom)HorizontalAlign(Start/Center/End).selected() 属性设置,非 .isOn()问题:659 个编译错误,"全新"项目代码(非编码损坏),根因是 ArkTS 语法限制。
| 语法 | 错误用法 | 正确替代 |
|---|---|---|
| 索引访问对象属性 | colors[key] 对普通对象做索引访问 | 改用 Record<string, string> 或 switch/case |
| for...in 循环 | for (let key in obj) | 改用 Object.keys(obj).forEach(key => {...}) |
| 接口中不存在的属性 | MockData 对象字面量包含 amount 属性 | 检查接口定义,只赋值已有属性 |
ColorTheme.ets 第65行:colors[key] — ArkTS 不支持通过变量键名索引访问对象属性,必须显式声明类型为 Record<string, T>StrategiesViewModel.ets 第88行:for (let key in obj) — ArkTS 禁止 for...in 循环,这是因为它依赖动态运行时反射MockData.ets:对象字面量赋值了接口不存在的属性 amount — ArkTS 严格检查对象字面量的属性完整性for...in 是 "从 TypeScript 迁移时 AI 最常生成的错误代码" 之一(与解构赋值 {}、any 并列前三)const colors: Record<string, string> = { 'red': '#ff0000' }; const c = colors['red'];新发现:
import 语句位置错误导致装饰器截断:Index.ets 中 import 放在了错误位置,导致其后的 @Entry/@Component 装饰器被截断——这是 "伪语法错误" 的另一个常见来源TradingPage.ets(400+行)含大量中文字符串时,AI 的 builtin_write_file 工具 JSON 序列化会报错(invalid escape character)经验:
import 语句是否完整、位置是否正确import 之间不能有空行或注释插入(某些 ArkTS 版本限制)新发现:
borderBottomWidth 不是 Column 有效属性:Column 组件没有 borderBottomWidth 属性,需用 .border({ bottom: { width: 1 } }) 替代build() 方法的组件树内部(如 List() { ... } 内)不能 const x = ...,必须用成员方法或提前计算{} 也需要显式类型:即使空对象 response.result as (Record<string, Object>) 也需要先声明显式类型经验:
borderBottomWidth/borderTopColor 等),统一用 border() 方法build() 方法中所有变量声明必须放在 @Builder 或 build() 最外层,不能在嵌套 UI 组件内部声明新发现:
export 修饰符缺失:页面组件(QuotesPage/StrategiesPage/BacktestPage/ProfilePage)的 @Component struct 前缺少 export,导致无法被其他模块 import经验:
internal(模块内可见),需要被其他模块引用时须加 exportexport structresponse.result as any 无法解决问题,必须转换为 response.result as Record<string, Object>| 坑 | 错误表现 | 根因 | 修复 |
|---|---|---|---|
| Column+Row 尺寸失控 | TextField 撑不满父容器 | Row 默认 flexStart 不拉伸子元素 | 显式设 width:.width('60%') |
| Button 点击被拦截 | onClick 不触发 | GestureDetector 优先级更高 | 别在 Button 上叠 GestureDetector |
| TextField 失去焦点 | focus() 无效 | 手动设了 focusable(false) | 默认就是 true,不要乱改 |
| Image + Text 渲染错乱 | 图片显示在文字后面 | Image 没设 width/height | 所有非文本组件必须显式设尺寸 |
| @Prop 修改不触发刷新 | 改了 user.name 视图不动 | @Prop 是值副本,非引用 | 用 @Link 或事件回调 |
ForEach(arr, item => {}, item => item.id),否则更新异常aboutToDisappear() 中必须释放图片缓存、传感器、事件监听器width('100%'),不是默认行为事件:用 sed + Python 批量替换 pet_review_harmony 项目中 23 个文件的 router.pushUrl → getUIContext().getRouter().pushUrl,导致:
import { router } 时误删了第 1 行,后续代码没有被影响——这个没问题, router.RouterMode.Standard) 时多删了 pushUrl 的闭括号 ),导致 30 个文件出现 pushUrl({ url: '...' };r"pushUrl\(\{ url: '([^']+)' \}" 过于贪婪,又加了一个多余的 } → pushUrl({ url: '...' }});教训:
git grep -n 'pattern' | wc -l 统计每个变体,逐一确认差异,一个一个文件改此章节由专家在每次对话结束后自动追加。记录了所有实战中踩过的坑、学到的经验。
D:/HUAWEI/开发资料/MultiVideoApplication-master/// 根据当前断点返回不同配置值,消除大量 if-else
class BreakpointType<T> {
constructor(xs: T, sm: T, md: T, lg: T, xl: T) { ... }
getValue(breakpoint: WidthBreakpoint): T { ... }
}
// 用法:new BreakpointType(1, 2, 3, 4, 6).getValue(currentBp)
createAvPlayer() 异步操作完成但状态已过期sessionId;异步操作开始前记录 sessionId,每个关键步骤检查 isSessionActive(sessionId)bumpSession() 递增 + if (!this.isSessionActive(sessionId)) return; 守卫WindowInfo 使用 @Observed 装饰,通过 @StorageLink 在组件间共享,窗口变化自动刷新 UI\u0000-\u001F\u007F)+ 长度截断(MAX_INPUT_LENGTH=50)canIUse('SystemCapability.Window.SessionManager') — 运行时判断折叠屏能力是否可用,PC/大屏设备优雅降级display.on('foldStatusChange', ...) — 监听折叠状态变化(展开/折叠/半折叠),联动全屏切换和布局重排HarmonyOS_Samples/application-trackuiContext.getUIObserver().on('willClick', cb) — 全局点击监听,无需在每个组件上绑 onClick 埋点。结合 node.getCustomProperty() 可获取组件自定义业务数据node.getCustomProperty(key) + .customProperty(key, value) — 为组件附加业务数据(埋点上下文),埋点框架通过 node.getId() 关联获取onDidBuild() 生命周期 + this.getUIContext().getFrameNodeByUniqueId(uid) — 构建后立即获取当前组件的 FrameNode,注册可见区域监听setOnVisibleAreaApproximateChange({ ratios, expectedUpdateInterval }, callback) — 组件曝光埋点的核心 API,按比例(0/0.5/1)和最小间隔回调可见区域变化TrackNode @BuilderParam 包装组件 — 用泛型包装器包裹任意子组件,自动注入埋点逻辑,实现非侵入式埋点hiAppEvent.addWatcher({ name, appEventFilters, triggerCondition, onTrigger }) — 事件消费端,可批量拉取埋点数据(按行数/大小/超时触发)onClick 埋点,UIObserver 模式做到 一次注册、全局生效,业务代码零侵入TrackNode 的 @BuilderParam 模式是 ArkTS 中 高阶组件 的标准实现方式interface MACDResult 和 interface KDJResult 声明在 struct StockDetailPage {} 内部,ArkTS 禁止 interface 作为 struct 成员 → 400+ 级联报错@Builder 方法返回 void,this.ChartTabItem().onClick(...) 无效,需用 Column() { this.ChartTabItem() }.onClick(...) 包裹build() 组件树内不能出现 const/let 声明(error 10905209),需提到方法中计算或内联Text() 构造函数不接受箭头函数 () => string,需改为三元表达式{ k: v } 即使有 Record<K,V> 注解也不够,需用 Record<K,V> = {}; x['k'] = vohos_ic_public_notification/ring/reset 为 Text('🔔'/'↻').fillColor() 设色调,Text 没有此属性,应使用 .fontColor()Image($r('sys.media.*')).fillColor(c) → Text('🔔').fontColor(c)onRefresh + 1 处 opacity 共 8 个编译错误Refresh 组件的下拉回调是 .onRefreshing(),不是 .onRefresh()。编译器提示 Did you mean 'onRefreshing'?SkeletonCard 中 @State opacity: number 与基类 CustomComponent 的 .opacity() 链式方法冲突(与 width/height 同类问题),重命名为 shimmerOpacity 解决opacity 与 width/height/borderRadius 同属基类冲突属性.onRefresh( → .onRefreshing((5 页 7 处 replace_all);opacity → shimmerOpacity(@State + animateTo 引用同步更新)r\"'[^']*\\?[^']*'\" 清除乱码? 的正常字符串(三元表达式、模板字符串)被替换为空 '',错误数从 ~30 暴增到 1398? 和合法代码中的 ?const detail = item.detail 报错 10905209;Index 行 212 if (route) {} 报错 "Statement or declaration expected"goToDetail() / navigateTo()),在 onClick 中调用Column(){} Row(){} Text() 和 .onClick(() => { this.xxx() }),不能写 const/let/if/forreview.username、review.ratingusername/rating,ArkTS 版 Review 接口是嵌套 review.user.username 和 review.scorereview.username → review.user ? review.user.username : '匿名用户',review.rating → review.score@Prop width: string / @Prop height: number / @Prop borderRadius: number@Component struct 继承自 CustomComponent,基类已有 .width() .height() .borderRadius() 方法imageWidth / imageHeight / imageRadiusimport { AppStorage } from '@kit.ArkUI' 报错 "has no exported member named 'AppStorage'"AppStorageV2;且 .ts 文件本身不能访问 ArkTS 全局对象cachedToken 替代,在 .ets 文件中同步 token 值NavHeader({ title: string }) 组件,17 个页面替换,消除 ~187 行重复代码@Component + @Prop 组合非常适合创建共享 UI 组件,比 Vue 的 slot/插槽更直接LiveCard(2,194下载)+ 51CTO + CSDN 技术文章ImageEditWithWavewheel(2,256下载),补齐最后缺口来源:华为开发者官网 + HDC 2026 + 51CTO 实战文章
| 方向 | 核心能力 | 影响范围 |
|---|---|---|
| 智能化 | Skill/VibeCoding + Agent架构 + 视觉AI | 应用→智能体→Skill 三级演进 |
| 空间化 | 沉浸光感 + 3DGS端侧重建 + 空间音频 | 手机/平板/VR/车机统一虚实融合 |
| 全场景 | 碰一碰精准分享 | 手机触屏→电脑/平板精准插入 |
| 媒体 | 空间音频引擎(降噪/变声/渲染节点组合) | 直播/音乐/导航声道分离 |
| 多窗 | 互动卡片 + 闪控窗(球/侧边栏/悬浮) | 桌面交互范式重构 |
| 安全 | 星盾机密风控 + 分布式数字身份(DID) + 数字盾 | 银行/支付/企业签章 TEE级保护 |
开发路径:应用智能化 → Agent化 → Skill化
| 形态 | 说明 | 交互 |
|---|---|---|
| 悬浮窗 | 正常浮动窗口 | 可拖动/调整大小/输入 |
| 侧边栏 | 拖到边缘自动吸附 | 不占空间随时拉出 |
| 闪控球 | 缩成悬浮小球 | 点击展开,无操作5秒半透明 |
权限:ohos.permission.SYSTEM_FLOAT_WINDOW(system_grant)
| 升级 | 说明 |
|---|---|
| 游戏快启 | 游戏启动速度优化 |
| 鸿蒙内核应用快启 | 内核级应用启动优化 |
| 冷启网络预建链 | 应用启动前预建网络连接 |
| QUIC 长连接 | 基于UDP的长连接保活 |
| 弱网直播优化 | 直播弱网场景专项优化 |
| LTPO可变帧率 | 屏幕帧率动态调整省电 |
碰一碰精准分享:
import http from '@ohos.net.http';
// 创建请求实例(建议在组件生命周期内管理)
const httpRequest: http.HttpRequest = http.createHttp();
// ✅ GET 请求
async function getRequest(url: string): Promise<object> {
const response = await httpRequest.request(url, {
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' },
expectDataType: http.HttpDataType.OBJECT,
usingCache: true,
});
if (response.responseCode === http.ResponseCode.OK) {
return response.result as object;
}
throw new Error(`HTTP ${response.responseCode}`);
}
// ✅ POST 请求
async function postRequest(url: string, data: object): Promise<object> {
const response = await httpRequest.request(url, {
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify(data),
expectDataType: http.HttpDataType.OBJECT,
});
return response.result as object;
}
// ✅ 全局请求头设置
httpRequest.setExtraHeaders({
'User-Agent': 'HarmonyOS-App/1.0',
'Accept-Language': 'zh-CN,zh;q=0.9',
});
// ⚠️ 组件销毁时释放资源
onDetach(() => { httpRequest.destroy(); });
import webSocket from '@ohos.net.webSocket';
const ws = webSocket.createWebSocket();
ws.connect('wss://server.com/ws', (err) => {
if (!err) { ws.send('Hello'); }
});
ws.on('message', (err, data) => { /* 处理消息 */ });
ws.on('error', (err) => { /* 错误处理 */ });
ws.close(() => { ws.destroy(); });
| 方案 | 适用场景 | 特点 |
|---|---|---|
| Preferences | 用户偏好设置、主题配置、简单键值对 | 轻量级 Key-Value,1MB 以内数据,XML/GSKV 两种存储模式 |
| RDB(关系型数据库) | 复杂结构化数据、多表关联查询 | 完整 SQL 支持、ACID 事务、支持索引和迁移 |
| KVStore(分布式) | 跨设备数据实时同步 | 自动同步、PUSH/PULL/PUSH_PULL 模式、支持离线缓存 |
import { preferences } from '@kit.ArkData';
// 获取实例
const dataPref = preferences.getPreferencesSync(context, { name: 'myStore' });
// 写入
dataPref.putSync('isDarkMode', true);
dataPref.flush(); // 持久化到文件
// 读取
const darkMode = dataPref.getSync('isDarkMode', false);
// 订阅变更
dataPref.on('change', (key) => { console.info(`Key ${key} changed`); });
import { relationalStore } from '@kit.ArkData';
const config: relationalStore.StoreConfig = {
name: 'app.db', securityLevel: relationalStore.SecurityLevel.S1,
};
const rdb = await relationalStore.getRdbStore(context, config);
// 建表
await rdb.executeSql(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER
)`);
// 插入
await rdb.insert('users', { name: 'Alice', age: 25 });
// 查询
const predicates = new relationalStore.RdbPredicates('users');
predicates.greaterThanOrEqualTo('age', 18).orderByAsc('name');
const resultSet = await rdb.query(predicates, ['id', 'name', 'age']);
import { distributedData } from '@kit.ArkData';
const kvManager = distributedData.createKVManager({
bundleName: 'com.example.app',
userInfo: { userId: 'default', userType: distributedData.UserType.SAME_USER_ID },
});
const kvStore = await kvManager.getKVStore('store', {
createIfMissing: true, autoSync: true,
kvStoreType: distributedData.KVStoreType.DEVICE_COLLABORATION,
});
await kvStore.put('key', 'value');
const val = await kvStore.get('key');
resources/
├── base/ # 默认资源(必选)
│ ├── element/ # 基础元素资源
│ │ ├── color.json { "color": [{"name":"bg","value":"#FFFFFF"}] }
│ │ ├── float.json { "float": [{"name":"margin","value":"16vp"}] }
│ │ ├── string.json { "string": [{"name":"hello","value":"你好"}] }
│ │ ├── plural.json { "plural": [{"name":"count","value":[{..}]}] }
│ │ ├── boolean.json { "boolean": [{"name":"enable","value":true}] }
│ │ └── integer.json { "integer": [{"name":"maxAge","value":100}] }
│ ├── media/ # 图片/音频等媒体文件
│ └── profile/ # 配置类文件(如技能卡片配置)
├── en_US/ # 英文语言限定(可不带 base 重复)
│ └── element/string.json
├── zh_CN/ # 中文
├── ja_JP/ # 日文
└── rawfile/ # 原始文件(不编译,直接读取)
└── icon.png
语言_文字_国家-地区_横竖屏_设备类型_颜色模式_屏幕密度
常用组合:
| 限定词 | 示例 | 作用 |
|---|---|---|
| 语言 | en_US / zh_CN | 多语言 |
| 横竖屏 | landscape / portrait | 横竖屏布局 |
| 屏幕密度 | ldpi / mdpi / hdpi / xhdpi | 不同分辨率适配 |
| 颜色模式 | dark | 深色模式 |
// ✅ 标准引用(编译检查,推荐)
Text($r('app.string.hello'))
Image($r('app.media.icon'))
// 带参数
Text($r('app.string.template', 'world')) // 对应 "hello %s"
// ✅ rawfile 引用(不编译,直接复制)
Image($rawfile('icon.png'))
// ✅ 系统资源引用
Text($r('sys.string.ok')) // 使用系统内置字符串
// ✅ 代码中访问资源
const str = this.context.resourceManager.getStringSync('app.string.hello');
// 复用 UI 片段
@Builder Header(title: string) {
Row() {
Text(title).fontSize(20).fontWeight(FontWeight.Bold)
Blank()
Button('更多').fontSize(14)
}.padding(16)
}
// 使用
@Component
struct MyPage {
build() {
Column() {
this.Header('个人中心')
this.Header('设置')
}
}
}
// 为 ArkUI 内置组件添加全局方法
@Extend(Text) function primaryTitle() {
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
}
@Extend(Button) function roundedStyle(radius: number = 12) {
.width('100%')
.height(48)
.borderRadius(radius)
.backgroundColor('#007AFF')
}
// 使用
Text('标题').primaryTitle()
Button('确定').roundedStyle(16)
// 通用样式函数(可应用于任意组件)
@Styles function cardShadow() {
.width('100%')
.padding(16)
.borderRadius(12)
.backgroundColor('#FFFFFF')
.shadow({ radius: 8, color: '#1A000000' })
}
// 使用
Column() { Text('卡片内容') }.cardShadow()
Row() { /* ... */ }.cardShadow()
// 高级用法:自定义绘制/动效修饰
class MyModifier implements CustomModifier {
draw(context: DrawContext) {
context.canvas.drawCircle({ x: 50, y: 50, radius: 30 });
}
}
@Component
struct MyComp {
modifier: MyModifier = new MyModifier();
build() {
Column() { }.attributeModifier(this.modifier)
}
}
华为官方维护的 425+ Sample 示例仓库,每个均为独立 DevEco Studio 工程项目,覆盖媒体/安全/UI/多设备/分布式等全场景。
| Sample 名称 | Star | 核心能力 | 参考价值 |
|---|---|---|---|
| HMOSLiveStream | 22⭐ | gitee 媒体直播开播端+看播端:音视频采集/播放/推流/拉流、ROI、背景音乐、前后摄翻转 | 直播全流程 |
| AVPlayerLongVideo | 3⭐ | gitee 长视频:播控、精准跳转、倍速、音量/亮度控制、焦点管理、弹幕、字幕、画中画 | 视频播放全场景 |
| MultiTabNavigation | 149⭐ | gitee 底部/顶部/侧边 Tab 导航 | 多Tab页架构 |
| CustomDialogGathers | 64⭐ | gitee CustomDialog + bindContentCover + bindSheet | 弹窗/模态/半模态全范式 |
| MultiTravelAccommodation | 40⭐ | gitee 栅格布局+List 响应式旅行住宿多场景 | 响应式布局实战 |
| AnimationCollection | 40⭐ | gitee 基础组件+通用属性+显式动效,多种常见动效案例 | 动画开发参考 |
| ContentPublisher | 31⭐ | gitee RichEditor + ArkUI 图文内容发布器 | 富文本编辑 |
| WaterFlow | 38⭐ | gitee WaterFlow 瀑布流、sections 混合排列、Tab吸顶、下拉刷新、无限加载 | 瀑布流全能力 |
| GridHybrid | 41⭐ | gitee Grid + List + Swiper 嵌套混合布局 | 复杂布局实战 |
| KnockShare | 7⭐ | gitee 碰一碰分享(API 26 新特性匹配) | 设备协同 |
| AudioFocus | 2⭐ | gitee 音频焦点/中断、自定义策略、AVSession后台播控 | 多音频冲突处理 |
| FluentBlog | 15⭐ | gitee 流畅刷页面示例 | 列表滚动性能优化 |
| ConcurrentModule | 18⭐ | gitee TaskPool + Worker 多线程实践 | 并发编程入门 |
| HMRouter | 53⭐ | gitee 鸿蒙路由库,支持声明式路由+拦截器+参数校验 | 路由框架选型参考 |
| UserAuth | 24⭐ | gitee 人脸+指纹认证、密码保险箱自动填充、防截屏/录屏 | 身份认证全流程 |
| Watermark | 28⭐ | gitee 页面水印/保存图片水印/拍照水印 | 版权保护实践 |
| ShareKit | 38⭐ | gitee Share Kit 数据分享与应用内文件分享 | 分享功能全场景 |
| WindowPiP | 17⭐ | gitee 视频画中画、手动/自动拉起、PiP窗口控制播放 | 画中画开发参考 |
| AudioInteraction | 22⭐ | gitee 后台播放、播控中心交互、焦点打断策略、路由切换 | 音频播控全场景 |
| SmoothSwitchShortVideos | 8⭐ | gitee LazyForEach+组件复用在短视频快速切换的性能优化 | 短视频性能优化 |
| FormGame | 5⭐ | gitee Stage 模型实现简单游戏卡片 | 服务卡片开发 |
| MultiDeviceInteraction | 3⭐ | gitee 触控屏/触控板/鼠标/键盘统一交互 | 跨设备交互归一化 |
| StateManagement | 12⭐ | gitee 页面级+应用级状态管理 | 状态管理入门 |
| Aspect | 0⭐ | gitee 基于libabcKit字节码的AOP切面编程库 | 字节码增强/无侵入AOP |
| Logger | 13⭐ | gitee hilog 日志系统封装,日志分级+格式化 | 日志工具参考 |
| FAQSnippets | 10⭐ | gitee 常见问题代码片段合集 | FAQ速查 |
以下 Sample 均基于「一次开发、多端部署」理念,覆盖常见行业场景。
| Sample | Star | 适配设备 | 技术要点 |
|---|---|---|---|
| MultiConvenientLife | 34⭐ | 手机/折叠屏/平板/2in1 | 自适应+响应式布局,便捷生活页面 |
| MultiNavBar | 33⭐ | 多设备形态 | 导航栏在不同设备上的样式切换 |
| MultiColumns | 22⭐ | 多设备形态 | 多场景分栏控件响应式变化 |
| MultiNewsRead | 20⭐ | 手机/折叠屏/平板 | 新闻阅读页多设备适配 |
| MultiTicketClass | 17⭐ | 多设备 | 栅格+List 股票类多场景响应式 |
| MultiShortVideo | 23⭐ | 手机/折叠屏/2in1 | 三层工程架构+短视频多设备 |
| MultiMobilePayment | 14⭐ | 多设备 | Scan Kit扫码+收付款码支付 |
独立、聚焦的原子级实践代码,适合快速参考,每条解决一个具体问题。
| 分类 | 片段名称 | 解决的问题 |
|---|---|---|
| 🚀 性能 | AppColdStart | 应用冷启动耗时优化指南 |
AvoidTimeConsume | 主线程耗时操作优化:异步化+TaskPool 拆分 | |
FramedRendering | 高负载分帧渲染:多帧分摊渲染压力 | |
FuzzySceneOptimization | 图像模糊动效性能优化 | |
ScreenFlickerSolution | 应用动效闪屏排查与修复 | |
VisibleComponent | 不可见组件低功耗策略 | |
| 🔐 安全 | AppDataSecurity | 应用数据安全规范 |
AppPrivacyProtection | 应用隐私保护实践 | |
| 🧩 UI | ComponentReuse | 组件复用(@Reusable)开发实践 |
CustomDialogPractice | 自定义弹窗规范开发实践 | |
CustomTitleBarWindowDrag | 自定义标题栏窗口拖动 | |
LiveViewLockScreen | 锁屏沉浸实况窗(闪控窗/互动卡片) | |
ResponsiveLayout | 自适应+响应式布局指导 | |
SubwindowAdaptWhenRotate | 子窗旋转场景适配 | |
| 🧵 并发 | ImageEditTaskPool | 基于 TaskPool 实现图片编辑(带进度回调) |
NdkQoS | 基于 QoS 设置线程优先级 | |
ThreadIssueDetection | 线程问题检测与优化 | |
| 📶 网络 | NetworkManagement | 网络管理与状态监听 |
PreHttpRequestUseFiles | Image 组件白块解决指导 | |
| 🎬 媒体 | SegmentedPhotograph | 分段式拍照:长曝光/多帧合成 |
TextureHypercompression | 纹理超压缩技术 | |
OnlineVideoPlaying | 在线视频播放优化 | |
| 🏗️ 架构 | NavigationRouter | Navigation + Router 选型指导 |
StabilityCodingSpecification | 稳定性编码规范 |
| 名称 | 地址 | 核心内容 | 定位 |
|---|---|---|---|
| ArkTS Patterns | GitHub | V2装饰器/TaskPool/Navigation/网络/持久化/手势/动画等 10+ 模式 | Claude Code Skill,100% 基准测试通过 |
源自官方 Sample
HMOSLiveStream(22⭐)及华为最佳实践《基于媒体能力实现直播单播功能》。
音视频采集(OHAudio / 相机)
↓
编码(H.264/H.265 / HDR Vivid)
↓
推流(Surface 模式)
↓
ROI 编码(主播区域高质量,背景低码率)
↓
系统压力反馈(动态调整码率/帧率)
| 模块 | 技术方案 |
|---|---|
| 音频采集 | OHAudio API:常规录音/语音通话/直播三种模式 |
| 视频采集(SDR) | 复用预览流,功耗低 + 红枫原色色彩算法 |
| 视频采集(HDR) | HDR Vivid 复用预览流,暗光/高动态场景 |
| 视频编码 | Surface 模式,性能最优 |
| ROI 编码 | 对主播区域高质量编码,背景压缩 |
| 智能调控 | 系统压力反馈接口 → 动态调整码率/帧率 |
流媒体加载(AVPlayer)
↓
音频播放时间戳同步
↓
视频送帧时延匹配
↓
音画同步输出
↓
内存泄漏防护(长时间直播稳定)
import { ohAudio } from '@kit.AudioKit';
// 直播模式:高保真 + 回声消除
const audioCapturer = ohAudio.createAudioCapturer({
streamUsage: ohAudio.StreamUsage.STREAM_USAGE_MUSIC,
sourceType: ohAudio.SourceType.SOURCE_TYPE_MIC,
capturerOptions: {
encoding: ohAudio.AudioEncoding.ENCODING_PCM,
sampleRate: 48000,
channelCount: 2,
}
});
await audioCapturer.start();
import { media } from '@kit.MediaKit';
avPlayer = await media.createAVPlayer();
avPlayer.url = 'rtmp://stream.example.com/live'; // 流媒体地址
avPlayer.stateChangeCallback = (state) => {
if (state === 'prepared') avPlayer.play();
};
| 场景 | 技术方案 |
|---|---|
| 🛒 电商直播 | 红枫原色色彩校正 + HDR Vivid 暗光增强 + ROI 主播聚焦 |
| 🎤 娱乐直播 | 高保真录音 + 回声消除 + ROI 表演区域 |
| 🌄 户外直播 | 红枫原色色彩 + 压力反馈动态调速 + 散热关注 |
源自官方 Sample
multi-short-video(23⭐),基于三层架构 + Swiper 短视频轮播。
与多设备长视频/音乐/社区评论同属"一多"系列,关键差异化特性:
| 特性 | 实现 |
|---|---|
| 短视频轮播 | Swiper + LazyForEach 纵向滑动切换 |
| 评论/点赞交互 | 底部浮层组件 |
| 个人作品页 | 三列 Grid 布局 |
| 分享 | Share Kit 设备自适应分享弹窗 |
| 三层工程架构 | commons/features/products 复用 |
三层架构(commons/features/products)+ Navigation Split 分栏 + BreakpointType / Tabs 响应式导航。
// Tabs 响应式:手机底部 ↔ 平板侧边
Tabs({
barPosition: this.currentBreakpoint === 'lg'
? BarPosition.Start : BarPosition.End
})
.barWidth(this.currentBreakpoint === 'lg'
? $r('app.float.tab_bar_width_lg') : '100%')
.vertical(this.currentBreakpoint === 'lg')
// 分类页双栏(Navigation Split 模式)
Navigation(this.pageInfo)
.mode(NavigationMode.Split)
.navBarWidth(new BreakpointType('96vp', '144vp', '200vp')
.getValue(this.currentBreakpoint))
// 页面边距断点适配
private static pageColPadding = new BreakpointType(
$r('app.float.padding_sm'),
$r('app.float.padding_md'),
$r('app.float.padding_lg'));
三层架构 + Scan Kit 扫码/码图生成,完整移动支付全流程。
import { scanCore } from '@kit.ScanKit';
import { generateBarcode } from '@kit.ScanKit';
// 扫一扫(默认界面)
const result = await scanCore.startScan({
scanType: scanCore.ScanType.QR_CODE,
});
// 生成付款码
const barcode = generateBarcode.createBarcode({
content: 'payment://merchant/xxx',
format: BarcodeFormat.QR_CODE,
width: 200, height: 200,
});
多设备适配:手机单列 → 折叠屏双栏 → 平板左侧扫码+右侧交易记录
Swiper + Video + LazyForEach 短视频上下滑动切换。
Swiper(this.swiperController) {
LazyForEach(this.data, (item: VideoData) => {
Stack({ alignContent: Alignment.BottomEnd }) {
Video({
src: item.video,
controller: item.controller
})
.width('100%').height('100%')
.objectFit(ImageFit.Contain)
.loop(true).autoPlay(item.auto)
.controls(false) // 隐藏控制栏
}
}, (item) => JSON.stringify(item))
}
.index(0).autoPlay(false).indicator(false)
.loop(true).duration(200).vertical(true)
// 性能:LazyForEach + cachedCount
源自 BestPracticeSnippets
LiveViewLockScreen+@kit.LiveViewKit。
import { liveViewManager } from '@kit.LiveViewKit';
const mgmt = liveViewManager.getLiveViewManager();
// 发布实况窗(进度模板)
const builder = new liveViewManager.LiveViewData.Builder();
builder.setTemplate(liveViewManager.LiveViewTemplate.PROGRESS);
builder.setTitle('订单 #202511290001');
builder.setProgress(50);
builder.setStatus('配送中');
await mgmt.publish(builder.build(), {
isPersistent: true,
isShowOnLockScreen: true,
});
// 关闭
await mgmt.dismiss();
// 状态监听
mgmt.on('liveViewStateChange', (state) => { /* SHOWN/DISMISSED */ });
注意事项:ohos.permission.REAL_TIME_ACTIVITY 权限 + AGC 正式申请 + 锁屏文字用白色
源自官方 Sample
ImageEditWithWavewheel+SubWinWaveWheel组件。Pen Kit 手写笔 + SubWindow 悬浮波轮 + 图片编辑。
Pen Kit(@kit.Penkit)
├── stylusInteraction → 轻捏(squeeze)/双击(doubleTap)
└── imageFeaturePicker → 屏幕取色(pickForResult)
SubWindow 悬浮波轮菜单
├── 可拖拽 + 智能吸边
├── 单击展开扇形工具栏(四象限自适应方向)
└── 手写笔轻捏快捷弹出
import { stylusInteraction, imageFeaturePicker } from '@kit.Penkit';
// ★ 回调引用必须保存为成员变量
private squeezeCB: (event: stylusInteraction.SqueezeEvent) => void = () => {};
aboutToAppear(): void {
this.squeezeCB = (event) => this.showWaveWheel();
}
onPageShow(): void {
try { stylusInteraction.on('squeeze', this.squeezeCB); } catch (e) {}
}
onPageHide(): void {
stylusInteraction.off('squeeze', this.squeezeCB); // 同一引用才能取消
}
// 屏幕取色器
Button('取色').onClick((e: ClickEvent) => {
imageFeaturePicker.pickForResult(e.displayX, e.displayY)
.then((info) => this.color = info.color)
.catch((err) => { if (err.code !== CANCEL) throw err; });
});
// 创建悬浮子窗口
const subWin = await ctx.windowStage.createSubWindow('wavewheel');
await subWin.setWindowType(window.WindowType.TYPE_FLOAT);
await subWin.resize(40, 40);
await subWin.setUIContent('pages/WaveWheel');
await subWin.showWindow();
// 扇形展开算法(四象限自适应,朝无阻挡方向展开)
function calcAngle(index: number, cx: number, cy: number) {
const centerX = screenW / 2, centerY = screenH / 2;
const gap = (90 - 10) / 3; // 4个按钮均分80°,留10°安全边界
let rad = 0;
if (cx < centerX && cy > centerY) rad = (175 - index * gap) * Math.PI / 180;
else if (cx > centerX && cy > centerY) rad = (355 - index * gap) * Math.PI / 180;
else if (cx < centerX && cy < centerY) rad = (85 - index * gap) * Math.PI / 180;
else rad = (265 - index * gap) * Math.PI / 180;
return { x: 120 * Math.cos(rad), y: 120 * Math.sin(rad) };
}
// 手势互斥(GestureMode.Exclusive)
Circle().gesture(GestureGroup(GestureMode.Exclusive,
TapGesture().onAction((e) => toggleMenu(e)),
PanGesture().onActionUpdate((e) => drag(e)).onActionEnd(() => snap()),
));
off() 无效会导致泄漏vp2px/px2vp
### 检测和切换
```typescript
// 方式1:跟随系统(推荐)
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_AUTO);
// 方式2:手动控制
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
// 方式3:组件内监听主题变化
@State isDark: boolean = false;
aboutToAppear() {
this.context.on('configuration', (config) => {
this.isDark = config.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
});
}
// 方式1:dark限定词目录(两套图)
// resources/base/media/logo.png ← 浅色
// resources/dark/media/logo.png ← 深色
Image($r('app.media.logo')) // 自动选择
// 方式2:SVG fillColor 动态变色
Image($r('app.media.ic_setting'))
.fillColor(this.isDark ? '#FFFFFF' : '#1A1A1A')
$r('app.color.xxx') 引用,不写死十六进制import { userAuth } from '@kit.UserAuthKit';
const auth = userAuth.getUserAuthInstance({
challenge: new Uint8Array([1,2,3]),
authType: [userAuth.UserAuthType.FACE, userAuth.UserAuthType.PIN],
});
auth.on('result', { onResult: (result) => {
if (result.result === 0) { /* 认证成功 */ }
}});
auth.start();
// 使用文本叠加实现水印
Text('内部资料 · 请勿外传')
.fontSize(16).fontColor('#40FFFFFF')
.rotate({ angle: -30 })
.position({ x: '60%', y: '40%' })
.width('100%').height('100%')
import { shareController } from '@kit.ShareKit';
import { fileShare } from '@kit.ShareKit';
// 分享文本
shareController.share({ text: '分享内容', title: '标题' });
// 分享文件
fileShare.share({ filePaths: ['/data/app/a.jpg'] });
基于系统 Navigation 封装的页面路由方案,注解声明式路由,支持拦截器/生命周期/转场动画。
| 特性 | 说明 |
|---|---|
| 注解声明路由 | @HMRouter({ pageUrl: 'home', singleton: true }) 声明页面 |
| 路由拦截器 | 全局拦截 + 单页面拦截 + 跳转时一次性拦截 |
| 生命周期回调 | 全局生命周期 + 单页面生命周期 + 一次性生命周期 + NavBar生命周期 |
| 转场动画 | 内置页面/Dialog动画,支持方向/透明度/缩放/交互式 |
| 服务路由 | 支持服务型路由,实现模块间解耦 |
| Dialog 页面 | 支持 Dialog 类型页面和单例页面 |
| 模块类型 | 支持 HAR / HSP / HAP |
| 路由栈嵌套 | 支持 Navigation 路由栈嵌套 |
import { HMRouter } from '@ohos/hmrouter';
@HMRouter({
pageUrl: 'home',
singleton: true, // 单例页面
dialog: false,
title: '首页'
})
@Component
export struct HomePage {
build() {
Column() { /* ... */ }
}
}
import { HMRouterMgr } from '@ohos/hmrouter';
// 页面跳转
HMRouterMgr.pushUrl({ pageUrl: 'product/detail', param: { id: 123 } });
// 拦截器示例
// LoginCheckInterceptor.ets — 未登录则拦截跳转到登录页
class LoginCheckInterceptor implements IHMRouterInterceptor {
onBeforePageJump(info: JumpInfo): boolean {
if (!isLoggedIn && info.pageUrl !== 'login') {
HMRouterMgr.pushUrl({ pageUrl: 'login' });
return false; // 拦截本次跳转
}
return true;
}
}
// 全局页面停留时长统计
class PageDurationLifecycle implements IHMRouterLifecycle {
onPageShow(info: RouterInfo): void { logger.info(`enter ${info.pageUrl}`); }
onPageHide(info: RouterInfo): void { logger.info(`leave ${info.pageUrl}`); }
}
entry/src/main/ets/
├── component/ # 业务页面
│ ├── home/ # 按功能模块分目录
│ ├── product/
│ ├── login/
│ ├── pay/
│ └── shoppingBag/
├── interceptor/ # 全局拦截器
│ ├── LoginCheckInterceptor.ets
│ └── JumpInfoInterceptor.ets
├── lifecycle/ # 全局生命周期
│ ├── PageDurationLifecycle.ets
│ ├── ExitAppLifecycle.ets
│ └── WelcomeLifecycle.ets
├── service/ # 服务路由
├── viewmodel/ # 视图模型
├── constant/ # 页面常量与断点
├── animation/ # 自定义转场动画
└── pages/ # Navigation 入口页面
鸿蒙内置运行时 AOP 能力,基于 libabcKit 字节码操作,对标 iOS 的 method_swizzling。
@kit.ArkTS中提供util.Aspect类。
import { util } from '@kit.ArkTS';
util.Aspect.addBefore(targetClass, methodName, isStatic, beforeFunc); // 执行前插桩
util.Aspect.addAfter(targetClass, methodName, isStatic, afterFunc); // 执行后插桩
util.Aspect.replace(targetClass, methodName, isStatic, insteadFunc); // 替换实现
在方法执行前插入逻辑。适合:日志埋点、参数校验、权限检查、打点统计。
class DataService {
data: string = 'init';
fetchData(url: string): number {
console.info('original fetchData: ' + url);
return 200;
}
}
// 在所有 fetchData 调用前插入日志
util.Aspect.addBefore(DataService, 'fetchData', false,
(instance: DataService, url: string): void => {
console.info(`[AOP] before fetchData: ${url}`);
instance.data = 'modified_by_aop';
});
在原方法之后插入逻辑。适合:结果增强、缓存写入、数据统计。
util.Aspect.addAfter(DataService, 'fetchData', false,
(instance: DataService, ret: number, url: string): number => {
console.info(`[AOP] after fetchData, result=${ret}`);
return ret + 100; // 可以修改返回值
});
完全替换原方法逻辑。适合:Mock 数据、功能降级、热修复。
util.Aspect.replace(DataService, 'fetchData', false,
(instance: DataService, url: string): number => {
console.info('[AOP] replaced fetchData, returning mock');
return 999; // Mock 返回
});
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 全埋点/无痕埋点 | addBefore | 在按钮点击/页面跳转方法前插入 |
| 参数合法性校验 | addBefore | 校验参数,不合法则提前 return |
| 权限检查 | addBefore | 检查权限状态,未授权则拦截 |
| 数据缓存 | addAfter | 网络请求成功后自动写入缓存 |
| 运行时 Mock | replace | 开发/测试环境替换真实 API 调用 |
| 热修复 | replace | 线上紧急修复,替换有 Bug 的方法 |
import { notificationManager } from '@kit.NotificationKit';
// 1. 普通文本通知
let request: notificationManager.NotificationRequest = {
id: 1,
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title: '标题', text: '内容' }
},
slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION
};
await notificationManager.publish(request);
// 2. 进度条通知(下载/更新)
let progressRequest: notificationManager.NotificationRequest = {
id: 2,
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_LONG_TEXT,
normal: { title: '下载中', text: '50%', additionalText: '5MB/10MB' }
},
// 进度通过 ongoing 持续更新
ongoing: true, // 不可滑动清除
};
// 更新进度
await notificationManager.publish({
...progressRequest,
content: { normal: { text: '80%', additionalText: '8MB/10MB' } }
});
// 3. 分组通知(同一会话归组)
const groupRequest = {
...request,
groupName: 'chat_group', // 同一 groupName 自动归组
groupOverview: { title: '3条消息', text: '最新:你好' }
};
// 4. 可交互通知(带按钮)
import { wantAgent } from '@kit.AbilityKit';
const wantAgentInfo = {
wants: [{ bundleName: 'com.example', abilityName: 'DetailAbility' }],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 100,
};
const agent = await wantAgent.getWantAgent(wantAgentInfo);
await notificationManager.publish({
id: 3,
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title: '新消息', text: '点击查看' }
},
wantAgent: agent, // 点击通知跳转
});
| SlotType | 用途 | 特性 |
|---|---|---|
| SOCIAL_COMMUNICATION | 社交消息 | 高优先级、横幅+锁屏 |
| SERVICE_INFORMATION | 服务信息 | 中等优先级 |
| CONTENT_INFORMATION | 内容资讯 | 普通优先级 |
| OTHER | 其他 | 最低优先级、静默 |
HAR 共享包发布到 OpenHarmony 三方库中心仓,复用生态。
{
"name": "@your/lib_log",
"version": "1.0.0",
"description": "简短的包描述",
"main": "Index.ets",
"author": "你的名字",
"license": "Mulan PSL v2",
"dependencies": {},
"packageType": "har" // har / hsp
}
# 1. 注册中心仓 https://ohpm.openharmony.cn 获取 publish_id
# 2. 生成密钥对(ohpm 要求加密传输,生成时必须设置密码)
ssh-keygen -m PEM -t RSA -b 4096 -f ~/.ssh_ohpm/mykey
# 3. 配置 ohpm(publish_id 和 key_path 也可在 publish 命令中直接传参)
ohpm config set publish_id your_publish_id
ohpm config set key_path ~/.ssh_ohpm/mykey
# 4. 构建 HAR
hvigorw assembleHar
# 5. 发布
ohpm publish lib_log.har
| 要求 | 说明 |
|---|---|
| 文件格式 | 必须是 .har 或 .tgz 包 |
| 必需文件 | oh-package.json5 + README.md + LICENSE + CHANGELOG.md(不能为空) |
| oh-package.json5 必填字段 | name、version、description、main、license |
| 依赖完整性 | 所有直接依赖必须在包的 oh-package.json5 中声明 |
| 版本唯一 | 已发布的名称+版本组合不可重复使用 |
ohpm install @your/packageohpm version patch/minor/majorohpm clean 删除所有 oh_modules 目录和 lock 文件ohpm install @ohos/package # 安装依赖
ohpm install --all # 安装全部模块依赖
ohpm install --registry <url> # 指定仓库地址
ohpm uninstall @ohos/package # 卸载依赖
ohpm list # 查看依赖树
ohpm update @ohos/package # 更新依赖
ohpm clean # 清理所有 oh_modules
来源:
NativeSoIntegration| Gitee harmonyos_samples | 源码级
| 方案 | 方式 | 复杂度 |
|---|---|---|
| 编译引用 | CMakeLists.txt + 头文件 | ⭐ |
| dlopen | dlopen()/dlsym() | ⭐⭐ |
| ArkTS 模块依赖 | oh-package.json5 + import | ⭐ |
// 方案3(最推荐): ArkTS 直接 import
// entry/oh-package.json5 → "dependencies": { "libmultiply": "file:src/main/cpp/types/libmultiply" }
// import { multiply } from 'libmultiply'
// multiply(3, 4) → 12
来源:
language-base-class-library| Gitee harmonyos_samples | 源码级
| 分类 | 模块 | 说明 |
|---|---|---|
| util工具 | StringCode / LRUCache / Base64 / RationalNumber / ScopeHelper / TypeCheck / JsonFunction | 字符串编解码/LRU缓存/Base64编解码/有理数比较/范围判断/类型检查/Json操作 |
| ArkTSUtil | AsonFunction | ISendable 序列化/反序列化 |
| TaskPool | @ohos.taskpool | 后台任务创建/执行/取消 |
| Uri/Url | @ohos.uri / @ohos.url | URI解析/URL参数增删改查 |
| Xml | @ohos.xml / @ohos.convertxml | XML解析生成 / XML转JS对象 |
| 线性容器 | ArrayList / Deque / LinkedList / List / Queue / Stack | 有序数据集合 |
| 非线性容器 | HashMap / HashSet / LightWeightMap / LightWeightSet / PlainArray / TreeMap / TreeSet | 键值对/集合 |
// HashMap 使用
let map = new HashMap<string, number>()
map.set('key', 1)
map.get('key') // → 1
map.delete('key')
// util 工具
let encoder = new util.TextEncoder()
let decoder = new util.TextDecoder('utf-8', { ignoreBOM: true })
// LRU Cache
let cache = new util.LRUCache<string, number>(100)
cache.put('a', 1)
cache.get('a') // → 1
一次开发多端部署(一多)的核心布局模式。注意:
BreakpointType是社区封装的工具类,非 ArkUI 内置 API,官方方案使用GridRow+MediaQuery。
| 策略 | 适用 | 官方 API |
|---|---|---|
| 自适应布局 | 同设备尺寸变化 | layoutWeight / flexShrink / 百分比% |
| 响应式布局 | 跨设备形态变化 | GridRow + GridCol / MediaQuery |
| 多态组件 | 同一组件不同显示 | @Styles + @Extend + 条件渲染 |
GridRow)// GridRow breakpoints 属性(官方 API)
GridRow({
breakpoints: {
value: ['600vp', '840vp'], // 断点:sm<600, md<840, lg>=840
reference: BreakpointsReference.WindowSize // 相对窗口大小
}
}) {
GridCol({ span: { sm: 12, md: 6, lg: 4 } }) { // 手机满宽,平板半宽,PC 1/3
Text('自适应内容')
}
}
mediaquery 监听断点(官方 API)import { mediaquery } from '@ohos.mediaquery';
@Component
struct AdaptivePage {
@State currentBreakpoint: 'sm' | 'md' | 'lg' = 'sm';
listener = mediaquery.matchMediaSync('(min-width: 600vp) and (max-width: 839vp)');
aboutToAppear() {
this.listener.on('change', (result) => {
// 根据 match 结果更新状态
});
}
}
| 断点 | vp 范围 | 典型设备 |
|---|---|---|
| xs | 0~319vp | 小屏手表 |
| sm | 320~599vp | 手机竖屏 |
| md | 600~839vp | 折叠屏展开/小平板 |
| lg | 840~1199vp | 平板横屏 |
| xl | ≥1200vp | PC/2in1 |
// 折叠屏自适应双栏/单栏(官方推荐方式)
if (this.currentBreakpoint === 'sm') {
// 单栏:Navigation 全屏
Navigation() { /* ... */ }
.title('列表')
.navDestination(this.pageStack)
} else {
// 双栏:侧栏 + 内容
Row() {
SideBarContainer() { /* 列表 */ }
.width(320)
Column() { /* 详情 */ }
.layoutWeight(1)
}
}
references/目录下收录了 60 个核心 @ohos. 模块的离线 API 参考文档*,覆盖网络、数据、Ability、ArkUI、媒体、安全、工具等主要分类。检索流程:先查索引 → 再读文档,避免全量读取。
SKILL.md → references/KITS.md / TASK_MAP.md → references/INDEX.md → 目标文档
| 层级 | 文件 | 用途 |
|---|---|---|
| 第1层 | KITS.md | Kit 导航:按功能分类(NetworkKit、AbilityKit、ArkData 等) |
| 第2层 | TASK_MAP.md | 任务导航:按"我想做什么"反查(网络请求、数据存储、权限申请等) |
| 第3层 | INDEX.md | 全库路径索引:所有模块文件的完整路径清单 |
# ① 在 INDEX 中搜索关键词
rg -n "UIAbility|Want|AbilityStage" references/INDEX.md | head
# ② 按模块前缀搜索
rg -n "net\\.http|file\\.fs|data\\.preferences" references/INDEX.md | head
# ③ 搜索特定方法名
rg -n "createHttp|request|destroy" references/INDEX.md | head
# ④ 在 KITS 中识别 Kit
rg -n "网络|数据|安全|媒体" references/KITS.md | head
| 分类 | 模块数 | 包含模块 |
|---|---|---|
| 📡 网络通信 | 4 | net.http, net.socket, net.webSocket, net.connection |
| 💾 数据存储 | 5 | data.preferences, data.rdb, relationalStore, distributedKVStore, distributedDataObject |
| 📱 Ability | 8 | app.ability.UIAbility, Want, AbilityStage, common, Configuration, appManager, wantAgent + 更多 |
| 🎨 ArkUI | 6 | arkui.UIContext, inspector, observer, StateManagement, dragController, componentSnapshot |
| 📷 媒体 | 4 | multimedia.camera, audio, image, media |
| 🔐 安全 | 4 | security.cryptoFramework, huks, cert, abilityAccessCtrl |
| 🔧 工具 | 7 | hilog, taskpool, worker, resourceManager, util, promptAction, hidebug |
| 📍 位置 | 3 | geoLocationManager, bluetooth, distributedDeviceManager |
| 📂 文件 | 3 | file.fs, file.picker, file.fileuri |
| 📦 其他 | 16 | 图形、通知、窗口、传感器、输入法、上传下载、后台任务、包管理等 |
references/INDEX.md 找到文档路径 → 只读对应 .md 文件
references/目录路径相对于本 SKILL.md。
目标读者:需要理解 HarmonyOS Stage 模型应用组件框架的开发者。本文基于 UIAbility组件概述 和 UIAbility生命周期 官方文档精学验证。
UIAbility 是一种包含 UI 的应用组件,主要用于和用户交互。是系统调度的基本单元,为应用提供绘制界面的窗口。
设计理念:
划分原则:
声明配置(module.json5):
{
"module": {
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background"
}
]
}
}
UIAbility 生命周期包括 Create、Foreground、Background、Destroy 四个状态,以及 WindowStageCreate、WindowStageDestroy 两个窗口相关状态。
来源:华为官方文档 + 51CTO/CSDN 实战文章,覆盖通知完整开发链路
| 类型 | 用途 | API | 要求 |
|---|---|---|---|
| 基础文本通知 | 简单消息提醒 | NotificationRequest + ContentType.BASIC_TEXT | 无特殊权限 |
| 社交通知 | 象/联系人消息 | ContentType.MULTI_LINE | 需 ohos.permission.NOTIFICATION_CONTROLLER |
| 实况窗(LiveView) | 进度型实时状态 | LiveViewKit | 需 ohos.permission.PUBLISH_AGENT_REMINDER |
| 闪控窗(闪控球) | 常驻迷你控制 | floatWindow | 需 ohos.permission.SYSTEM_FLOAT_WINDOW |
import { notificationManager } from '@kit.NotificationKit';
import { Want } from '@kit.AbilityKit';
// 1. 创建通知渠道(应用启动时注册)
async function createNotificationChannel() {
const channel: notificationManager.NotificationChannel = {
id: 'play_control',
name: '播放控制',
description: '用于显示音乐播放状态和控制',
importance: notificationManager.NotificationImportance.LOW,
badgeFlag: false
};
await notificationManager.addNotificationChannel(channel);
}
// 2. 构建 WantAgent(点击通知跳转指定页面)
async function createWantAgent(): Promise<WantAgent> {
const want: Want = {
bundleName: 'com.example.myapp',
abilityName: 'EntryAbility',
parameters: { page: 'music_player' }
};
return await wantAgent.getWantAgent({
action: wantAgent.ActionType.START_ABILITY,
wants: [want],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0
});
}
// 3. 发布通知
async function publishNotification(title: string, text: string) {
const wantAgentObj = await createWantAgent();
const request: notificationManager.NotificationRequest = {
id: 1001,
slotType: notificationManager.SlotType.SERVICE_REMINDER,
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title, text }
},
wantAgent: wantAgentObj, // 点击跳转
actionButtons: [
{ title: '暂停', icon: '' },
{ title: '下一首', icon: '' }
]
};
await notificationManager.publish(request);
}
// 4. 取消通知
await notificationManager.cancel(1001);
import { liveViewManager } from '@kit.NotificationKit';
// 发布实况窗(进度环模板)
const liveView: liveViewManager.LiveViewInfo = {
liveViewId: 'music_progress',
slotType: notificationManager.SlotType.SERVICE_REMINDER,
templateName: 'progressRing', // 系统内置进度环模板
extraInfo: {
title: '正在播放',
text: '歌曲名',
progressValue: 65,
progressMaxValue: 100
}
};
await liveViewManager.publishLiveView(liveView);
| 坑 | 原因 | 解法 |
|---|---|---|
| 通知不显示 | 未注册通知渠道 | addNotificationChannel() 在 UIAbility.onCreate() 中调用 |
| 点击通知无反应 | 没配置 wantAgent | 使用 wantAgent.getWantAgent() 创建跳转意图 |
| 后台通知被系统杀 | 长时任务未绑定前台服务 | startBackgroundRunning() + injectNotification() |
| 多进程通知冲突 | 多进程同时操作通知 | 统一由 ServiceAbility 发布通知 |
| 实况窗不更新 | 未调用 updateLiveView() | 定时更新 extraInfo 后重新 publish |
来源:CSDN + 博客园实战文章,完整覆盖三种存储方案 + 选型决策树 + 性能对比
| 维度 | Preferences | KV Store | RelationalStore |
|---|---|---|---|
| 数据结构 | 扁平 KV | 扁平 KV | 关系型表 |
| 存储引擎 | XML 文件 | RocksDB | SQLite |
| 跨设备同步 | ❌ 不支持 | ✅ 原生支持 | ❌ 不支持 |
| 事务支持 | ❌ 不支持 | ❌ 不支持 | ✅ 支持 |
| 数据量 | < 100 key (< 1MB) | 中等 | 理论无上限 |
| 查询能力 | 按 key 精确 | 按 key 精确 | SQL 全功能 |
| 加密支持 | ❌ | ✅ | ✅ |
| 典型场景 | 主题/语言/开关 | 跨设备剪贴板 | 聊天记录/订单 |
需要持久化数据?
├── 需要跨设备同步? → YES → KV Store(autoSync: true)
│ → NO ↓
├── 数据量 < 100 条 且 结构简单(KV)? → YES → Preferences
│ → NO ↓
└── 需要结构化查询 / 大量数据 / 事务? → YES → RelationalStore
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
const PREF_NAME = 'user_settings';
// 初始化(在 AbilityStage 或 UIAbility.onCreate 中)
async function initPref(context: common.UIAbilityContext) {
const pref = await preferences.getPreferences(context, PREF_NAME);
const isDark = (await pref.get('isDarkMode', false)) as boolean;
const fontSize = (await pref.get('fontSize', 16)) as number;
return pref;
}
// 读写
async function saveSetting(pref: preferences.Preferences, key: string, value: boolean) {
await pref.put(key, value);
await pref.flush(); // ⚠️ 必须flush!否则数据只在内存中
}
// 监听变更
pref.on('change', (key: string) => {
if (key === 'isDarkMode') {
pref.get('isDarkMode', false).then(val => { /* 更新UI */ });
}
});
⚠️ Preferences 三大踩坑:
flush() 不调用 → 应用闪退数据丢失import { relationalStore } from '@kit.ArkData';
// 初始化
const DB_CONFIG: relationalStore.StoreConfig = {
name: 'notes.db',
securityLevel: relationalStore.SecurityLevel.S1,
};
let rdbStore: relationalStore.RdbStore | null = null;
async function initDB(context: Context) {
rdbStore = await relationalStore.getRdbStore(context, DB_CONFIG);
await rdbStore.executeSql(
'CREATE TABLE IF NOT EXISTS notes (' +
'id INTEGER PRIMARY KEY AUTOINCREMENT,' +
'title TEXT NOT NULL,' +
'body TEXT,' +
'ts INTEGER DEFAULT (strftime(\'%s\',\'now\')))'
);
// ⚠️ 建索引!无索引全表扫描10万行耗时数百ms
await rdbStore.executeSql('CREATE INDEX IF NOT EXISTS idx_ts ON notes(ts)');
}
// CRUD
async function insertNote(title: string, body: string): Promise<number> {
const bucket: relationalStore.ValuesBucket = { title, body };
return await rdbStore!.insert('notes', bucket);
}
async function queryNotes(keyword: string) {
const predicates = new relationalStore.RdbPredicates('notes');
predicates.contains('title', keyword).orderByDesc('ts').limitAs(20);
const cursor = await rdbStore!.query(predicates, ['id', 'title', 'body', 'ts']);
const results: Array<{ id: number; title: string }> = [];
while (cursor.goToNextRow()) {
results.push({
id: cursor.getLong(cursor.getColumnIndex('id')),
title: cursor.getString(cursor.getColumnIndex('title')),
});
}
cursor.close(); // ⚠️ 必须close!否则SQLite连接泄漏
return results;
}
// 批量插入必须用事务(性能差距200倍!)
async function batchInsert(notes: Array<{ title: string; body: string }>) {
rdbStore!.beginTransaction();
try {
for (const n of notes) {
await rdbStore!.insert('notes', { title: n.title, body: n.body });
}
rdbStore!.commit();
} catch (e) {
rdbStore!.rollBack();
throw e;
}
}
⚠️ RelationalStore 三大踩坑:
import { distributedKVStore } from '@kit.ArkData';
// 初始化
const kvManager = distributedKVStore.createKVManager({
context: context,
bundleName: 'com.example.myapp',
});
const kvStore = await kvManager.getKVStore<distributedKVStore.SingleKVStore>(
'shared_store', {
createIfMissing: true,
encrypt: false,
autoSync: true, // ⚠️ 高频场景设false,批量后手动sync()
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1,
}
);
// 读写
await kvStore.put('clip_content', '最新剪贴板内容');
const data = await kvStore.get('clip_content');
// 跨设备变更监听
kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, (data) => {
for (const entry of data.updateEntries) {
console.info(`远程更新: key=${entry.key}`);
}
});
⚠️ KVStore 三大踩坑:
| 方案 | 单次写入 | 批量1000条 | 适合数据量 |
|---|---|---|---|
| Preferences | < 1ms | ❌ 不适合 | < 1MB |
| RelationalStore(有事务) | ~2ms/条 | ~50ms | 无限制 |
| RelationalStore(无事务) | ~10ms/条 | ~10s | ❌ |
| KVStore | ~5ms/条 | ~500ms | 无限制 |
来源:博客园 ifeng0918 深度文章,覆盖三层架构 + HSP/HAR选型 + 动态加载 + 模块通信
产品定制层 → 设备差异化适配、业务定制组件
↓
基础特性层 → 功能UI + 业务逻辑(HAP/Feature)
↓
公共能力层 → 网络请求、日志、工具类(HAR)
| 维度 | HAR(静态共享包) | HSP(动态共享包) |
|---|---|---|
| 编译时 | 集成到 HAP 中 | 独立编译,运行时加载 |
| 更新 | 需随 HAP 一起更新 | 可独立更新 |
| 包体积 | 重复打包,体积大 | 共享一份,体积小 |
| 适用 | 工具类/UI组件库 | 按需加载的功能模块 |
| API要求 | 无特殊限制 | API 10+ Stage模型 |
{
"module": {
"name": "customer",
"type": "feature",
"dependencies": ["shared_utils"],
"abilities": [{
"name": "CustomerMainAbility",
"srcEntry": "./ets/customer/ability/CustomerMainAbility.ts",
"label": "客户管理",
"exported": true
}]
}
}
type Constructor<T> = new (...args: any[]) => T;
class Container {
private services: Map<string, any> = new Map();
private static instance: Container;
static getInstance(): Container {
if (!Container.instance) Container.instance = new Container();
return Container.instance;
}
register<T>(key: string, service: T): void {
this.services.set(key, service);
}
resolve<T>(key: string): T {
const service = this.services.get(key);
if (!service) throw new Error(`服务 ${key} 未注册`);
return service;
}
}
export const container = Container.getInstance();
// 注册: container.register('ApiClient', new ApiClient());
// 使用: const api = container.resolve<ApiClient>('ApiClient');
class RouterService {
private static instance: RouterService;
private loadedModules: Map<string, boolean> = new Map();
static getInstance(): RouterService {
if (!RouterService.instance) RouterService.instance = new RouterService();
return RouterService.instance;
}
async navigateTo(moduleName: string, abilityName: string, params?: object): Promise<void> {
const want = {
bundleName: "com.example.app",
abilityName: `${moduleName}.${abilityName}`,
parameters: params || {}
};
await this.context.startAbility(want);
}
}
@Entry
@Component
struct AppMain {
@State activeTab: number = 0;
@State loadedModules: Set<string> = new Set();
build() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() { HomeScreen() }.tabBar('首页')
TabContent() {
if (this.loadedModules.has('customer')) {
CustomerModule()
} else {
LoadingProgress().onClick(() => this.loadModule('customer'))
}
}.tabBar('客户')
}
}
private async loadModule(name: string): Promise<void> {
await routerService.loadModule(name);
this.loadedModules.add(name);
}
}
⚠️ HSP 五大约束:
来源:华为官方 API 26 参考文档,覆盖 WebView 25+ 管理类
| 管理类 | 功能 | API版本 |
|---|---|---|
WebviewController | 页面加载、导航、JS执行 | 9+ |
WebCookieManager | Cookie管理 | 9+ |
WebDataBase | 数据库管理 | 9+ |
WebDownloadManager/Delegate | 下载任务管理 | 10+ |
WebSchemeHandler | 指定Scheme请求拦截 | 11+ |
WebResourceHandler | 资源加载控制 | 11+ |
ProxyConfig/ProxyController | 网络代理配置 | 10+ |
GeolocationPermissions | 地理位置 | 9+ |
AdsBlockManager | 广告过滤 | 11+ |
BackForwardCacheOptions | 前进后退缓存 | 12+ |
WebMessagePort | JS与ArkTS双向通信 | 9+ |
WebMessageExt | 前端与应用通信数据 | 11+ |
NativeMediaPlayerBridge/Handler | 媒体播放托管 | 12+ |
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebViewPage {
controller: webview.WebviewController = new webview.WebviewController();
@State url: string = 'https://example.com';
build() {
Column() {
Web({ src: this.url, controller: this.controller })
.width('100%')
.height('100%')
.onPageEnd((event) => {
console.info(`页面加载完成: ${event?.url}`);
})
}
}
}
// ArkTS端:创建消息端口
const port = this.controller.createWebMessagePorts();
// port[0] = ArkTS端, port[1] = 发送给JS端
// 注册接收回调
port[0].onMessageEvent((result: webview.WebMessage) => {
const data = result.getData();
console.info(`收到JS消息: ${data}`);
});
// 将 port[1] 发送给 JS
this.controller.postMessageToWeb({ portId: port[1].getPortId() }, 'init_port');
// 发送消息到JS
port[0].postMessageEvent('来自ArkTS的消息');
// JS端代码(在网页中):
// const port = window.__web_message_port__;
// port.onmessage = (e) => { console.log('收到ArkTS:', e.data); };
// port.postMessage('来自JS的消息');
// 拦截自定义Scheme请求
const schemeHandler = new webview.WebSchemeHandler();
schemeHandler.onInterceptRequest((request: webview.WebSchemeHandlerRequest) => {
const url = request.getUrl();
if (url.startsWith('app://')) {
// 返回本地资源
const response = new webview.WebSchemeHandlerResponse();
response.setResponseData(localResourceBuffer);
response.setResponseMimeType('text/html');
return response;
}
return null; // 不拦截,正常加载
});
this.controller.addWebSchemeHandler('app', schemeHandler);
| 坑 | 原因 | 解法 |
|---|---|---|
| 网页不加载 | 缺网络权限 | module.json5 添加 ohos.permission.INTERNET |
| JS回调不触发 | 消息端口未初始化 | 先 createWebMessagePorts() 再 postMessageToWeb |
| 混合内容被拦截 | HTTPS页面加载HTTP资源 | mixedMode: MixedMode.All |
| Cookie不持久 | 未调用flush | WebCookieManager.flush() |
| 内存泄漏 | Web组件未销毁 | aboutToDisappear() 中调用 controller.destroy() |
来源:华为官方文档 2026-06-12 更新,覆盖生命周期/启动模式/多Ability协作
Create → WindowStageCreate → Foreground → (Background → Foreground循环) → WindowStageDestroy → Destroy
| 回调 | 触发时机 | 常见操作 |
|---|---|---|
| onCreate | Ability首次创建 | 初始化全局数据、Preferences |
| onWindowStageCreate | 窗口创建完成 | 加载主页面 loadContent('pages/Index') |
| onForeground | 回到前台 | 恢复播放、刷新数据 |
| onBackground | 退到后台 | 保存草稿、释放GPU资源 |
| onWindowStageDestroy | 窗口销毁 | 清理窗口资源 |
| onDestroy | Ability销毁 | 清理所有资源、关闭DB连接 |
| 模式 | 说明 | 典型场景 |
|---|---|---|
| singleton | 全局唯一实例,新请求复用旧实例 | 主入口、单窗口App |
| specified | 每个key创建一个实例 | 文档编辑(每文件一个窗口) |
| multiton | 每次启动创建新实例 | 计算器(多实例独立计算) |
// module.json5 配置启动模式
{
"abilities": [{
"name": "EntryAbility",
"launchType": "singleton", // singleton / specified / multiton
"srcEntry": "./ets/entryability/EntryAbility.ets"
}]
}
// EntryAbility.onCreate 中判断是否创建新实例
export default class DocAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 根据 want.parameters 中的 key 决定是否复用
const docId = want.parameters?.docId as string;
// 如果已有相同 docId 的实例,系统会复用而非创建新实例
}
}
// 启动指定文档的 Ability
const want: Want = {
bundleName: 'com.example.docapp',
abilityName: 'DocAbility',
parameters: { docId: 'file_001' }
};
this.context.startAbility(want);
// 启动 Ability 并传参
const want: Want = {
bundleName: 'com.example.myapp',
abilityName: 'PaymentAbility',
parameters: {
amount: 99.9,
orderId: 'ORD-20260707-001'
}
};
await this.context.startAbility(want);
// 目标 Ability 接收参数
export default class PaymentAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
const amount = want.parameters?.amount as number;
const orderId = want.parameters?.orderId as string;
}
}
| 场景 | 建议 | 说明 |
|---|---|---|
| 单窗口App | 1 UIAbility + 多页面 | 任务视图显示1个任务 |
| 多功能App | 多 UIAbility | 消息列表 + 视频通话各1个 |
| 分屏场景 | 多 UIAbility | 两个Ability可分屏显示 |
来源:51CTO/CSDN/HuaweiCloud 实战文章,覆盖三种任务类型 + 配额机制 + 踩坑
| 类型 | 执行时长 | 典型场景 | 配额机制 |
|---|---|---|---|
| 短时任务 | 几分钟 | 数据同步、文件下载 | 每日配额,用完即止 |
| 长时任务 | 持续运行 | 音乐播放、导航、录音 | 按子类型限制,必须显示通知 |
| 延时任务 | 按条件触发 | 定时备份、缓存清理 | 每小时/每天配额 |
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
async function startShortTask() {
// 申请短时任务
const taskId = backgroundTaskManager.requestSuspendDelay(
'data_sync',
() => { console.info('短时任务即将超时'); }
);
// 执行实际逻辑
await doDataSync();
// 完成后释放
backgroundTaskManager.cancelSuspendDelay(taskId);
}
// ⚠️ 检查剩余配额
const quota = await backgroundTaskManager.getRemainingDelayTime();
if (quota < 60000) { // 不足60秒
console.warn('短时任务配额不足,建议合并操作');
}
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { ServiceAbility, Want } from '@kit.AbilityKit';
import { notificationManager } from '@kit.NotificationKit';
export default class PlayServiceAbility extends ServiceAbility {
onStart(want: Want): void {
// 1. 创建通知(必须!否则任务被杀)
this.createNotification();
// 2. 申请长时任务
backgroundTaskManager.startBackgroundRunning(
this.context,
backgroundTaskManager.BackgroundType.PLAYING // 音乐播放类型
);
// 3. 开始播放逻辑
this.startPlayback();
}
onStop(): void {
// 4. 停止长时任务 + 移除通知
backgroundTaskManager.stopBackgroundRunning(this.context);
this.context.cancelNotification(1001);
}
private createNotification(): void {
const request: notificationManager.NotificationRequest = {
id: 1001,
slotType: notificationManager.SlotType.SERVICE_REMINDER,
content: {
contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title: '正在播放', text: '歌曲名' }
},
actionButtons: [{ title: '暂停', icon: '' }]
};
this.context.injectNotification(request); // ⚠️ injectNotification 不是 publish!
}
}
import { workScheduler } from '@kit.WorkSchedulerKit';
// 申请延时任务
const request: workScheduler.WorkInfo = {
workId: 1,
interval: 24 * 60 * 60, // 24小时
networkType: workScheduler.NetworkType.NETWORK_TYPE_WIFI,
batteryStatus: workScheduler.BatteryStatus.BATTERY_STATUS_LOW_OR_OKAY,
isRepeat: true,
abilityName: 'BackupAbility',
bundleName: 'com.example.myapp'
};
await workScheduler.scheduleWork(request);
// 在 BackupAbility.onWorkStart() 中执行备份
| 坑 | 原因 | 解法 |
|---|---|---|
| 长时任务被杀 | 未绑定前台通知 | injectNotification() + startBackgroundRunning() |
| 短时任务配额耗尽 | 每日配额限制 | 合并操作,getRemainingDelayTime() 检查 |
| Context类型错误 | UIAbilityContext不能用于长时任务 | 用 ServiceExtensionContext |
| 状态不同步 | start成功但stop仍可调用 | 布尔变量跟踪 bgRunning 状态 |
| 延时任务不触发 | 条件未满足(电量/WiFi) | 设置宽松条件,不要同时要求多个条件 |
| 通知被用户划掉 | 系统设计如此 | 通知不能设为"可清除"类型 |
来源:51CTO 踩坑实录 + 华为官方文档 2026-07 更新
| 文件 | 用途 | 生成方式 |
|---|---|---|
.p12 | 签名密钥库 | keytool -genkeypair |
.cer | 签名证书 | AGC 控制台上传 CSR |
.p7b | Profile文件 | AGC 控制台下载 |
# 生成密钥库(有效期100年)
keytool -genkeypair -alias myapp_release -keyalg RSA -keysize 2048 -validity 36500 -keystore myapp_release.p12 -storetype PKCS12 -dname "CN=MyApp, OU=Dev, O=MyCompany, L=Beijing, ST=Beijing, C=CN"
# 生成CSR(上传到AGC获取.cer)
keytool -certreq -alias myapp_release -keystore myapp_release.p12 -file myapp_release.csr
⚠️ 从 DevEco 26.0.0 Beta1 开始,支持自动签名:无需手动准备三件套!
# 清理构建
./hvigorw clean --no-daemon
# 构建 Release HAP
./hvigorw assembleHap --no-daemon -p buildMode=release
# 构建 Release APP(多模块)
./hvigorw assembleApp --no-daemon -p buildMode=release
| 原因 | 频率 | 解法 |
|---|---|---|
| 隐私合规 | 最高频 | 隐私政策URL必须可访问,首次启动弹同意窗 |
| 功能不完整 | 高频 | 所有入口有实际功能,不能有空白页/敬请期待 |
| 截图不合规 | 中频 | 真实界面截图,不含第三方品牌logo |
| 权限过度 | 中频 | 检查module.json5,删掉不需要的权限 |
| 内容违规 | 低频 | 所有素材有版权,内容合规 |
@Entry
@Component
struct PrivacyPage {
@State showPrivacyDialog: boolean = false;
aboutToAppear() {
const agreed = AppStorage.get<boolean>('privacyAgreed');
if (!agreed) this.showPrivacyDialog = true;
}
build() {
Column() {
if (this.showPrivacyDialog) {
Column() {
Text('隐私政策').fontSize(20).fontWeight(FontWeight.Bold)
Text('请仔细阅读《隐私政策》,我们严格保护您的个人信息安全。')
.fontSize(14).margin({ top: 12 })
Row() {
Button('不同意').onClick(() => terminateSelf())
Button('同意').onClick(() => {
AppStorage.setOrCreate('privacyAgreed', true);
this.showPrivacyDialog = false;
})
}.margin({ top: 20 })
}.padding(24).borderRadius(16).width('90%')
} else {
this.MainPage()
}
}
}
}
{
"module": {
"versionName": "1.2.0", // 用户可见版本号
"versionCode": 1020000 // 内部整数,每次必须递增
}
}
推荐编码:1020000 = 1.2.0,1020100 = 1.2.1,保证 versionCode 严格递增
来源:51CTO 实战踩坑文章 2026-06-16,Agent DSL 任务调度核心要点
| 坑 | 原因 | 解法 |
|---|---|---|
| TaskAllocator不生效 | 只在agent.json配置,没在Agent注解引用 | @Agent(taskAllocators=["backgroundTask"]) 显式绑定 |
| 任务调度延迟3秒+ | 使用了AGENT_TYPE_USER类型 | 改用 AGENT_TYPE_BACKGROUND 或 AGENT_TYPE_SYSTEM |
| 内存泄漏飙升 | TaskInfo未手动release | onTaskScheduled 结尾必须 taskInfo.release() |
| 配置错误零提示 | name大小写不一致 | agent.json与@Agent注解中name必须完全一致(大小写敏感) |
{
"taskAllocators": [{
"name": "background_task", // ⚠️ name必须与@Agent注解完全一致
"type": "com.example.task.BackgroundTaskAllocator",
"priority": 8 // 优先级8比默认5更高
}]
}
@Agent(
name = "MyAgent",
taskAllocators = ["background_task"] // ⚠️ 显式引用!
)
public class MyAgent extends Agent {
@Override
public void onTaskScheduled(TaskInfo taskInfo) {
try {
doWork(taskInfo);
} finally {
taskInfo.release(); // ⚠️ 必须释放!否则内存泄漏
}
}
}
AGENT_TYPE_BACKGROUNDname 与 @Agent 注解完全一致onTaskScheduled 中务必调用 taskInfo.release()