宣纸黛青间的飞羽世界:HarmonyOS Notification Kit 通知授权流与 Map Kit 长按监听在城市观鸟导览中的全栈实践
城市观鸟是一项兼具科学性与休闲性的户外活动,观鸟者需要在最佳时段前往最佳地点,观察并记录鸟类行为。一个完整的观鸟导览应用需要覆盖鸟种图鉴浏览、观鸟点地图导航、关键字搜索、相机跟拍、手动对焦、晨昏提醒和个人档案等多个功能模块。HarmonyOS 在 6.1.1 版本中提供了 Map Kit 的 searchByText 相关性检索与 Marker/POI 双长按监听、Camera Kit 的 AUTO_FRAMING 影随人动与 PhotoSession 手动对焦三接口,以及 Notification Kit 的 basicText 通知发布与授权四态流——三套件在同一页面文件中叠加,可以构建一个覆盖"图鉴→地图→搜索→相机→对焦→提醒→我的"七 Tab 全链路的观鸟导览应用。本文将逐段拆解"观羽"城市观鸟导览应用的代码实现,深入分析三大套件的叠加架构与工程细节。
一、技术前置:HarmonyOS 开发栈与三大套件概述
1.1 ArkUI 声明式 UI 范式

ArkUI 是 HarmonyOS 的应用 UI 框架,采用声明式编程范式。开发者通过 @Entry 和 @Component 装饰器声明页面入口与组件,在 build() 方法中通过链式调用组织容器组件构建视图树。@State 装饰器使成员变量变为响应式状态——任何变更自动触发依赖视图的重渲染。@Observed 装饰器扩展了状态追踪粒度,使 class 实例的属性级变更也能被框架感知。@Builder 装饰器用于声明可复用视图片段,无需拆分为独立 struct,在当前组件内部定义和调用。

1.2 Map Kit 地图服务套件
Map Kit 通过 MapComponent 组件嵌入地图视图,配合 mapCommon、map、site 模块提供地图初始化、标注管理和 POI 搜索能力。在 HarmonyOS 6.1.1 中,Map Kit 新增了 Marker 长按监听(onMarkerLongClick,回调参数 map.Marker)和 POI 长按监听(onPoiLongClick,回调参数 mapCommon.Poi),使地图从静态展示进化为可交互的操作面。site.searchByText 接口提供基于关键字 + 位置 + 半径的 POI 检索,返回结果中携带 reliability 相关性分数(0~1),可用于搜索结果的排序与质量分档展示。

1.3 Camera Kit 相机服务套件
Camera Kit 围绕 CameraManager、CameraInput、PreviewOutput 和 Session(会话)展开。在 6.1.1 版本中引入两项关键特性:

AUTO_FRAMING 影随人动:挂载于 VideoSession,通过 Control Center 控制中心实现。调用链路为 isControlCenterSupported() → getSupportedEffectTypes() → includes(AUTO_FRAMING) → enableControlCenter(true)。在观鸟场景中,当观鸟者在林间移动追踪鸟群时,预览画面自动跟随平移缩放,确保观鸟者始终居中。

手动对焦三接口:挂载于 PhotoSession,包含 isFocusDistanceSupported()(能力查询)、setFocusDistance(value)(设置 0.0~1.0)、getFocusDistance()(读回)。在观鸟场景中,近距对焦观察林缘灌丛地面觅食鸟、中距对焦观察树冠层枝头停歇鸟、远距对焦观察湿地水面和高空猛禽。

