云端之眼:HarmonyOS ArkWeb 下载溯源与 Camera Kit 影随人动在无人机航拍场景的深度融合实践
无人机航拍服务是一个高度依赖移动端应用的行业场景——飞手需要在手机上管理航拍任务进度、预览云台画面、起飞前巡检设备、浏览空域资料、下载素材档案并管理设备台账。每一个环节都涉及不同的系统能力:相机预览需要 Camera Kit 的会话管理,素材下载需要 ArkWeb 的下载代理回调,数据可视化需要 Canvas 的 2D 绘制能力。HarmonyOS 在 6.1.1 版本中引入了 ArkWeb 下载双 URL 溯源能力(getOriginalUrl + getReferrerUrl),配合 Camera Kit 的 AUTO_FRAMING 影随人动和 PhotoSession 手动对焦三接口,以及 Canvas 的自定义图表绘制,可以在单页面文件中构建一个覆盖"任务管理→相机预览→起飞巡检→网页浏览→素材下载→个人中心"全链路的航拍服务应用。本文将逐段拆解这个"云镜"无人机航拍服务应用的代码实现,深入分析三大 HarmonyOS 套件在同一页面中的叠加架构与工程细节。
一、技术前置:HarmonyOS 开发栈与三大套件概述
1.1 ArkUI 声明式 UI 范式与状态管理
ArkUI 是 HarmonyOS 应用开发的核心 UI 框架,采用声明式编程范式。开发者通过 @Entry 和 @Component 装饰器声明页面入口和组件结构,在 build() 方法中通过链式调用组织 Column、Row、Stack、Scroll、List 等容器组件构建视图树。ArkUI 的状态管理体系以 @State 为核心——任何被 @State 修饰的成员变量发生变化时,框架会自动追踪依赖该变量的所有视图节点并触发重渲染。@Observed 装饰器进一步扩展了状态追踪的粒度,使 class 实例的属性级变更也能被框架感知,这对于列表项数据的精细更新至关重要。

@Builder 装饰器用于声明可复用的视图片段,无需拆分为独立的 struct 组件,可以在当前组件内部定义和调用,极大降低了视图组织的复杂度。在本应用中,6 个 Tab 的内容区全部通过 @Builder 方法实现,每个 Tab 承载完全不同的布局结构和功能模块。
1.2 Camera Kit 相机服务套件
Camera Kit 是 HarmonyOS 提供的相机能力套件,其核心架构围绕以下组件展开:

- CameraManager:相机管理器,通过
camera.getCameraManager(ctx)获取,负责发现设备、创建输入流和会话。 - CameraInput:相机输入流,绑定物理摄像头设备。
- PreviewOutput:预览输出流,绑定到 XComponent 的 Surface 上实现实时预览。
- VideoSession / PhotoSession:两种会话模式,分别承载不同的相机特性。
在 HarmonyOS 6.1.1 中,Camera Kit 引入了两项关键特性:

AUTO_FRAMING 影随人动:挂载于 VideoSession,通过 Control Center 控制中心实现。调用链路为 isControlCenterSupported() → getSupportedEffectTypes() → includes(AUTO_FRAMING) → enableControlCenter(true),系统接管画面构图后,人物在移动时画面自动跟随平移缩放。在航拍场景中,当飞手在地面移动操作遥控器时,云台预览画面会自动跟随飞手位置,确保主体始终居中。
手动对焦三接口:挂载于 PhotoSession,包含 isFocusDistanceSupported()(能力查询)、setFocusDistance(value)(设置对焦距离 0.0~1.0)、getFocusDistance()(读回对焦距离)。在起飞前巡检场景中,飞手可以对焦到近距检查电池序列号、对焦到中距检查桨叶裂纹、对焦到远景观察起飞环境。

1.3 ArkWeb 套件与下载双 URL 溯源
ArkWeb 是 HarmonyOS 的 Web 组件套件,通过 Web 组件嵌入网页视图,配合 webview.WebviewController 控制器实现 URL 加载、页面控制和下载管理。在 API 24(HarmonyOS 6.1.1 对应版本)中,ArkWeb 引入了 WebDownloadDelegate 下载代理和双 URL 溯源能力。
WebDownloadDelegate 提供四个回调接口,覆盖下载的完整生命周期:
- onBeforeDownload:下载开始前触发,必须在此调用
item.start(path)提供沙箱存储路径,否则下载任务永远停在 PENDING 状态。同时可获取建议文件名getSuggestedFileName()。 - onDownloadUpdated:下载进行中持续触发,通过
item.getPercentComplete()获取进度百分比,用于刷新 UI 进度条。 - onDownloadFailed:下载失败时触发,通过
item.getGuid()获取失败标识。 - onDownloadFinish:下载完成时触发,此回调中可以调用 6.1.1 新增的双 URL 溯源接口——
item.getOriginalUrl()获取下载项的原始 URL(文件直链来源),item.getReferrerUrl()获取引用页 URL(触发下载的页面地址)。同时可通过item.getTotalBytes()获取文件总大小。

双 URL 溯源的价值在于:当飞手从不同网页下载同一文件时,原始 URL 记录了文件的实际来源地址,引用页 URL 记录了用户是从哪个页面发起的下载。这对于素材版权追溯和下载路径审计至关重要。
1.4 Canvas 2D 绘制能力
ArkUI 的 Canvas 组件暴露 CanvasRenderingContext2D 上下文,支持路径、弧线、填充、描边、渐变、文字等标准 2D Canvas API。本应用使用 Canvas 绘制两类图表:任务完成率进度环(drawRing)和近 12 次飞行架次时长折线图(drawLine),两者均通过 onReady 回调首绘,再配合呼吸定时器驱动的周期性重绘实现动画效果。
二、应用全景:无人机航拍服务架构总览
本应用以"云镜"为产品名,定位为面向持证飞手的无人机航拍任务与素材管理平台。整体采用 6 Tab 单排底部导航架构,三大套件(Camera Kit / ArkWeb / Canvas)在同一页面文件中叠加。

