城市观鸟是一项兼具科学性与休闲性的户外活动,观鸟者需要在最佳时段前往最佳地点,观察并记录鸟类行为。一个完整的观鸟导览应用需要覆盖鸟种图鉴浏览、观鸟点地图导航、关键字搜索、相机跟拍、手动对焦、晨昏提醒和个人档案等多个功能模块。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 组件嵌入地图视图,配合 mapCommonmapsite 模块提供地图初始化、标注管理和 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 围绕 CameraManagerCameraInputPreviewOutput 和 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 的通知发布与授权管理全部通过这个命名空间访问。第五行导入 BusinessErrorAsyncCallback 通用类型。

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 完成四项初始化:

  1. 地图回调装配:调用 setupMapCallback() 注册 MapComponent 初始化回调。
  2. 通知授权查询notificationManager.isNotificationEnabled() 异步查询当前授权状态,结果赋值给 granted。这是授权四态流的第一步——“查询”。
  3. 种子对焦记录:预注入 3 条记录展示三种典型状态。
  4. 呼吸定时器:每秒翻转 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。两个监听都将事件封装为 EventLogunshift 到日志列表,最多保留 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 是首页,包含三块内容:

  1. 双列鸟种卡Flex({ wrap: FlexWrap.Wrap }) 实现两列布局,每个卡片展示鸟种名、科属、稀有度标签和最佳观察季。稀有度通过 rarityColor() 着色(罕见红/稀有金/常见绿),点击卡片可预填鸟种名到新增观察记录弹窗。
  2. 今日打卡横滑卡Scroll 横向滚动展示 6 个观鸟点的打卡状态,已打卡的卡片高亮。
  3. 最近观察记录行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 + mapEventManagerVideoSession ControlCenterPhotoSession ManualFocusnotificationManager
核心APIsearchByText / onMarkerLongClick / onPoiLongClickisControlCenterSupported / getSupportedEffectTypes / enableControlCenterisFocusDistanceSupported / setFocusDistance / getFocusDistanceisNotificationEnabled / requestEnableNotification / openNotificationSettings / publish
调用模式异步(Promise)同步查询 + 同步启用同步查询 + 同步设置 + 同步读回异步(Promise)
返回数据Site[] + reliability 分数布尔值 + 枚举数组布尔值 + 数字布尔值(授权)+ void(发布)
UI呈现搜索结果列表 + 分数条 + 事件日志流能力链状态卡 + 枚举表预设档位 + 滑杆 + 校验时间线授权状态卡 + 时间轴 + 发布历史流
容错策略空结果保留Mock / 异常保留Mock三步降级try-catch + 读回差值校验1600004错误码 → openNotificationSettings 二次引导
状态联动markerListenOn / poiListenOn / eventLogsframingState / framingSupportedfocusSupported / focusDistance / focusRecordsgranted / 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 的授权四态流是本应用最具工程价值的设计:

  1. 查询态aboutToAppear 中调用 isNotificationEnabled() 查询当前授权状态,初始化 granted 变量,让 UI 立即反映真实状态。
  2. 请求态:用户点击"请求授权"按钮调用 requestEnableNotification(context),首次调用弹系统授权对话框。
  3. 拒绝态:用户曾拒绝时返回 1600004 错误码,granted 保持 false,UI 显示"未授权"状态和"请求授权"按钮。
  4. 引导态:拒绝后不放弃,调用 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 将自动执行以下操作:

  1. 生成项目骨架(Stage 模型目录结构)
  2. 执行 ohpm install 安装依赖
  3. 运行 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.1Release✅ 已安装

界面顶部提示:“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 246.1.1.100Release✅ 已安装
API Version 236.1.0.28Beta1未安装
API Version 226.0.2.112Release未安装

安装路径示例:D:\DevTools\ArkUI-X\sdk

说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

在这里插入图片描述


三、小结

步骤操作关键点
创建项目欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成使用 Stage 模型 + ArkTS 语言
查看 SDK设置 → HarmonyOS SDKSDK 已内置,无需手动安装
跨平台扩展设置 → ArkUI-X根据需要安装对应 API 版本

至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。


本文基于 DevEco Studio 6.1.1 Release 版本编写,不同版本界面可能存在细微差异。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