1.4 Notification Kit 通知服务套件
Notification Kit 是 HarmonyOS 的通知管理套件,通过 notificationManager 模块提供通知发布、授权查询和设置引导能力。在本应用中,Notification Kit 的核心使用包括:
- 授权查询:
notificationManager.isNotificationEnabled()返回 Promise,查询当前应用是否已被用户授权发送通知。 - 授权请求:
notificationManager.requestEnableNotification(context)首次调用时弹系统授权对话框;若用户曾拒绝,返回错误码 1600004,此时调用notificationManager.openNotificationSettings(context)拉起通知设置页引导手动开启——形成"查询→请求→设置页引导"的授权四态流。 - 通知发布:构建
NotificationRequest对象,设置id(自增 notifyId++ 避免覆盖上一条)、notificationSlotType(槽类型)、content(通知内容,使用NOTIFICATION_CONTENT_BASIC_TEXT基础文本类型),通过notificationManager.publish(request)发布。
二、应用全景:城市观鸟导览架构总览
本应用以"观羽"为产品名,以昆明为城市中心,采用 7 Tab 单排底部导航架构——比常规 6 Tab 多一个"提醒" Tab 来承载 Notification Kit 的通知发布功能。
2.1 整体架构流程
┌────────────────────────────────────────────────────────────────┐
│ 观羽主页面 (Page) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 头部区域 (headerMain) │ │
│ │ 应用名 + Tab 联动副标题 + 三特性状态胶囊 + 呼吸圆点 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ─────────────────── 分割线 ──────────────────────────────── │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 内容区 (7 Tab 切换) │ │
│ │ │ │
│ │ Tab0 图鉴 │ 双列鸟种卡 + 今日打卡横滑 + 最近观察记录行 │ │
│ │ Tab1 地图 │ MapComponent昆明6观鸟点 + 双长按监听 + 日志流 │ │
│ │ Tab2 搜索 │ searchByText + reliability三档标签 + 分数条 │ │
│ │ Tab3 相机 │ XComponent预览 + 影随人动 + 三效果枚举表 │ │
│ │ Tab4 对焦 │ 能力查询 + 三档景别预设 + 滑杆 + 读回校验时间线 │ │
│ │ Tab5 提醒 │ 晨昏观鸟时间轴 + 通知授权四态卡 + 发布按钮+历史 │ │
│ │ Tab6 我的 │ 观鸟人渐变大卡 + 装备清单 + 月度观察柱状图 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 底部导航栏 (tabBar) - 7 tab 单排自绘 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 弹窗系统 (Stack 覆盖层) │ │
│ │ 添加观察记录 / 修改提醒时段 / 删除确认 │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
2.2 三特性叠加关系
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ Map Kit │ │ Camera Kit │ │ Notification Kit │
│ (特性 A) │ │ (特性 B) │ │ (特性 C) │
├──────────────────┤ ├──────────────────┤ ├──────────────────────┤
│ searchByText │ │ AUTO_FRAMING │ │ isNotificationEnabled│
│ reliability分数 │ │ 影随人动 │ │ 授权查询 │
│ │ │ │ │ │
│ Marker长按监听 │ │ setFocusDistance │ │ requestEnableNotify │
│ POI长按监听 │ │ 手动对焦三接口 │ │ 授权请求+设置页引导 │
│ │ │ │ │ │
│ │ │ │ │ publish(basicText) │
│ │ │ │ │ 通知发布+历史记录 │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────────┘
│ │ │
└─────────────────────┼─────────────────────┘
│
┌────────┴────────┐
│ 统一状态管理 │
│ @State 驱动 │
│ @Observed 响应 │
│ 式数据模型 │
└─────────────────┘
2.3 通知授权四态流
┌─────────────────┐
│ isNotification │
│ Enabled() 查询 │
└────────┬────────┘
│
┌─────────────┴─────────────┐
│ │
已授权 granted 未授权 !granted
│ │
│ ┌──────┴──────┐
│ │ requestEnable│
│ │ Notification │
│ └──────┬──────┘
│ │
│ ┌───────────┴───────────┐
│ │ │
│ 用户同意授权 用户拒绝(1600004)
│ │ │
│ │ ┌────────┴────────┐
│ │ │openNotification │
│ │ │Settings()引导 │
│ │ └────────┬────────┘
│ │ │
│ │ 用户在设置页手动开启
└───────────────┴───────────────────────┘
│
┌──────────┴──────────┐
│ publish(request) │
│ 发布 basicText 通知 │
│ notifyId++ 自增 │
└─────────────────────┘
三、逐段代码深度解析
3.1 模块导入层
import { MapComponent, mapCommon, map, site } from '@kit.MapKit';
import { camera } from '@kit.CameraKit';
import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError, AsyncCallback } from '@kit.BasicServicesKit';
五行 import 引入了全部系统能力。第一行从 @kit.MapKit 解构导入 MapComponent(地图 UI 组件)、mapCommon(通用类型)、map(控制器类)和 site(搜索能力)。第二行从 @kit.CameraKit 导入 camera 命名空间。第三行从 @kit.AbilityKit 导入 abilityAccessCtrl(权限管理)和 common(通用上下文类型,用于获取 UIAbilityContext)。第四行从 @kit.NotificationKit 导入 notificationManager——这是本应用的核心差异点,Notification Kit 的通知发布与授权管理全部通过这个命名空间访问。第五行导入 BusinessError 和 AsyncCallback 通用类型。
3.2 颜色系统:浅色主题的设计语言
interface ColorPalette {
bg: string;
card: string;
chip: string;
title: string;
sub: string;
text3: string;
teal: string;
tealD: string;
green: string;
gold: string;
blue: string;
red: string;
line: string;
tabOn: string;
mask: string;
}
const COLORS: ColorPalette = {
bg: '#F5F3EB',
card: '#FFFFFF',
chip: '#EBE7DA',
title: '#2A2E22',
sub: '#5F6650',
text3: '#93998A',
teal: '#2E7D6B',
tealD: '#1F5F52',
green: '#5E8C61',
gold: '#C9A227',
blue: '#4E7BB0',
red: '#C0504D',
line: '#E2DCCB',
tabOn: '#2E7D6B',
mask: 'rgba(0,0,0,0.5)'
};
与前述深色主题应用不同,本应用采用浅色主题——“宣纸米白 + 黛青 + 榕绿”。背景色 #F5F3EB 是温暖的宣纸米白色调,卡片用纯白 #FFFFFF 保证内容区域清爽。主题色 #2E7D6B(黛青)取自传统中国画中的黛青颜料,用于 Tab 激活态、品牌渐变和所有主色元素。辅助色 #5E8C61(榕绿)用于常见鸟种标识和已生效状态。#C9A227(鸦金)用于稀有鸟种和中相关搜索结果。#C0504D(警示红)用于罕见鸟种和危险操作。
3.3 常量数据段
Tab 配置(7 Tab)
const TAB_LIST: TabMeta[] = [
{ icon: '🐦', label: '图鉴' },
{ icon: '🗺️', label: '地图' },
{ icon: '🔍', label: '搜索' },
{ icon: '📷', label: '相机' },
{ icon: '🔬', label: '对焦' },
{ icon: '🔔', label: '提醒' },
{ icon: '👤', label: '我的' }
];
7 个 Tab 遵循"图鉴浏览→地图导航→搜索→相机跟拍→手动对焦→通知提醒→个人档案"的产品逻辑。第 6 个 Tab"提醒"是本应用独有的 Tab,用于承载 Notification Kit 的通知发布和授权管理功能。
昆明观鸟点
const CITY_CENTER: mapCommon.LatLng = { latitude: 25.0389, longitude: 102.7183 };
const MARKER_SPOTS: SpotItem[] = [
{ name: '翠湖公园', lat: 25.0493, lng: 102.7075, tag: '越冬水鸟' },
{ name: '昆明植物园', lat: 25.1455, lng: 102.7415, tag: '林鸟观察径' },
{ name: '滇池海埂大坝', lat: 24.9550, lng: 102.6620, tag: '鸥群投喂点' },
{ name: '西山森林公园', lat: 25.0455, lng: 102.6280, tag: '猛禽观察台' },
{ name: '大观楼南园', lat: 25.0360, lng: 102.6720, tag: '湿地水鸟' },
{ name: '黑龙潭公园', lat: 25.1530, lng: 102.7460, tag: '古树留鸟' }
];
昆明六大观鸟点涵盖翠湖公园(越冬红嘴鸥聚集地)、昆明植物园(高原林鸟观察径)、滇池海埂大坝(鸥群投喂点)、西山森林公园(猛禽观察台)、大观楼南园(湿地水鸟)和黑龙潭公园(古树留鸟),每个点携带名称、坐标和标签三个字段。
对焦预设
const FOCUS_PRESETS: FocusPreset[] = [
{ label: '林缘灌丛', distance: 0.3, scene: '0.3 · 林缘灌丛近景' },
{ label: '树冠层', distance: 0.6, scene: '0.6 · 树冠层中景' },
{ label: '湿地远景', distance: 0.95, scene: '0.95 · 湿地远景' }
];
对焦预设对应观鸟的三种景别:近距观察林缘灌丛中地面觅食的鸟类(如山斑鸠)、中距观察树冠层枝头停歇的鸟类(如红嘴蓝鹊)、远距观察湿地水面和高空猛禽(如黑翅鸢)。
3.4 数据模型
应用定义了 8 个 @Observed 数据模型:
- BirdCard:鸟种图鉴卡(名称、科属、稀有度、最佳观察季)
- ObserveRow:观察记录行(日期、鸟种、地点、数量、行为备注)
- SearchRecord:搜索结果(名称、地址、距离、reliability 分数)
- EventLog:地图长按事件日志(类型、名称、坐标、时间)
- FocusRecord:对焦校验记录(设置值、读回值、结论)
- RemindItem:晨昏观鸟提醒(时段、鸟种、地点、开关)
- NoticeLog:通知发布历史(标题、正文、时间)
- GearItem:观鸟装备(图标、名称、规格、状态)
每个模型都使用 @Observed 装饰并采用全参构造函数,确保数据完整性和响应式追踪能力。种子数据涵盖昆明常见和特色鸟种(红嘴蓝鹊、红嘴鸥、黑翅鸢、蓝喉蜂虎等),观鸟点搜索结果覆盖 reliability 高中低三档(0.93~0.26),晨昏提醒覆盖一天 6 个鸟类活动高峰时段。
3.5 辅助函数群
function reliabilityScore(score: number): ScoreLevel {
if (score >= 0.8) { return { label: '高相关', color: COLORS.teal }; }
if (score >= 0.5) { return { label: '中相关', color: COLORS.gold }; }
return { label: '低相关', color: COLORS.text3 };
}
function rarityColor(rarity: string): string {
if (rarity === '罕见') { return COLORS.red; }
if (rarity === '稀有') { return COLORS.gold; }
return COLORS.green;
}
function framingStateColor(s: string): string {
if (s === '影随人动已启用') { return COLORS.green; }
if (s === '控制中心不支持' || s === 'AUTO_FRAMING 未声明') { return COLORS.blue; }
if (s.indexOf('失败') >= 0 || s.indexOf('被拒') >= 0 || s.indexOf('未就绪') >= 0) { return COLORS.red; }
return COLORS.text3;
}
辅助函数遵循"语义→视觉"的统一映射模式:reliabilityScore 将搜索分数分三档着色,rarityColor 将鸟种稀有度映射为颜色(罕见红/稀有金/常见绿),framingStateColor 将影随人动状态映射为颜色。此外还有 readStateColor(对焦校验配色)、sessionLabel(会话模式标签)、distanceLabel(对焦距离→观鸟景别文案)、gearStateColor(装备状态配色)、typeColor(长按事件类型配色)和 nowTime(时间戳)等函数。
3.6 页面结构体与状态管理
@Entry
@Component
struct Page1296 {
@State currentTab: number = 0;
@State addModal: boolean = false;
@State editModal: boolean = false;
@State delModal: boolean = false;
// ...
struct 内部将成员变量分为八组:Tab 状态、弹窗状态、弹窗表单缓存、动画状态、业务数据数组、Map Kit 状态、Camera Kit 成员、Notification Kit 状态。
Notification Kit 状态
@State granted: boolean = false;
@State notifyId: number = 300;
granted 是通知授权状态,通过 isNotificationEnabled() 查询初始化。notifyId 是通知 ID 自增基数,每次发布通知 notifyId++ 避免覆盖上一条——Notification Kit 的 publish() 方法使用相同的 id 会覆盖之前的通知,因此自增 ID 是确保每条通知独立展示的关键设计。
3.7 生命周期方法
aboutToAppear() {
this.setupMapCallback();
notificationManager.isNotificationEnabled().then((enabled: boolean) => {
this.granted = enabled;
}).catch((err: BusinessError) => {
console.error(`isNotificationEnabled failed: ${err.message}`);
});
this.focusRecords.unshift(new FocusRecord(0.95, 0.95, '已生效'));
this.focusRecords.unshift(new FocusRecord(0.6, 0.61, '已生效'));
this.focusRecords.unshift(new FocusRecord(0.3, 0.33, '读回偏差'));
this.timer = setInterval(() => {
this.breath = !this.breath;
}, 1000);
}
aboutToAppear 完成四项初始化:
- 地图回调装配:调用
setupMapCallback()注册 MapComponent 初始化回调。 - 通知授权查询:
notificationManager.isNotificationEnabled()异步查询当前授权状态,结果赋值给granted。这是授权四态流的第一步——“查询”。 - 种子对焦记录:预注入 3 条记录展示三种典型状态。
- 呼吸定时器:每秒翻转
breath驱动呼吸动画。
3.8 Map Kit 方法群
地图初始化回调
setupMapCallback() {
this.mapCallback = async (err: BusinessError, mapController: map.MapComponentController) => {
if (err) {
console.error(`Map init failed, code: ${err.code}, message: ${err.message}`);
return;
}
this.mapController = mapController;
this.mapEventManager = mapController.getEventManager();
for (const spot of MARKER_SPOTS) {
const markerOptions: mapCommon.MarkerOptions = {
position: { latitude: spot.lat, longitude: spot.lng },
clickable: true, visible: true, rotation: 0,
zIndex: 0, alpha: 1, anchorU: 0.5, anchorV: 1,
draggable: false, flat: false
};
try {
await this.mapController.addMarker(markerOptions);
} catch (e) {
console.error(`addMarker failed: ${(e as BusinessError).message}`);
}
}
this.mapEventManager.onMarkerLongClick((marker: map.Marker) => {
this.eventLogs.unshift(new EventLog('Marker',
`#${marker.getId()} 观鸟点标记`, marker.getPosition().latitude,
marker.getPosition().longitude, nowTime()));
if (this.eventLogs.length > 12) { this.eventLogs.pop(); }
});
this.mapEventManager.onPoiLongClick((poi: mapCommon.Poi) => {
this.eventLogs.unshift(new EventLog('POI', poi.name,
poi.position.latitude, poi.position.longitude, nowTime()));
if (this.eventLogs.length > 12) { this.eventLogs.pop(); }
});
};
}
setupMapCallback 构建异步回调函数,在 MapComponent 初始化完成后被调用。回调内完成三步操作:获取 controller 和 eventManager → 批量添加 6 个观鸟点 Marker → 注册 Marker 和 POI 双长按监听。
Marker 长按监听的回调参数类型是 map.Marker,通过 getId() 和 getPosition() 获取标注 ID 和坐标;POI 长按监听的回调参数类型是 mapCommon.Poi,仅携带 id、name 和 position。两个监听都将事件封装为 EventLog 并 unshift 到日志列表,最多保留 12 条。
关键字搜索
async runSearch() {
this.searchState = '搜索中…';
const params: site.SearchByTextParams = {
query: this.queryInput,
location: CITY_CENTER,
radius: 5000,
language: 'zh'
};
try {
const result: site.SearchByTextResult = await site.searchByText(params);
const sites: site.Site[] = result.sites ?? [];
if (sites.length === 0) {
this.searchState = '无结果,已保留当前推荐';
return;
}
this.searchRecords = sites.map((s: site.Site) => new SearchRecord(
s.name ?? '未命名观鸟点', s.formatAddress ?? '暂无地址',
s.distance ?? 0, s.reliability ?? 0));
this.searchState = `返回 ${sites.length} 个观鸟点`;
} catch (e) {
const err = e as BusinessError;
this.searchState = `搜索失败(${err.code}),保留当前推荐`;
}
}
runSearch 以昆明为中心、5km 半径搜索观鸟点。搜索结果中的 reliability 字段通过 ?? 0 空值合并提供兜底,避免 undefined 进入 UI 层。搜索失败时保留 Mock 数据并提示错误码——保证 AGC 未配置或网络不可用时页面仍有数据。
3.9 Camera Kit 方法群
Camera Kit 方法群包括权限申请、影随人动模式启动、AUTO_FRAMING 能力链、手动对焦模式切换、会话释放、能力查询和设置读回校验。这些方法与前述应用高度对称,核心差异在于对焦预设的语义映射——本应用将三档预设映射到观鸟景别(林缘灌丛近景/树冠层中景/湿地远景),而非垂钓场景的拟饵微距/竿稍/钓位。
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() → includes(AUTO_FRAMING) → enableControlCenter(true)。在观鸟场景中,系统接管画面构图后,观鸟者在林间追踪鸟群移动时画面自动跟随,确保观鸟者始终居中。
手动对焦三接口
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})`));
}
}
"设置→读回→差值校验"闭环:setFocusDistance(value) 设置对焦距离,getFocusDistance() 读回实际值,Math.abs(readBack - set) < 0.01 判断是否在容差范围内。每次操作生成 FocusRecord 并置顶到时间线,最多保留 20 条。
3.10 Notification Kit 方法群
通知授权请求
requestAuth() {
const hostCtx = this.getUIContext().getHostContext() as common.UIAbilityContext;
if (!hostCtx) { return; }
notificationManager.requestEnableNotification(hostCtx).then(() => {
this.granted = true;
}).catch((err: BusinessError) => {
console.error(`requestEnableNotification failed: ${err.code}`);
notificationManager.openNotificationSettings(hostCtx).then(() => {
}).catch((e: BusinessError) => {
console.error(`openNotificationSettings failed: ${e.message}`);
this.granted = false;
});
});
}
requestAuth 实现了通知授权四态流的核心逻辑:
第一步:获取上下文。通过 getUIContext().getHostContext() 获取 UIAbilityContext,并转型为 common.UIAbilityContext 类型。HarmonyOS 中 getContext(this) 已废弃,必须使用 getUIContext().getHostContext() 获取上下文,且需要判空处理。
第二步:请求授权。notificationManager.requestEnableNotification(hostCtx) 首次调用时弹系统授权对话框。如果用户同意,Promise resolve,granted 置为 true。如果用户曾拒绝(返回错误码 1600004),Promise reject,进入 catch 块。
第三步:设置页引导。在 catch 块中调用 notificationManager.openNotificationSettings(hostCtx) 拉起系统通知设置页,引导用户手动开启通知权限。这是一个"二次授权"策略——当首次申请被拒绝后,不直接放弃,而是引导用户到设置页重新开启。
通知发布
publishNotice(title: string, text: string) {
const hostCtx = this.getUIContext().getHostContext() as common.UIAbilityContext;
if (!hostCtx) { return; }
const request: notificationManager.NotificationRequest = {
id: this.notifyId++,
notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: title,
text: text,
additionalText: '观羽 · 城市观鸟导览'
}
}
};
notificationManager.publish(request).then(() => {
this.noticeLogs.unshift(new NoticeLog(title, text, nowTime()));
if (this.noticeLogs.length > 8) { this.noticeLogs.pop(); }
}).catch((err: BusinessError) => {
this.noticeLogs.unshift(new NoticeLog(`发布失败(${err.code})`, text, nowTime()));
if (this.noticeLogs.length > 8) { this.noticeLogs.pop(); }
});
}
publishNotice 构建 NotificationRequest 对象并发布 basicText 通知:
ID 自增策略:id: this.notifyId++ 使每条通知的 ID 递增,避免覆盖上一条——Notification Kit 的 publish() 方法使用相同的 id 会覆盖之前的通知,自增 ID 是确保每条通知独立展示的关键。
槽类型:notificationSlotType: SlotType.SOCIAL_COMMUNICATION 设置通知槽类型为社交通信,这个槽类型的通知在系统中有较高的优先级和可见性。
内容类型:NOTIFICATION_CONTENT_BASIC_TEXT 基础文本类型,包含 title(标题)、text(正文)和 additionalText(附加文本,显示应用名"观羽 · 城市观鸟导览")三个字段。
发布结果处理:成功时将通知信息记入 noticeLogs 历史流(最多 8 条),失败时记录失败码——常见 1600004(未授权)会引导用户去授权卡开启。
提醒发布
publishRemindNotice() {
let next = '今日观鸟';
for (const item of this.remindList) {
if (item.on) {
next = item.time + ' ' + item.place + ' · ' + item.bird;
break;
}
}
this.publishNotice('观羽 · 晨昏观鸟提醒',
`「${next}」时段即将开始,带上双筒镜出发,观察时保持安静勿惊扰。`);
}
publishRemindNotice 从晨昏提醒列表中找到第一条开启的提醒,拼装通知文案后发布。通知正文包含时段、地点和目标鸟种,并附上观鸟礼仪提醒"保持安静勿惊扰"。
3.11 Builder 群:页面骨架
头部
头部包含应用名"观羽 · 城市观鸟导览" + Tab 联动副标题(7 种文案根据 currentTab 切换)+ 三特性状态胶囊(Marker 长按/POI 长按/相机会话/通知授权)+ 呼吸圆点。
图鉴 Tab(Tab0)
图鉴 Tab 是首页,包含三块内容:
- 双列鸟种卡:
Flex({ wrap: FlexWrap.Wrap })实现两列布局,每个卡片展示鸟种名、科属、稀有度标签和最佳观察季。稀有度通过rarityColor()着色(罕见红/稀有金/常见绿),点击卡片可预填鸟种名到新增观察记录弹窗。 - 今日打卡横滑卡:
Scroll横向滚动展示 6 个观鸟点的打卡状态,已打卡的卡片高亮。 - 最近观察记录行:
ForEach遍历observeRows,每行展示日期、鸟种、地点、数量和行为备注,可删除。
地图 Tab(Tab1)
独占高度,包含双 Toggle 开关行 + MapComponent 本体 + 长按事件日志流。
搜索 Tab(Tab2)
搜索框 + searchByText 状态行 + reliability 分档说明卡 + 搜索结果列表(名称 + reliability 标签 + 地址 + 距离 + 分数进度条)。
相机 Tab(Tab3)
独占高度,授权卡 + 模式切换行 + XComponent 预览 + 影随人动能力链状态卡 + 效果枚举表。
对焦 Tab(Tab4)
独占高度,能力查询卡 + 三档景别预设 + 焦距滑杆 + 设置/读回按钮行 + FocusRecord 时间线。
提醒 Tab(Tab5)——Notification Kit 核心
提醒 Tab 是本应用的核心差异 Tab,承载 Notification Kit 的全部功能:
┌─────────────────────────────────────────┐
│ 通知授权四态卡 │
│ ┌─────────┐ ┌──────────┐ │
│ │ 授权状态 │ │ 请求授权 │ │
│ │ granted │ │ 按钮 │ │
│ └─────────┘ └──────────┘ │
├─────────────────────────────────────────┤
│ 晨昏观鸟时间轴 │
│ ┌─────────────────────────────────┐ │
│ │ 06:30 红嘴鸥晨飞 翠湖公园 [ON] │ │
│ │ 07:40 晨鸣林鸟 昆明植物园 [ON] │ │
│ │ 16:50 鸻鹬类归巢 滇池海埂 [OFF] │ │
│ │ 18:10 猛禽盘旋 西山森林公园 [ON] │ │
│ │ 19:00 夜鹭出动 大观楼南园 [ON] │ │
│ │ 06:00 晨雾留鸣 黑龙潭公园 [OFF] │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ [发布晨昏观鸟提醒] 按钮 │
├─────────────────────────────────────────┤
│ 通知发布历史 │
│ ┌─────────────────────────────────┐ │
│ │ 观羽 · 观鸟提醒 06:31:02 │ │
│ │ 红嘴鸥已抵达翠湖公园... │ │
│ ├─────────────────────────────────┤ │
│ │ 观羽 · 新种速报 11:20:47 │ │
│ │ 蓝喉蜂虎记录到1只... │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
我的 Tab(Tab6)
观鸟人渐变大卡(黛青渐变背景,含持证等级/观察时长/年度新种)+ 装备清单行 + 月度观察柱状图(传统 Column+ForEach,breath 联动奇偶柱交替波动)。
弹窗系统
三个弹窗通过统一遮罩叠加:
- panelAdd:添加观察记录(鸟种名+地点+数量+行为备注),可从图鉴卡带入鸟种名预填。
- panelEdit:修改提醒时段(单输入框,回填当前时段)。
- panelDel:删除观察记录确认(红色警示)。
弹窗系统采用统一 closeAllModals() 方法复位所有弹窗状态和表单缓存,避免状态残留。
四、三特性技术对比表
| 维度 | Map Kit 特性 | Camera Kit 特性一 | Camera Kit 特性二 | Notification Kit 特性 |
|---|---|---|---|---|
| 能力名称 | searchByText + 双长按监听 | AUTO_FRAMING 影随人动 | 手动对焦三接口 | basicText 通知发布 + 授权四态流 |
| 宿主模块 | site + mapEventManager | VideoSession ControlCenter | PhotoSession ManualFocus | notificationManager |
| 核心API | searchByText / onMarkerLongClick / onPoiLongClick | isControlCenterSupported / getSupportedEffectTypes / enableControlCenter | isFocusDistanceSupported / setFocusDistance / getFocusDistance | isNotificationEnabled / requestEnableNotification / openNotificationSettings / publish |
| 调用模式 | 异步(Promise) | 同步查询 + 同步启用 | 同步查询 + 同步设置 + 同步读回 | 异步(Promise) |
| 返回数据 | Site[] + reliability 分数 | 布尔值 + 枚举数组 | 布尔值 + 数字 | 布尔值(授权)+ void(发布) |
| UI呈现 | 搜索结果列表 + 分数条 + 事件日志流 | 能力链状态卡 + 枚举表 | 预设档位 + 滑杆 + 校验时间线 | 授权状态卡 + 时间轴 + 发布历史流 |
| 容错策略 | 空结果保留Mock / 异常保留Mock | 三步降级 | try-catch + 读回差值校验 | 1600004错误码 → openNotificationSettings 二次引导 |
| 状态联动 | markerListenOn / poiListenOn / eventLogs | framingState / framingSupported | focusSupported / focusDistance / focusRecords | granted / notifyId / noticeLogs |
| 观鸟场景 | 搜索观鸟点 + 标记交互 | 林间追踪鸟群时画面自动跟拍 | 林缘灌丛/树冠层/湿地远景对焦 | 晨昏时段观鸟提醒推送 |
| 生命周期管理 | MapComponent 初始化回调中注册 | releaseSession() 统一释放 | releaseSession() 统一释放 | aboutToAppear 查询授权状态 |
| ID管理 | 无 | 无 | 无 | notifyId++ 自增避免覆盖 |
| 授权依赖 | 无需授权 | CAMERA 权限 (user_grant) | CAMERA 权限 (user_grant) | 通知授权 (requestEnableNotification) |
五、深度总结
5.1 架构设计亮点
本应用在单页面文件中实现了 Map Kit、Camera Kit 和 Notification Kit 三大套件的深度叠加,7 Tab 架构比常规 6 Tab 多出一个"提醒" Tab 来承载通知功能:
Map Kit 侧,searchByText 以昆明为中心 5km 半径搜索观鸟点,reliability 分数分三档展示(高/中/低相关)。Marker/POI 双长按监听让地图从静态展示变为可交互的操作面,长按事件日志流实时记录用户与地图的交互历史。
Camera Kit 侧,AUTO_FRAMING 影随人动在观鸟者林间追踪鸟群时自动跟随构图。手动对焦三档预设精确映射到观鸟三种景别——林缘灌丛近景观察地面觅食鸟、树冠层中景观察枝头停歇鸟、湿地远景观察水面和高空猛禽。设置→读回→差值校验的闭环确保对焦操作真实生效。
Notification Kit 侧,通知授权四态流是核心设计——查询(isNotificationEnabled)→请求(requestEnableNotification)→拒绝处理(1600004 错误码)→设置页引导(openNotificationSettings),形成完整的授权管理闭环。通知发布采用 notifyId++ 自增策略,确保每条通知独立展示不覆盖。basicText 通知包含标题、正文和附加文本三个字段,在晨昏观鸟时段推送提醒,引导观鸟者按时出发。
5.2 浅色主题的设计差异
与前述深色主题应用不同,本应用采用"宣纸米白 + 黛青 + 榕绿"的浅色主题。背景色 #F5F3EB 温暖的米白底色模拟宣纸质感,主题色 #2E7D6B 黛青取自传统中国画颜料,辅助色 #5E8C61 榕绿和 #C9A227 鸦金分别对应鸟种稀有度三档。浅色主题的选择与观鸟活动的自然属性高度契合——观鸟者在户外自然光环境下使用应用,浅色背景减少与环境光的对比度差异,降低视觉疲劳。
5.3 通知授权四态流的工程价值
Notification Kit 的授权四态流是本应用最具工程价值的设计:
- 查询态:
aboutToAppear中调用isNotificationEnabled()查询当前授权状态,初始化granted变量,让 UI 立即反映真实状态。 - 请求态:用户点击"请求授权"按钮调用
requestEnableNotification(context),首次调用弹系统授权对话框。 - 拒绝态:用户曾拒绝时返回 1600004 错误码,
granted保持 false,UI 显示"未授权"状态和"请求授权"按钮。 - 引导态:拒绝后不放弃,调用
openNotificationSettings(context)拉起系统通知设置页,引导用户手动开启——这是"二次授权"策略,很多用户在首次拒绝后会改变主意。
这种四态流设计确保了无论用户在授权流程中的哪个阶段,应用都能提供恰当的引导和反馈,不会出现"卡死"状态。
5.4 状态管理的工程实践
notifyId自增策略:每次发布通知notifyId++,避免相同 ID 覆盖上一条通知。granted状态驱动:授权状态变化驱动 UI 更新——授权后显示"已授权"和"发布"按钮,未授权时显示"请求授权"按钮。@Observed精细追踪:提醒列表项的on属性(开关状态)和time属性(时段)修改直接触发列表行重渲染。closeAllModals()统一复位:弹窗关闭时统一复位所有弹窗状态和表单缓存,避免状态残留影响下次打开。
5.5 产品与技术的深度融合
本应用将每个 HarmonyOS 特性精确映射到观鸟场景需求:searchByText 搜索观鸟点 + reliability 分数评估信息可靠性、Marker/POI 长按标记观鸟点位置、AUTO_FRAMING 林间追踪鸟群时自动跟拍、手动对焦三档对应林缘灌丛/树冠层/湿地远景三种观鸟景别、Notification Kit 晨昏时段推送观鸟提醒。这种"技术能力→业务场景"的精确映射使应用既有技术深度又有产品温度,是 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)