2.1 整体架构流程
┌──────────────────────────────────────────────────────────────┐
│ 云镜主页面 (Page) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 头部区域 (headerMain) │ │
│ │ 天空青渐变 Banner + 品牌语 + Tab 联动副标题 │ │
│ │ 三枚统计胶囊(任务数/月架次/素材库) + 呼吸圆点 │ │
│ │ 搜索条 + 新建任务按钮 │ │
│ └────────────────────────────────────────────────────────┘ │
│ ─────────────────── 分割线 ────────────────────────────── │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 内容区 (6 Tab 切换) │ │
│ │ │ │
│ │ Tab0 任务 │ 进度条清单 + Canvas完成率环/架次折线 + 电池卡 │ │
│ │ Tab1 相机 │ XComponent预览 + 影随人动 + 三效果枚举表 │ │
│ │ Tab2 对焦 │ 能力查询 + 三档巡检预设 + 滑杆 + FocusRecord │ │
│ │ Tab3 网页 │ 地址栏 + 快捷站点横滑 + Web组件 + 主动下载行 │ │
│ │ Tab4 下载 │ 进行中进度卡 + 双URL溯源列表 + 代码预览卡 │ │
│ │ Tab5 我的 │ 飞手渐变大卡 + 设备清单 + 月度飞行柱状图 │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 底部导航栏 (tabBar) - 6 tab 单排自绘 │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 弹窗系统 (Stack 覆盖层) │ │
│ │ 新建航拍任务 / 修改任务区域 / 取消任务确认 │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
2.2 三特性叠加关系
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Camera Kit │ │ ArkWeb │ │ Canvas │
│ (特性 A) │ │ (特性 B) │ │ (特性 C) │
├──────────────────┤ ├──────────────────┤ ├──────────────────┤
│ AUTO_FRAMING │ │ WebDownload │ │ drawRing │
│ 影随人动 │ │ Delegate 四回调 │ │ 完成率进度环 │
│ │ │ │ │ │
│ setFocusDistance │ │ getOriginalUrl │ │ drawLine │
│ getFocusDistance │ │ getReferrerUrl │ │ 架次时长折线图 │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
└─────────────────────┼─────────────────────┘
│
┌────────┴────────┐
│ 统一状态管理 │
│ @State 驱动 │
│ @Observed 响应 │
│ 式数据模型 │
└─────────────────┘
2.3 页面生命周期与下载代理绑定流程
aboutToAppear()
│
├── setupDownloadDelegate() → 下载代理注册
│ │
│ ├── onBeforeDownload → item.start(沙箱路径)
│ ├── onDownloadUpdated → 刷新进度条
│ ├── onDownloadFailed → 置失败文案
│ └── onDownloadFinish → getOriginalUrl + getReferrerUrl
│ → 新建 DownloadRecord 置顶
│
├── 种子对焦记录 (3条 FocusRecord 置顶)
│
└── 启动呼吸动画定时器 (1s 心跳)
│
├── breath 翻转
├── ringReady → drawRing() 重绘
└── lineReady → drawLine() 重绘
aboutToDisappear()
│
├── clearInterval(timer)
└── releaseSession() → 释放相机资源
三、逐段代码深度解析
3.1 模块导入层
import { camera } from '@kit.CameraKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
四行 import 分别引入了本应用所需的全部系统能力。
第一行从 @kit.CameraKit 导入 camera 命名空间,它是相机能力的统一入口,涵盖 CameraManager、CameraInput、PreviewOutput、VideoSession、PhotoSession、CameraPosition、ControlCenterEffectType 等全部相机相关类型和枚举。在本应用中,Camera Kit 承担两项职责:VideoSession 上的 AUTO_FRAMING 影随人动能力链,以及 PhotoSession 上的手动对焦三接口。
第二行从 @kit.AbilityKit 导入 abilityAccessCtrl,用于动态申请 ohos.permission.CAMERA 权限。HarmonyOS 的权限模型将涉及用户隐私的权限划分为 user_grant 级别,要求必须在运行时弹窗申请,不能仅在 module.json5 配置文件中声明即可使用。相机权限正是典型的 user_grant 权限。
第三行从 @kit.ArkWeb 导入 webview 命名空间,它是 ArkWeb 套件的核心模块,提供 WebviewController(Web 视图控制器)和 WebDownloadDelegate(下载代理)两个关键类。WebviewController 负责 URL 加载、页面导航和应用侧主动发起下载等操作;WebDownloadDelegate 负责监听网页内触发的下载事件,通过四个回调覆盖下载全生命周期。
第四行从 @kit.BasicServicesKit 导入 BusinessError 类型,用于统一捕获异步操作中的错误对象,提供 code 和 message 两个字段供错误处理和日志记录使用。
3.2 颜色系统:深色主题的设计语言
interface ColorPalette {
bg: string;
card: string;
dark: string;
title: string;
sub: string;
text3: string;
cyan: string;
cyanD: string;
orange: string;
blue: string;
green: string;
red: string;
line: string;
tabOn: string;
mask: string;
cyanFade: string;
}
ColorPalette 接口定义了 16 个语义化颜色槽位,比前述应用多了一个 cyanFade 字段——这是天空青的渐隐色(rgba(59,168,196,0.07)),专门用于折线图渐变填充的终点,使填充区域从天空青自然过渡到近乎透明,产生"天空消散"的视觉效果。
const COLORS: ColorPalette = {
bg: '#0F1418',
card: '#1A2229',
dark: '#232E38',
title: '#E8F1F5',
sub: '#A3B8C4',
text3: '#6E8290',
cyan: '#3BA8C4',
cyanD: '#2A8AA6',
orange: '#E8874B',
blue: '#4E9BE3',
green: '#4EC98A',
red: '#E05E5E',
line: '#26333D',
tabOn: '#3BA8C4',
mask: 'rgba(0,0,0,0.6)',
cyanFade: 'rgba(59,168,196,0.07)'
};
色板取名为"云层石墨 + 天空青 + 护航橙",背景色 #0F1418 是极深的石墨黑色调,模拟阴天时云层的厚重感;卡片色 #1A2229 和深层色 #232E38 形成三层深浅梯度。主题色 #3BA8C4(天空青)灵感来自晴朗天空的青蓝色,用于所有主色元素——Tab 激活态、进度环弧线、折线主线、品牌渐变等。辅助暖色 #E8874B(护航橙)用于警示信息和交互引导——电池低电量提醒、编辑操作按钮、对焦手动模式标识等。信息蓝 #4E9BE3 专门用于下载记录中的原始 URL 展示,与引用页 URL 的灰色形成区分。
这种"零硬编码"原则确保全部 Builder 方法中所有色值通过 COLORS.xxx 引用,主题切换只需修改一个常量对象。
3.3 常量数据段
Tab 元信息
interface TabMeta {
icon: string;
label: string;
}
const TAB_LIST: TabMeta[] = [
{ icon: '🛩️', label: '任务' },
{ icon: '📷', label: '相机' },
{ icon: '🎯', label: '对焦' },
{ icon: '🌐', label: '网页' },
{ icon: '📥', label: '下载' },
{ icon: '👤', label: '我的' }
];
6 个 Tab 遵循"任务管理→相机操作→起飞巡检→资料浏览→素材下载→个人中心"的产品逻辑。每个 Tab 使用 emoji 图标避免引入外部图片资源,中文标签简洁明了。
Tab 联动副标题
const TAB_SUBS: string[] = [
'航拍任务进度与飞行架次总览',
'云台预览与影随人动跟拍',
'起飞前巡检手动对焦三接口',
'空域资料与航拍素材站浏览',
'素材下载双 URL 溯源档案',
'飞手资质与航拍设备台账'
];
TAB_SUBS 数组与 TAB_LIST 一一对应,在头部 Banner 中根据 currentTab 索引显示当前 Tab 的功能描述,让用户始终知道当前所在页面的定位和用途。
Camera Kit 枚举与预设
interface EffectInfo {
type: number;
name: string;
desc: string;
}
const EFFECT_INFOS: EffectInfo[] = [
{ type: 0, name: 'BEAUTY', desc: '美颜 · since 20' },
{ type: 1, name: 'PORTRAIT', desc: '人像 · since 20' },
{ type: 2, name: 'AUTO_FRAMING', desc: '影随人动 · 6.1.1 新增' }
];
EFFECT_INFOS 对应 ControlCenterEffectType 枚举,三种效果中 AUTO_FRAMING(type=2)是本应用核心使用特性。枚举表中标注了 since 20 和 6.1.1 新增,明确区分了既有能力和新增能力,在相机 Tab 的效果类型卡片中用于教育性展示。
interface FocusPreset {
label: string;
distance: number;
scene: string;
}
const FOCUS_PRESETS: FocusPreset[] = [
{ label: '近距', distance: 0.2, scene: '0.2 · 电池序列号核验' },
{ label: '中距', distance: 0.5, scene: '0.5 · 桨叶裂纹检查' },
{ label: '远景', distance: 0.9, scene: '0.9 · 起飞环境观察' }
];
对焦预设分三档,对应无人机起飞前巡检的三种典型场景:近距离对焦检查电池序列号和接口触点、中距离对焦检查桨叶裂纹和电机状态、远距离对焦观察起飞环境和净空障碍物。distance 值域 0.0~1.0,与 Camera Kit 的 setFocusDistance 参数对齐。
ArkWeb 快捷站点
interface QuickSite {
icon: string;
name: string;
url: string;
}
const QUICK_SITES: QuickSite[] = [
{ icon: '🚁', name: '大疆社区', url: 'https://community.dji.com' },
{ icon: '📸', name: '天空之城', url: 'https://www.skypixel.com' },
{ icon: '🛩️', name: 'UOM 平台', url: 'https://uom.caac.gov.cn' },
{ icon: '📋', name: '民航局专栏', url: 'https://www.caac.gov.cn' }
];
快捷站点列表包含四个无人机航拍行业的高频网站:大疆社区(设备讨论和固件更新)、天空之城(航拍作品展示和素材站)、UOM 平台(民航局无人机空域申报系统)和民航局专栏(政策法规)。在网页 Tab 中以横滑卡片形式展示,点击即加载对应 URL。
Canvas 图表数据
const FLIGHT_LINE_DATA: number[] = [24, 18, 32, 27, 35, 21, 38, 29, 33, 25, 36, 31];
const FLIGHT_LABELS: string[] = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
const FLIGHT_LINE_MAX: number = 45;
const MONTH_NAME: string[] = ['04', '05', '06', '07', '08', '09'];
const MONTH_FLIGHTS: number[] = [18, 24, 29, 26, 35, 31];
const MONTH_MAX: number = 40;
折线图数据覆盖近 12 次飞行架次的单架时长(分钟),最高 38 分钟(第 7 架次),最低 18 分钟(第 2 架次),波动趋势清晰。柱状图数据覆盖近 6 个月的飞行架次次数,8 月达到峰值 35 次。
3.4 辅助函数群
应用在 struct 外部定义了 9 个辅助函数,覆盖状态配色、字节数换算和语义转换等通用逻辑。
任务状态配色
function missionStateColor(s: string): string {
if (s === '已完成') { return COLORS.green; }
if (s === '执行中') { return COLORS.cyan; }
if (s === '素材整理') { return COLORS.orange; }
return COLORS.text3;
}
将航拍任务的四种状态映射为语义色:已完成用通过绿(成功/完成)、执行中用天空青(主色/进行中)、素材整理用护航橙(注意/处理中)、待起飞用灰青(弱化/待命)。
字节数换算
function fmtBytes(bytes: number): string {
if (bytes >= 1073741824) { return (bytes / 1073741824).toFixed(1) + ' GB'; }
if (bytes >= 1048576) { return (bytes / 1048576).toFixed(1) + ' MB'; }
if (bytes >= 1024) { return (bytes / 1024).toFixed(1) + ' KB'; }
return bytes.toString() + ' B';
}
fmtBytes 将 getTotalBytes() 返回的字节数换算为人类可读格式——B/KB/MB/GB,保留一位小数。这个函数在 onDownloadFinish 回调中使用,将下载完成后的文件大小格式化后存入 DownloadRecord。
其余函数包括 readStateColor(对焦校验结果配色)、framingStateColor(影随人动状态配色)、distanceLabel(对焦距离→巡检景别文案)、dlStateColor(下载状态配色)、sessionLabel(会话模式中文标签)、batteryColor(电池电量配色)、gearKindColor(设备类别配色),均遵循"语义→视觉"的统一映射模式。
3.5 数据模型:@Observed 响应式实体
航拍任务条目
@Observed export class MissionItem {
name: string;
area: string;
progress: number;
state: string;
constructor(name: string, area: string, progress: number, state: string) {
this.name = name;
this.area = area;
this.progress = progress;
this.state = state;
}
}
MissionItem 封装航拍任务的核心信息——任务名、拍摄区域、进度(0~100)和状态(待起飞/执行中/素材整理/已完成)。使用 @Observed 装饰后,修改 item.area 或 item.progress 属性即可触发列表对应行的重渲染,无需替换整个数组。种子数据包含 7 条航拍任务,涵盖滨江夜景巡查、婚庆跟拍、光伏电站巡检等真实场景。
下载记录(双 URL 溯源)
@Observed export class DownloadRecord {
fileName: string;
fileSize: string;
finishTime: string;
originalUrl: string;
referrerUrl: string;
constructor(fileName: string, fileSize: string, finishTime: string,
originalUrl: string, referrerUrl: string) {
this.fileName = fileName;
this.fileSize = fileSize;
this.finishTime = finishTime;
this.originalUrl = originalUrl;
this.referrerUrl = referrerUrl;
}
}
DownloadRecord 是 ArkWeb 双 URL 溯源特性的数据载体,携带五要素:建议文件名(getSuggestedFileName)、文件大小(getTotalBytes 换算)、完成时间、原始 URL(getOriginalUrl,文件直链来源)和引用页 URL(getReferrerUrl,触发下载的页面)。种子数据包含 6 条下载记录,涵盖 4K 航拍素材、空域数据包、固件更新、预设文件、飞行日志和交付包等真实下载场景。
此外还有 FocusRecord(对焦校验记录)、GearItem(航拍设备)和 BatteryItem(智能电池状态)三个 @Observed 数据模型,全部采用全参构造函数确保数据完整性。
3.6 页面结构体与状态管理
@Entry
@Component
struct Page1295 {
struct 内部将成员变量分为七个组:Tab 状态、弹窗状态、动画状态、航拍任务数据、Camera 成员、Canvas 成员、ArkWeb 成员。
Camera Kit 成员
private previewController: XComponentController = new XComponentController();
private cameraInput?: camera.CameraInput;
private previewOutput?: camera.PreviewOutput;
private videoSession?: camera.VideoSession;
private photoSession?: camera.PhotoSession;
@State surfaceReady: boolean = false;
@State sessionMode: string = 'idle';
@State framingState: string = '未查询';
@State framingSupported: boolean = false;
@State focusSupported: boolean = false;
@State focusDistance: number = 0.5;
@State focusRecords: FocusRecord[] = [];
@State permState: string = '未申请';
控制器类成员(previewController、cameraInput、previewOutput、videoSession、photoSession)声明为 private,因为它们不直接参与 UI 渲染;而面向 UI 的状态变量全部使用 @State。sessionMode 有三个值:idle(空闲)、video(影随人动会话)、photo(手动对焦会话),控制模式切换按钮的高亮状态和对焦 Tab 的按钮可用性。
Canvas 成员
private ringCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(true));
private lineCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(true));
@State ringReady: boolean = false;
@State lineReady: boolean = false;
两个 Canvas 上下文分别用于进度环和折线图。ringReady 和 lineReady 是 Canvas 就绪标志,仅在 onReady 回调后置为 true,呼吸定时器中根据这两个标志决定是否调用重绘——避免在 Canvas 尚未就绪时调用绘制方法导致异常。
ArkWeb 成员
private webController: webview.WebviewController = new webview.WebviewController();
private downloadDelegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();
@State urlInput: string = QUICK_SITES[0].url;
@State webUrl: string = QUICK_SITES[0].url;
@State dlName: string = '';
@State dlPercent: number = 0;
@State dlState: string = '空闲';
@State downloadRecords: DownloadRecord[] = DOWNLOAD_LIST;
ArkWeb 成员是本应用最核心的特性组。webController 是 Web 视图控制器,负责加载 URL 和主动发起下载。downloadDelegate 是下载代理实例,在 aboutToAppear 中注册四个回调后绑定到 webController。
urlInput 和 webUrl 采用双状态分离设计——urlInput 是地址栏的输入值(用户正在编辑),webUrl 是 Web 组件实际加载的值(点击"前往"后同步),这种分离避免了用户输入过程中 Web 组件频繁重新加载。
3.7 生命周期方法
aboutToAppear
aboutToAppear() {
this.setupDownloadDelegate();
this.focusRecords.unshift(new FocusRecord(0.9, 0.9, '已生效'));
this.focusRecords.unshift(new FocusRecord(0.5, 0.52, '读回偏差'));
this.focusRecords.unshift(new FocusRecord(0.2, 0.2, '已生效'));
this.timer = setInterval(() => {
this.breath = !this.breath;
if (this.ringReady) { this.drawRing(); }
if (this.lineReady) { this.drawLine(); }
}, 1000);
}
aboutToAppear 完成三项初始化:
- 下载代理注册:调用
setupDownloadDelegate()注册WebDownloadDelegate的四个回调并绑定到webController,使网页内触发的下载进入回调链路。 - 种子对焦记录:预先注入 3 条对焦校验记录(远景 0.9 已生效、中距 0.5 读回偏差、近距 0.2 已生效),展示"已生效"和"读回偏差"两种典型状态。
- 呼吸定时器:每 1000ms 翻转
breath并根据 Canvas 就绪状态重绘进度环和折线图,实现"呼吸"动画效果。
aboutToDisappear 与 switchTab
aboutToDisappear() {
clearInterval(this.timer);
this.releaseSession();
}
switchTab(idx: number) {
if (this.currentTab === 1 && idx !== 1) {
this.releaseSession();
}
this.currentTab = idx;
}
页面销毁时清理定时器并释放相机资源。Tab 切换时,离开相机 Tab(index=1)会主动释放会话——因为相机是独占资源,不释放会阻止其他相机应用使用。
3.8 Camera Kit 方法群
Camera Kit 方法群包括权限申请、影随人动模式启动、AUTO_FRAMING 能力链、手动对焦模式切换、会话释放、能力查询和设置读回校验七个方法。
影随人动模式
async startVideoMode() {
if (!this.surfaceReady) { this.framingState = 'Surface 未就绪'; return; }
if (this.sessionMode === 'video') { return; }
if (this.sessionMode === 'photo') { await this.releaseSession(); }
const granted = await this.requestCameraPermission();
if (!granted) { this.framingState = '权限被拒'; return; }
try {
const ctx = this.getUIContext().getHostContext();
const manager = camera.getCameraManager(ctx);
let device: camera.CameraDevice | undefined = undefined;
for (const d of manager.getSupportedCameras()) {
if (d.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK) {
device = d; break;
}
}
const capability = manager.getSupportedOutputCapability(device, camera.SceneMode.NORMAL_VIDEO);
const profile = capability.previewProfiles.length > 0 ? capability.previewProfiles[0] : undefined;
this.cameraInput = manager.createCameraInput(device);
await this.cameraInput.open();
this.previewOutput = manager.createPreviewOutput(profile,
this.previewController.getXComponentSurfaceId());
this.videoSession = manager.createSession<camera.VideoSession>(camera.SceneMode.NORMAL_VIDEO);
this.videoSession.on('error', (err: BusinessError) => { console.error(`session error: ${err.code}`); });
this.videoSession.beginConfig();
this.videoSession.addInput(this.cameraInput);
this.videoSession.addOutput(this.previewOutput);
await this.videoSession.commitConfig();
this.queryFraming(this.videoSession);
await this.videoSession.start();
this.sessionMode = 'video';
} catch (e) {
const err = e as BusinessError;
this.framingState = `会话失败(${err.code})`;
await this.releaseSession();
}
}
startVideoMode 是影随人动模式的完整启动流程,步骤极为丰富。首先进行前置检查:确认 Surface 已就绪、当前不是 video 模式(避免重复启动)、如果处于 photo 模式则先释放。然后申请 CAMERA 权限。获取权限后进入核心流程:获取相机管理器 → 选择后摄 → 获取预览 Profile → 创建输入流并打开 → 创建预览输出绑定到 Surface → 创建 VideoSession 并配置 → 在 start() 之前调用 queryFraming() 查询并启用影随人动 → 启动会话 → 更新 sessionMode 为 video。
AUTO_FRAMING 能力链
queryFraming(session: camera.VideoSession) {
if (!session.isControlCenterSupported()) {
this.framingState = '控制中心不支持';
this.framingSupported = false;
return;
}
const effects = session.getSupportedEffectTypes();
this.framingSupported = effects.includes(camera.ControlCenterEffectType.AUTO_FRAMING);
if (!this.framingSupported) { this.framingState = 'AUTO_FRAMING 未声明'; return; }
try {
session.enableControlCenter(true);
this.framingState = '影随人动已启用';
} catch (e) {
this.framingState = `接管失败(${(e as BusinessError).code})`;
}
}
三步能力链:第一步 isControlCenterSupported() 查询控制中心支持性;第二步 getSupportedEffectTypes() 获取效果枚举列表并检查是否包含 AUTO_FRAMING;第三步 enableControlCenter(true) 请求系统接管画面构图。每一步都有状态文案反馈,UI 层通过 framingStateColor() 映射为颜色(天空青=已启用、护航橙=能力缺失、警示红=失败)。
手动对焦三接口
queryFocusSupport() {
if (this.photoSession === undefined) { this.focusSupported = false; return; }
try {
this.focusSupported = this.photoSession.isFocusDistanceSupported();
} catch (e) {
this.focusSupported = false;
}
}
applyFocus() {
if (this.photoSession === undefined) {
this.focusRecords.unshift(new FocusRecord(this.focusDistance, -1, '失败(未启动拍照会话)'));
return;
}
try {
this.photoSession.setFocusDistance(this.focusDistance);
const readBack = this.photoSession.getFocusDistance();
const ok = Math.abs(readBack - this.focusDistance) < 0.01 ? '已生效' : '读回偏差';
this.focusRecords.unshift(new FocusRecord(this.focusDistance, readBack, ok));
if (this.focusRecords.length > 20) { this.focusRecords.pop(); }
} catch (e) {
const err = e as BusinessError;
this.focusRecords.unshift(new FocusRecord(this.focusDistance, -1, `失败(${err.code})`));
}
}
手动对焦三接口构成"查询→设置→读回→校验"闭环:isFocusDistanceSupported() 同步查询能力,setFocusDistance(value) 设置对焦距离,getFocusDistance() 读回实际值,通过 Math.abs(readBack - set) < 0.01 判断是否在容差范围内。每次操作生成 FocusRecord 并置顶到时间线,最多保留 20 条。
3.9 ArkWeb 方法群:下载双 URL 溯源
下载代理注册
setupDownloadDelegate() {
this.downloadDelegate.onBeforeDownload((item: webview.WebDownloadItem) => {
const hostCtx = this.getUIContext().getHostContext();
const dir = hostCtx ? hostCtx.filesDir : '';
this.dlName = item.getSuggestedFileName();
this.dlPercent = 0;
this.dlState = '已开始';
item.start(dir + '/' + item.getSuggestedFileName());
});
this.downloadDelegate.onDownloadUpdated((item: webview.WebDownloadItem) => {
this.dlPercent = item.getPercentComplete();
this.dlState = '正在下载 ' + item.getPercentComplete() + '%';
});
this.downloadDelegate.onDownloadFailed((item: webview.WebDownloadItem) => {
this.dlState = '下载失败 · ' + item.getGuid();
this.dlPercent = 0;
});
this.downloadDelegate.onDownloadFinish((item: webview.WebDownloadItem) => {
const originalUrl: string = item.getOriginalUrl();
const referrerUrl: string = item.getReferrerUrl();
this.downloadRecords.unshift(new DownloadRecord(
item.getSuggestedFileName(),
fmtBytes(item.getTotalBytes()),
'刚刚', originalUrl, referrerUrl));
this.dlState = '下载完成';
this.dlPercent = 100;
});
try {
this.webController.setDownloadDelegate(this.downloadDelegate);
} catch (error) {
console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
}
setupDownloadDelegate 是 ArkWeb 双 URL 溯源特性的核心方法,注册 WebDownloadDelegate 的四个回调:
onBeforeDownload(下载开始前):这是最关键的回调——必须在此调用 item.start(path) 提供沙箱存储路径,否则下载任务永远停在 PENDING 状态。通过 getHostContext().filesDir 获取应用沙箱目录,拼接建议文件名构建完整路径。同时提取建议文件名并更新 UI 状态为"已开始"。
onDownloadUpdated(下载进行中):持续触发,通过 item.getPercentComplete() 获取进度百分比,更新 dlPercent 和 dlState 驱动下载 Tab 的进度条和状态文案实时刷新。
onDownloadFailed(下载失败):通过 item.getGuid() 获取失败标识,置失败文案并清零进度。
onDownloadFinish(下载完成):这是双 URL 溯源的核心回调。调用 item.getOriginalUrl() 获取下载项的原始 URL(文件直链来源地址),调用 item.getReferrerUrl() 获取引用页 URL(触发下载的页面地址),调用 item.getTotalBytes() 获取文件总大小并经 fmtBytes() 换算为人类可读格式。将五要素(文件名+大小+时间+原始URL+引用页URL)封装为 DownloadRecord 并 unshift 到下载记录列表顶部。
最后通过 webController.setDownloadDelegate(this.downloadDelegate) 将下载代理绑定到 Web 控制器——只有绑定后,网页内触发的下载才会进入上述回调链路。绑定操作包裹在 try-catch 中,消除可能的抛错告警。
URL 加载与主动下载
loadUrl() {
let url = this.urlInput.trim();
if (url === '') { return; }
if (!url.startsWith('https://') && !url.startsWith('http://')) {
url = 'https://' + url;
}
this.urlInput = url;
this.webUrl = url;
}
triggerDownload(url: string) {
try {
this.dlName = url.slice(url.lastIndexOf('/') + 1);
this.dlPercent = 0;
this.dlState = '已发起下载请求';
this.webController.startDownload(url);
} catch (error) {
this.dlState = '发起失败 ' + (error as BusinessError).code;
}
}
loadUrl 实现地址栏的"前往"功能:校验协议前缀(无 http(s):// 时自动补 https://),然后同步 urlInput 和 webUrl。triggerDownload 实现应用侧主动发起下载——无需用户在网页内点击下载链接,直接调用 webController.startDownload(url) 即可触发下载流程,下载事件同样进入 Delegate 的四回调链路。这为"下载空域数据包"和"下载 4K 航拍样片"两个快捷按钮提供了实现基础。
3.10 Canvas 绘制方法群
任务完成率进度环
drawRing() {
const ctx = this.ringCtx;
ctx.antialias = true;
const cx = 90;
const cy = 90;
const r = 64;
const progress = this.missionRate() / 100;
const breathVal = this.breath ? 1.0 : 0.88;
// 外圈呼吸光环
ctx.globalAlpha = this.breath ? 0.42 : 0.14;
ctx.beginPath();
ctx.arc(cx, cy, r + 12, 0, Math.PI * 2);
ctx.strokeStyle = COLORS.cyanD;
ctx.lineWidth = 3;
ctx.stroke();
ctx.globalAlpha = 1;
// 背景环
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.strokeStyle = COLORS.dark;
ctx.lineWidth = 13;
ctx.stroke();
// 进度弧
ctx.beginPath();
ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * progress * breathVal);
ctx.strokeStyle = COLORS.cyan;
ctx.lineWidth = 13;
ctx.lineCap = 'round';
ctx.stroke();
// 中心百分比
ctx.fillStyle = COLORS.title;
ctx.font = 'bold 26px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(Math.round(progress * 100).toString() + '%', cx, cy + 2);
// 中心副标签
ctx.font = '10px sans-serif';
ctx.fillStyle = COLORS.text3;
ctx.fillText('今日任务完成率', cx, cy + 24);
}
drawRing 绘制五层环形进度图:
第一层:外圈呼吸光环。在进度环外测画一圈半径 r+12 的细环,透明度随 breath 在 0.42 和 0.14 之间波动,产生"光晕呼吸"效果。绘制完成后 globalAlpha 复位为 1,确保不影响后续图层。
第二层:背景环。用 COLORS.dark 画一个完整的粗环(lineWidth=13),作为进度弧的底色。
第三层:进度弧。从 12 点方向(-Math.PI/2)起笔,弧长为 2π × progress × breathVal。breathVal 随呼吸在 1.0 和 0.88 之间波动,使弧长产生 12% 的微变——模拟"心跳"般的进度波动。使用 lineCap = 'round' 圆头线帽,让弧段端点圆润。COLORS.cyan 天空青是主色。
第四层:中心百分比大字。在环心绘制 Math.round(progress * 100) + '%' 的粗体大字,数据源是 missionRate() 方法计算的任务平均完成率。
第五层:中心副标签。在百分比下方绘制"今日任务完成率"小字。
飞行架次时长折线图
drawLine() {
const ctx = this.lineCtx;
ctx.antialias = true;
const w = ctx.width > 0 ? ctx.width : 340;
const h = 168;
const pad = 26;
const max = FLIGHT_LINE_MAX;
const n = FLIGHT_LINE_DATA.length;
const stepX = (w - pad * 2) / (n - 1);
const plotH = h - pad * 2 - 14;
const baseY = h - pad - 14;
// 背景网格
ctx.strokeStyle = COLORS.line;
ctx.lineWidth = 1;
for (let i = 0; i <= 3; i++) {
const y = pad + plotH * i / 3;
ctx.beginPath();
ctx.moveTo(pad, y);
ctx.lineTo(w - pad, y);
ctx.stroke();
}
// 天空青渐变填充
const grad = ctx.createLinearGradient(0, pad, 0, baseY);
grad.addColorStop(0, COLORS.cyan);
grad.addColorStop(1, COLORS.cyanFade);
ctx.beginPath();
ctx.moveTo(pad, baseY);
for (let i = 0; i < n; i++) {
const x = pad + i * stepX;
const y = baseY - (FLIGHT_LINE_DATA[i] / max) * plotH;
ctx.lineTo(x, y);
}
ctx.lineTo(w - pad, baseY);
ctx.closePath();
ctx.fillStyle = grad;
ctx.fill();
// 折线主线
ctx.beginPath();
for (let i = 0; i < n; i++) {
const x = pad + i * stepX;
const y = baseY - (FLIGHT_LINE_DATA[i] / max) * plotH;
if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); }
}
ctx.strokeStyle = COLORS.cyan;
ctx.lineWidth = 2;
ctx.stroke();
// 数据点(末点呼吸放大)
for (let i = 0; i < n; i++) {
const x = pad + i * stepX;
const y = baseY - (FLIGHT_LINE_DATA[i] / max) * plotH;
ctx.beginPath();
ctx.arc(x, y, i === n - 1 ? (this.breath ? 4.5 : 3.5) : 3, 0, Math.PI * 2);
ctx.fillStyle = COLORS.card;
ctx.fill();
ctx.strokeStyle = COLORS.cyan;
ctx.lineWidth = 1.5;
ctx.stroke();
}
// 横轴架次标签
ctx.font = '9px sans-serif';
ctx.textAlign = 'center';
ctx.fillStyle = COLORS.text3;
for (let i = 0; i < n; i += 2) {
const x = pad + i * stepX;
ctx.fillText(FLIGHT_LABELS[i], x, h - 4);
}
}
drawLine 绘制五层折线图:
第一层:背景网格。3 条水平线均匀分布,使用 COLORS.line 深色分割线色。
第二层:天空青渐变填充。使用 createLinearGradient 从顶部天空青到底部 cyanFade(近乎透明),沿折线路径绘制闭合区域并填充渐变,形成面积图效果。
第三层:折线主线。天空青描边,线宽 2px,从第一个点 moveTo 开始 lineTo 连接。
第四层:数据点。每个数据点画白心青边小圆点(半径 3px),末点(最新架次)在 breath 为 true 时放大到 4.5px,产生脉冲效果。
第五层:横轴标签。每隔一个数据点绘制架次序号,避免拥挤。
3.11 Builder 群:页面骨架
头部 Banner
头部使用天空青渐变背景(linearGradient 从 cyanD 到 cyan 再到 cyanD),包含品牌语"云镜" + Tab 联动副标题 + 三枚统计胶囊(任务数/月架次/素材库大小)+ 呼吸圆点状态。下方是搜索条和"新建任务"按钮的横排布局。
任务 Tab
任务 Tab 是首页,包含四块内容:
- 航拍任务进度条清单:
ForEach遍历missionList,每个任务项包含名称、状态胶囊、区域、进度条和"改"/"取消"操作按钮。进度条颜色通过missionStateColor()按状态着色。 - Canvas 完成率环卡:左侧 180×180 的 Canvas 进度环,右侧是统计文字和图例。
onReady回调置ringReady = true并首绘。 - 近 12 次飞行架次时长折线卡:全宽 Canvas 折线图,
onReady置lineReady = true并首绘。 - 电池电量状态卡:
ForEach遍历batteryList(四仓电池),每仓显示电量进度条、状态胶囊和百分比。充电中的仓位 emoji 透明度随呼吸变化。
相机 Tab
相机 Tab 独占高度,包含授权状态卡、模式切换行(影随人动/手动对焦)、XComponent 预览本体(layoutWeight(1) 占满中间)、以及滚动信息区(影随人动能力链状态卡 + 效果枚举表)。
对焦 Tab
对焦 Tab 是手动对焦三接口的操作面板:能力查询卡 → 三档巡检预设 → 焦距滑杆卡 → 设置/读回按钮行 → 对焦巡检记录时间线。与相机 Tab 共用同一 XComponent Surface。
网页 Tab
网页 Tab 独占高度,包含地址栏、快捷站点横滑、Web 组件本体(layoutWeight(1))和主动下载演示行。Web 组件加载 webUrl 指定的 URL,网页内点击下载链接自动进入 Delegate 四回调。
下载 Tab
下载 Tab 包含三块内容:
- 进行中任务卡:文件名 + Progress 进度条 + 百分比 + 状态文案,实时反映
onDownloadUpdated的进度刷新。 - 已完成双 URL 溯源记录列表:
ForEach遍历downloadRecords,每条记录展示序号、文件名、大小+时间、🔗原始URL(蓝色 monospace)和📄引用页URL(灰色 monospace)两行。 - onDownloadFinish 代码预览卡:深色底 monospace 两行核心调用代码(
getOriginalUrl()和getReferrerUrl()),配以四回调流程说明。
我的 Tab
飞手渐变大卡(天空青渐变背景,含持证等级/飞行小时/累计架次)+ 设备清单行(左色条按类别着色)+ 月度飞行柱状图(传统 Column+ForEach,breath 联动柱高微动)。
底部导航栏
6 Tab 单排自绘,选中态天空青粗体,未选中态暗青灰常规体,顶部有 1px 分割线。
3.12 弹窗系统
三个全屏弹窗通过 Stack 覆盖层叠加在主界面上方:
- panelAdd:新建航拍任务弹窗,包含任务名输入框、拍摄区域输入框和初始进度滑杆,确认时创建
MissionItem并unshift置顶。 - panelEdit:修改任务区域弹窗,包含区域输入框和进度滑杆,确认时直接修改
MissionItem的area和progress属性——@Observed使属性级变更自动触发列表行重渲染。 - panelDel:取消任务确认弹窗,红色警示按钮,确认时
splice移除对应条目。
四、三特性技术对比表
| 维度 | Camera Kit 特性一 | Camera Kit 特性二 | ArkWeb 特性 | Canvas 特性一 | Canvas 特性二 |
|---|---|---|---|---|---|
| 能力名称 | AUTO_FRAMING 影随人动 | 手动对焦三接口 | 下载双 URL 溯源 | drawRing 进度环 | drawLine 折线图 |
| 宿主模块 | VideoSession ControlCenter | PhotoSession ManualFocus | WebDownloadDelegate | CanvasRenderingContext2D | CanvasRenderingContext2D |
| 核心API | isControlCenterSupported / getSupportedEffectTypes / enableControlCenter | isFocusDistanceSupported / setFocusDistance / getFocusDistance | onBeforeDownload / onDownloadUpdated / onDownloadFailed / onDownloadFinish + getOriginalUrl / getReferrerUrl | arc / stroke / fillText | createLinearGradient / moveTo / lineTo |
| 调用模式 | 同步查询 + 同步启用 | 同步查询 + 同步设置 + 同步读回 | 异步回调(事件驱动) | 同步绘制 | 同步绘制 |
| 返回数据 | 布尔值 + 枚举数组 | 布尔值 + 数字 | WebDownloadItem 对象(含 URL/大小/文件名/进度) | 无(直接渲染) | 无(直接渲染) |
| UI呈现 | 能力链状态卡 + 枚举表 | 预设档位 + 滑杆 + 校验时间线 | 进度卡 + 双URL溯源列表 + 代码预览卡 | 进度环 + 统计文字 + 图例 | 网格 + 渐变面积 + 折线 + 数据点 |
| 容错策略 | 三步降级 | try-catch + 读回差值校验 | 四回调全覆盖 + start() 强制落盘 + try-catch | onReady首绘 + 就绪标志 | onReady首绘 + 就绪标志 |
| 状态联动 | framingState / framingSupported | focusSupported / focusDistance / focusRecords | dlName / dlPercent / dlState / downloadRecords | breath(弧长波动) | breath(末点放大) |
| 航拍场景 | 飞手地面移动时云台自动跟拍 | 起飞前巡检电池/桨叶/环境 | 素材下载来源追溯与版权审计 | 任务完成率可视化 | 飞行架次时长趋势追踪 |
| 生命周期管理 | releaseSession() 统一释放 | releaseSession() 统一释放 | aboutToAppear 注册绑定 | clearInterval 停止重绘 | clearInterval 停止重绘 |
| API版本 | 6.1.1 新增 | 6.1.1 新增 | API 24 (6.1.1) | ArkUI 基础 | ArkUI 基础 |
| 资源独占性 | cameraInput 独占 | 与 VideoSession 互斥 | Web 组件内下载触发 | 无 | 无 |
五、深度总结
5.1 架构设计亮点
本应用在单页面文件中实现了 Camera Kit、ArkWeb、Canvas 三大套件的深度叠加,每个套件都构建了完整的能力链路:
Camera Kit 侧,VideoSession 和 PhotoSession 两种会话模式互斥切换。AUTO_FRAMING 影随人动通过"能力查询→效果枚举→启用接管"三步链路实现,每一步都有状态文案反馈和颜色映射。手动对焦三接口通过"设置→读回→差值校验"的闭环验证对焦操作的真实生效,时间线记录让操作历史可追溯。在航拍场景中,影随人动用于飞手地面移动时的云台自动跟拍,手动对焦用于起飞前的电池序列号、桨叶裂纹和起飞环境巡检——每个特性都映射到明确的业务需求。
ArkWeb 侧,WebDownloadDelegate 的四个回调覆盖了下载的完整生命周期,从 onBeforeDownload 的沙箱路径落盘,到 onDownloadUpdated 的实时进度刷新,到 onDownloadFailed 的失败处理,再到 onDownloadFinish 的双 URL 溯源——形成了完整的下载事件处理闭环。getOriginalUrl() 和 getReferrerUrl() 的双 URL 溯源能力使每个下载素材都携带"文件来自哪里"和"从哪个页面下载的"两条溯源线索,对于素材版权追溯和下载路径审计具有实际的产品价值。同时,应用侧主动下载(startDownload)和网页内被动下载(Delegate 回调)走同一套处理链路,保证了行为一致性。
Canvas 侧,drawRing 进度环通过五层绘制(呼吸光环→背景环→进度弧→中心百分比→副标签)实现丰富的视觉层次,breath 联动弧长波动产生"心跳"效果。drawLine 折线图通过五层绘制(网格→渐变面积→折线→数据点→标签)实现标准的数据可视化呈现,末点呼吸放大突出最新数据。Canvas 就绪标志(ringReady / lineReady)确保重绘只在 Canvas 初始化完成后执行,避免空指针异常。
5.2 ArkWeb 双 URL 溯源的工程价值
getOriginalUrl() 和 getReferrerUrl() 是 ArkWeb 在 API 24 中新增的关键能力。在本应用的下载 Tab 中,每条下载记录都展示了两行 URL:原始 URL 用信息蓝色 monospace 字体显示文件直链来源,引用页 URL 用灰色 monospace 字体显示触发下载的页面。这种双 URL 展示让飞手可以清楚地追溯每个素材的来源——是从天空之城的作品页下载的航拍素材?还是从 UOM 平台下载的空域数据?还是从大疆社区下载的固件更新?这种溯源能力在素材管理和版权审计场景中具有不可替代的价值。
5.3 状态管理的工程实践
urlInput/webUrl双状态分离:用户编辑地址栏时只更新urlInput,点击"前往"后才同步到webUrl,避免输入过程中 Web 组件频繁重新加载。- Canvas 就绪标志:
ringReady和lineReady在onReady回调后置为 true,呼吸定时器中根据标志决定是否重绘,避免 Canvas 未就绪时调用绘制方法。 @Observed精细追踪:MissionItem的area和progress属性修改直接触发列表行重渲染,无需替换整个数组。- 先清空引用再异步释放:
releaseSession()先将控制器变量置为 undefined,再按顺序异步释放,防止 await 期间二次调用。
5.4 健壮性设计
- 权限链路:CAMERA 权限动态申请,申请结果布尔化处理。
- 相机链路:会话创建全链 try-catch,失败时调用 releaseSession 清理,能力查询异常时降级为"不支持"。
- 下载链路:
onBeforeDownload中必须调用start()提供沙箱路径,否则下载永远停在 PENDING——这是 ArkWeb 下载代理的关键约束。setDownloadDelegate绑定包裹 try-catch,startDownload主动下载也包裹 try-catch。 - URL 协议校验:
loadUrl()自动补https://前缀,避免无协议 URL 导致 Web 组件加载失败。
5.5 产品与技术的深度融合
本应用将每个 HarmonyOS 特性精确映射到无人机航拍场景的真实需求:AUTO_FRAMING 影随人动用于飞手地面移动时的云台跟拍,手动对焦三档预设用于起飞前巡检,WebDownloadDelegate 四回调用于素材下载管理,双 URL 溯源用于素材来源追溯,Canvas 进度环用于任务完成率可视化,折线图用于飞行架次时长追踪。这种"技术能力→业务场景"的精确映射,使应用既有技术深度又有产品温度,是 HarmonyOS 全场景开发理念在无人机航拍垂直领域的优秀实践。
附录:DevEco Studio 创建新项目与查看 SDK 版本
本章节演示如何使用 DevEco Studio 创建一个 HarmonyOS 新项目,并查看当前 IDE 已安装的 SDK 版本,适合作为其他技术博文的补充操作指南。
一、创建新项目
1.1 进入欢迎界面
启动 DevEco Studio 后,首先看到的是欢迎界面。左侧导航栏默认选中 “项目”,右侧提供三个主要入口:
- 新建项目:从头创建新项目
- 打开项目:打开本地已有项目
- 克隆仓库:从 Git 等版本控制拉取代码
点击 “新建项目” 按钮,进入项目创建向导。

1.2 选择项目模板
在弹出的"新建项目"对话框中,左侧分类标签提供了两种项目类型:
| 类型 | 说明 |
|---|---|
| 应用(Application) | 开发标准的 HarmonyOS 应用,具备完整的 Ability 生命周期 |
| 元服务(Atomic Service) | 开发轻量级的原子化服务,无需安装即可使用 |
选择 “应用” 标签后,右侧展示多种模板。对于大多数场景,推荐选择 “Empty Ability” —— 这是一个最基础的入门模板,仅包含 Hello World 功能,适合从零开始构建应用。

1.3 配置项目信息
点击 “下一步” 后,进入项目配置界面,需要填写以下核心参数:
| 配置项 | 示例值 | 说明 |
|---|---|---|
| 项目名称(Project name) | rollboat | 应用的项目名称,建议使用英文命名 |
| 包名(Bundle name) | com.rollboat.myapplication | 应用唯一标识,采用反向域名格式 |
| 保存路径(Save location) | D:\CodeFactory\rollboat | 项目本地存储路径,避免使用中文和空格 |
| 兼容 SDK(Compatible SDK) | 6.1.1(24) | 目标 HarmonyOS API 版本,点击"查看参考"可了解各版本差异 |
| 模块名称(Module name) | entry | 主模块名称,默认 entry 为应用入口模块 |
| 设备类型(Device types) | ☑ Phone | 勾选目标设备:Phone / Tablet / 2in1 / Car / Wearable / TV |
右侧预览区会实时展示当前模板的默认效果 —— 一个居中显示的 “Hello World” 文本。

1.4 完成创建
确认配置无误后,点击右下角 “完成” 按钮,IDE 将自动执行以下操作:
- 生成项目骨架(Stage 模型目录结构)
- 执行
ohpm install安装依赖 - 运行 Hvigor 构建初始化(
Build Init)
构建日志中显示 “退出代码为 0” 表示项目初始化成功。

1.5 项目结构概览
创建完成后,左侧项目面板展示的是标准的 Stage 模型 目录结构:
rollboat/
├── .hvigor/ # Hvigor 构建工具缓存
├── .idea/ # IDE 配置文件
├── AppScope/ # 应用级全局配置
│ └── app.json5
├── entry/ # 主模块(入口模块)
│ ├── src/main/ets/
│ │ ├── entryability/ # Ability 生命周期管理
│ │ │ └── EntryAbility.ets
│ │ └── pages/ # UI 页面
│ │ └── Index.ets # 首页(默认 Hello World)
│ ├── src/main/resources/ # 资源文件
│ ├── module.json5 # 模块配置
│ └── build-profile.json5 # 构建配置
├── oh_modules/ # OHPM 依赖包
├── build-profile.json5 # 工程构建配置
├── hvigorfile.ts # Hvigor 构建脚本
└── oh-package.json5 # 包管理配置
核心文件 Index.ets 的默认代码如下,采用 ArkTS 声明式 UI 语法:
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
this.message = 'Welcome';
})
}
.height('100%')
.width('100%')
}
}
| 关键语法 | 作用 |
|---|---|
@Entry | 标记为页面入口,可用于路由跳转 |
@Component | 声明为自定义组件 |
@State | 状态变量,数据变更时自动触发 UI 刷新 |
RelativeContainer | 相对布局容器,替代传统线性布局 |
.onClick() | 点击事件,此处点击后文本变为 “Welcome” |
打开右侧 Previewer(预览器),选择 Phone 设备,即可实时预览 Hello World 效果,无需连接真机或启动模拟器。

二、查看 SDK 版本
2.1 查看 HarmonyOS SDK
DevEco Studio 安装时已内置 HarmonyOS SDK,无需单独下载。通过以下路径查看:
文件 → 设置 → HarmonyOS SDK(或快捷键
Ctrl + Alt + S搜索 “HarmonyOS SDK”)
在设置面板中,可以看到当前已安装的 SDK 版本信息:
| 名称 | 阶段 | 状态 |
|---|---|---|
| HarmonyOS 6.1.1 | Release | ✅ 已安装 |
界面顶部提示:“HarmonyOS SDK 已经包含在 IDE,无需单独安装”,省去了手动配置 SDK 的繁琐步骤。

2.2 查看 ArkUI-X SDK(跨平台扩展)
如果项目需要将 ArkUI 框架扩展到多个 OS 平台(Android / iOS / OpenHarmony),还需要配置 ArkUI-X SDK。路径如下:
文件 → 设置 → 语言和框架 → ArkUI-X
在这里可以查看已安装和可选的 ArkUI-X SDK 版本:
| 版本 | SDK 版本号 | 阶段 | 状态 |
|---|---|---|---|
| API Version 24 | 6.1.1.100 | Release | ✅ 已安装 |
| API Version 23 | 6.1.0.28 | Beta1 | 未安装 |
| API Version 22 | 6.0.2.112 | Release | 未安装 |
安装路径示例:D:\DevTools\ArkUI-X\sdk
说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

三、小结
| 步骤 | 操作 | 关键点 |
|---|---|---|
| 创建项目 | 欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成 | 使用 Stage 模型 + ArkTS 语言 |
| 查看 SDK | 设置 → HarmonyOS SDK | SDK 已内置,无需手动安装 |
| 跨平台扩展 | 设置 → ArkUI-X | 根据需要安装对应 API 版本 |
至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。
本文基于 DevEco Studio 6.1.1 Release 版本编写,不同版本界面可能存在细微差异。
更多推荐

所有评论(0)