鸿蒙开发5.0案例分析:基于原生能力的各类通知管理

📝往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

🚩 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

🚩 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

🚩 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

🚩 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

🚩 记录一场鸿蒙开发岗位面试经历~

📃 持续更新中……


场景描述

  1. Notification Kit 提供通知发布功能,可以将应用产生的通知在客户端推送给用户。除了携带基本的文本图片信息外,还支持文件上传下载进度场景下的进度条通知,以及点击通知栏可以拉起目标应用的意图类型通知,Notification仅支持进程存在时发送通知。
  2. Push Kit 推送服务,是华为提供的消息推送平台,实现应用的实时消息推送,即使应用进程不在也能实时推送消息。
  3. Live View Kit 实况窗服务用于帮助用户聚焦正在进行的任务,方便快速查看和即时处理的通知形态多用于网约车、外卖等实时通知场景。
  4. AVSession Kit 提供音频视频播控服务,当创建AVSession对象,设置媒体信息并注册基础的播控回调后,播放时锁屏界面和状态栏会自动出现播放控制器。

方案描述

场景一:本地通知 Notification

1. 基础类型通知

除了基础类型通知外还包含长文本类型和多行文本类型,长文本和多行文本类型通知支持展开,通知对比如下:

方案:

发布携带图片的通知,支持横幅效果(横幅通知需在应用通知管理-提醒方式开启),通知3s(时间可自定义)后自动消除。

构造通知对象NotificationRequest:

a.选择文本类型通知,在normal下主要配置标题title和文本text即可。

b.通知携带图片需通过resourceManager.getMediaContent获取到media文件夹下图片资源后转为PixelMap,然后传递至largeIcon。

c.横幅通知,三方应用仅支持通过通知渠道SlotType设置,只有社交通信和服务提醒支持通知横幅效果且需在设置中开启横幅通知样式。

d.通知的定时发送与消除:Notification暂无定时发送能力,需依赖 代理提醒 实现,定时消除可通过autoDeletedTime设置通知消除时间。

e.点击通知拉起应用详见下文第三部分意图通知。

核心代码:

将media下资源图片转化为PixelMap对象。

let resourceManager = getContext().resourceManager;
let imageArray = await resourceManager.getMediaContent($r('app.media.icon').id);
let imageResource = image.createImageSource(imageArray.buffer);
let imagePixelMap = await imageResource.createPixelMap();

构造NotificationRequest对象。

let notificationRequest: notificationManager.NotificationRequest = {
  id: 1,
  largeIcon: imagePixelMap,
  content: {
    notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, // 长文本类型通知
    normal: {
      title: '图片类型通知',
      text: '3s后通知自动消除',
    }
  },
  badgeNumber: 1, //通知角标
  autoDeletedTime: Date.now() + 3000, //3s后自动消除
  notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION
};

发布通知。

notificationManager.publish(notificationRequest);

2. 进度条通知

方案:

文件下载时实现实时进度通知,下载完成后点击通知预览下载文件。

a.构造NotificationRequest对象和进度条模板downloadTemplate,模板中包含三个字段标题title、下载文件名fileName、下载进度progress。

b.downloadFile下载pdf文件,使用downloadTask.on监听任务下载进度,根据“当前完成文件下载大小/文件总大小”得到下载进度,传入通知进度条模板中,实时更新应用下载进度。

c.应用下载完成后点击通知拉起应用预览下载文件,该能力实现详见下文第三部分意图通知。

核心代码:

构造NotificationRequest对象。

let notificationRequest: notificationManager.NotificationRequest = {
  id: 5,
  largeIcon: imagePixelMap,
  wantAgent: wantAgent,
  badgeNumber: 1,
  notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION, //社交类型通知
  content: {
    notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
    normal: {
      title: 'test_title',
      text: 'test_text',
    }
  },
  //构造进度条模板,name字段当前需要固定配置为downloadTemplate
  template: {
    name: 'downloadTemplate',
    data: { title: 'PDF文件下载', fileName: 'test.pdf下载完成:' + progress + '%', progressValue: progress }
  },
}

下载文件并开启下载任务进度订阅。

let context = getContext() as common.UIAbilityContext;
let filesDir = context.filesDir;
request.downloadFile(context, {
  url: 'https://xxxx/xxxx.pdf',
  filePath: filesDir + '/test.pdf'
}).then((downloadTask: request.DownloadTask) => {
  let progressCallback = (receivedSize: number, totalSize: number) => {
    publishNotification(Math.round(100 * receivedSize / totalSize))
  };
  downloadTask.on('progress', progressCallback)
})

3. 意图通知

方案:

以创建拉起应用的通知为例:

a.将动作类型actionType设为START_ABILITY拉起一个有页面的ability;如是触发公共事件通知,则设为SEND_COMMON_EVENT,并根据action设置事件名。由于WantAgentInfo中的actionType不支持同时配置两个,所以只能选择发布拉起应用的通知或者携带公共事件的通知,不能实现点击通知后既拉起应用还触发公共事件。

b.构造NotificationRequest对象,如果希望点击通知后通知不消失,则配置tapDismissed为false。

核心代码:

拉起应用场景。

let wantAgent: _WantAgent;
let wantAgentInfo: WantAgent.WantAgentInfo = {
  wants: [
    {
      bundleName: 'com.example.mynotificationservice',
      abilityName: 'EntryAbility',
    } as Want
  ],
  actionType: WantAgent.OperationType.START_ABILITIES,
  requestCode: 0,
  wantAgentFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
WantAgent.getWantAgent(wantAgentInfo).then((data) => {
  wantAgent = data;
})

发布公共事件场景。

let wantAgentInfo: WantAgent.WantAgentInfo = {
  wants: [
    {
      action:'eventName'  //事件名
    } as Want
  ],
  actionType: WantAgent.OperationType.SEND_COMMON_EVENT,
  requestCode: 0,
  wantAgentFlags: [WantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};

构建通知对象NotificationRequest。

let notificationRequest: notificationManager.NotificationRequest = {
  content: {
    notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
    normal: {
      title: '含行为意图的通知',
      text: '点击后拉起应用',
    },
  },
  id: 6,
  notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION, //社交类型通知
  label: 'TEST',
  wantAgent: wantAgent,
  tapDismissed: false, //通知是否自动清除
};

发布通知。

notificationManager.publish(notificationRequest)

场景二:云端推送服务****push Kit

开通推送服务

1)AGC上配置签名证书指纹,参考 手动签名 。

2)登录 AppGallery Connect 网站,开通推送服务并配置Client ID,详见文档 开通推送服务与配置Client ID 。

发送消息通知:

1)获取Push Token,Push Token标识了每台设备上每个应用**,调用getToken()接口**向Push Kit服务端请求Token,获取到Token后,使用Push Token来推送消息。

2)调用notificationManager.requestEnableNotification开启应用通知权限。

3)应用服务端调用Push Kit服务端的REST API推送通知消息

发送测试:

1)左侧导航栏选择“推送服务“,推送通知(V3 Beta)下点击”添加推送通知。

2)配置getToken获取到的Token,然后填写消息内容,完成后点击“提交”即可在设备收到推送消息。

场景三、实况窗服务

开通实况窗参考: 开通实况窗权益 ,使用本地实况窗也需要应用的bundleName在 AppGallery Connect 上开通实况窗服务,具体实现demo详见 实况窗服务-即时配送 。本场景以打车出行为例实现。

方案:

以打车出行场景为例创建实况窗模板,根据event选择应用场景,包含出行打车、外卖配送、航班等,然后配置LiveViewData详细信息,主要设置卡片形态(即状态栏和锁屏页面展示的)参数 PrimaryData;除了基本的标题文本内容外,出行打车场景通常选择进度模板LAYOUT_TYPE_PROGRESS展示司机当前距离乘客位置。

1)构建LiveView对象,卡片模板分为三个部分分别为固定区、辅助区、扩展区,锁屏状态下如果展示的是胶囊通知,则只会显示固定区和辅助区内容。打车出行场景下,可以定义进度类型扩展区ProgressLayout,通过progress实现车辆与乘客距离的实时展示,实况窗卡片模板详见 通用卡片模板 。

点击卡片拉起应用需通过clickAction点击事件实现,WantAgent参数配置可以参考场景一第三条意图通知。

2)startLiveView创建实况窗,乘客打车发布订单后,预估接驾距离、时间,使用startLiveView发布实况窗。

3)updateLiveView更新实况窗,当司机接单后,实时更新汽车颜色、型号、车牌,与乘客距离等信息,锁屏状态下可以通过liveViewLockScreenAbilityName实时展示锁屏沉浸实况窗页面(需用户长按实况窗才会进入沉浸式场景),由于实况窗开发涉及权限申请,本文仅以liveViewLockScreenPicture图片类型锁屏沉浸实况窗为例展示本地实现效果,加载锁屏沉浸实况窗页面依赖LiveViewLockScreenExtensionAbility,可以在onSessionCreate生命周期中加载应用指定页面。

"extensionAbilities": [
  {
    "name": "LiveViewLockScreenExtAbility",
    "srcEntry": "./ets/liveviewlockscreenextability/LiveViewLockScreenExtAbility.ets",
    "description": "$string:LiveViewLockScreenExtAbility_desc",
    "icon": "$media:layered_image",
    "label": "$string:LiveViewLockScreenExtAbility_label",
    "type": "liveViewLockScreen",
  }
]

4)stopLiveView结束实况窗,司机到达后更新最后实况窗,提醒乘客到指定地点上车。

核心代码:

构建实况窗对象。

private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
  let resourceManager = getContext().resourceManager;
  let imageArray = await resourceManager.getMediaContent($r('app.media.icon').id);
  let imageResource = image.createImageSource(imageArray.buffer);
  let imagePixelMap = await imageResource.createPixelMap();
 
  return {
  id: 0,
  event: 'PICK_UP', //出行打车
  sequence: 1,
  liveViewData: {
    primary: {
      title: await LiveViewController.resourceManager
        .getStringValue($r("app.string.Delivery_default_primary_title")),
      content: [
        {
          text: await LiveViewController.resourceManager.getStringValue($r("app.string.Delivery_default_primary_content1")),
          textColor: LiveViewController.contentColor
        },
        {
          text: await LiveViewController.resourceManager.getStringValue($r("app.string.Delivery_default_primary_content2")),
          textColor: LiveViewController.contentColor
        }
      ],
      // liveViewLockScreenPicture: 'delivery_4x.png',
      // liveViewLockScreenAbilityName: 'LiveViewLockScreenExtAbility',
      // liveViewLockScreenAbilityParameters: dataInfo,
      keepTime: 15,
      clickAction: await ContextUtil.buildWantAgent(),
      extensionData: {
        type: liveViewManager.ExtensionType.EXTENSION_TYPE_ICON,
        pic: 'icon_merchant.png',
        clickAction: await ContextUtil.buildWantAgent()
      },
      layoutData: {
        layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PICKUP,
        title: await LiveViewController.resourceManager.getStringValue($r("app.string.Delivery_default_layout_title")),
        content: await LiveViewController.resourceManager.getStringValue($r("app.string.Delivery_default_layout_content")),
        underlineColor: LiveViewController.underLineColor,
        descPic: 'coffee.png'
      }
    },
    capsule: {
      type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
      status: 1,
      icon: 'capsule_store.png',
      backgroundColor: LiveViewController.capsuleColor,
      title: await LiveViewController.resourceManager.getStringValue($r("app.string.Delivery_default_capsule_title")),
      content: '...'
    }
  }
}
}

发布实况窗。

public async startLiveView(): Promise<void> {
  this.defaultView = await LiveViewController.buildDefaultView();
 
  try {
  const result = await liveViewManager.startLiveView(this.defaultView);
} catch (e) {
  const err: BusinessError = e as BusinessError;
}
}

更新实况窗。

public async updateLiveView(): Promise<boolean> {
  try {
  // live view is disabled or default view is not init
  if (!LiveViewController.isLiveViewEnabled() || !this.defaultView) {
  return false;
}
 
switch (this.defaultView.sequence) {
  case LiveViewStatus.WAITING_TAKE_ORDER: //司机接单,距离2公里,4分钟
    this.defaultView.liveViewData.primary.title = Constants.TAXI_AWAIT_TITLE;
    this.defaultView.liveViewData.primary.content = [
      {
        text: Constants.TAXI_AWAIT_TEXT1,
        textColor: LiveViewController.contentColor
      },
    ];
    this.defaultView.liveViewData.primary.liveViewLockScreenPicture = 'icon_car1.jpg';
    this.defaultView.liveViewData.primary.layoutData = {
      layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
      progress: 0,
      lineType: liveViewManager.LineType.LINE_TYPE_DOTTED_LINE,
      indicatorType: liveViewManager.IndicatorType.INDICATOR_TYPE_UP,
      indicatorIcon: 'icon_taxi.png',
      nodeIcons: [
        'icon_order.png',
        'icon_store_white.png',
        'icon_finish.png'
      ]
    };
    this.defaultView.liveViewData.capsule = {
      type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
      status: 1,
      icon: 'capsule_store.png',
      backgroundColor: LiveViewController.capsuleColor,
      title: await LiveViewController.resourceManager.getStringValue($r("app.string.Delivery_merchant_capsule_title"))
    }
    break;
 
  case LiveViewStatus.WAITING_COUNTDOWN4: //司机接单,距离1.5公里,3分钟
    break;
 
  case LiveViewStatus.WAITING_COUNTDOWN3: //司机接单,距离1公里,2分钟
    break;
 
  case LiveViewStatus.WAITING_COUNTDOWN2: //司机接单,距离0.5公里,1分钟
    break;
 
  case LiveViewStatus.WAITING_COUNTDOWN1: //司机接单,已到达
    break;
 
  case LiveViewStatus.WAITING_ARRIVED:
    await this.stopLiveView();
    return false;
  default:
    await this.stopLiveView();
    return false;
}
this.defaultView.sequence += 1;
const result = await liveViewManager.updateLiveView(this.defaultView);
return true;
} catch (e) {
  const err: BusinessError = e as BusinessError;
  Logger.error('Request updateLiveView error: %{public}s', err.message);
  return false;
}
}

场景四、锁屏界面播放器管理

方案:

调用avSession.createAVSession创建audio或video类型的 AVSession ,然后setAVMetadata设置相关媒体信息,setAVPlaybackState设置各种播放状态,当应用进入播放状态时,系统会自动发送通知,并在状态栏和锁屏界面展示,底层基于实况窗实现。

<think>好的,用户现在的问题是关于鸿蒙Stage模型中,图标和标签没有变化但版本可以修改的原因,以及string.json在配置中的作用。之前已经给过他们一个详细的回答,现在需要结合他们提供的站内引用和之前的回答,进一步分析和解答。 首先,我需要回顾之前的回答,确认已经覆盖了可能的原因,比如资源路径错误、入口技能未声明、多级配置覆盖、缓存问题、资源命名冲突和设备类型适配。用户现在提到string.json的作用,这可能涉及到标签的字符串定义是否正确引用,以及资源命名是否冲突的问题。 接下来,用户提供了四个引用内容,我需要仔细看一下这些引用的内容,看看有没有新的信息需要整合。比如引用1提到DevEco Studio 5.0.3.800版本之后不再强制校验module.json5中的icon和label,所以module和app的配置只需要一个,如果同时存在,module的会覆盖app的。这可能是一个关键点,用户之前的问题中可能没有完全考虑到这一点,导致图标和标签未更新,因为可能存在配置覆盖的问题。 引用2是关于权限配置的示例,可能和当前问题关联不大,但需要确认是否有权限问题影响资源加载,不过根据之前的回答,可能更多是配置覆盖或资源路径的问题。 引用3是用户放在GitHub上的一个icon组件的封装代码,这可能涉及到自定义图标组件是否正确引用资源,或者是否与Stage模型的配置冲突。例如,用户可能在代码中使用了自定义的SVG图标,但未在app.json5或module.json5中正确配置,导致系统图标未被正确覆盖。 引用4提到北向开发基础,可能涉及Ability的配置,特别是入口技能(skills)的声明是否正确,例如是否包含entity.system.home,如果没有正确声明,可能导致图标不显示。 用户现在的问题还提到wechat配置原因,可能是在问微信相关的配置是否有影响,但目前的信息中没有提到微信的具体情况,可能需要进一步询问,但根据现有引用和问题,应该集中在Stage模型的配置上。 现在需要整合这些信息,结合之前的回答,给出更精确的原因和解决方案。重点可能在于: 1. 配置覆盖问题:module.json5中的icon和label覆盖了app.json5中的配置,而用户可能只修改了app.json5但没有修改module.json5,或者反之,导致图标标签未更新。同时,由于DevEco Studio不再强制校验,可能存在配置冲突未被及时发现。 2. string.json中的标签定义是否正确,是否存在命名冲突或未正确引用的情况。例如,标签使用了$string:entry_label,但在string.json中没有对应的键值,或者名称拼写错误。 3. 缓存问题:修改了配置但未清理缓存,导致旧资源仍然被使用,版本号因为是直接修改数值,所以可能不需要重新编译或清理缓存即可生效。 4. 自定义图标组件(如引用3中的SvgIcon)可能和系统配置的图标冲突,导致系统图标未被正确应用。 解决方案需要包括检查配置覆盖情况,确认string.json中的定义,清理缓存,以及检查自定义组件是否影响系统图标。 另外,用户可能没有正确理解string.json的作用,需要解释它在资源管理中的角色,特别是在定义标签和其他字符串资源时的引用机制。 最后,确保回答结构清晰,分点列出原因和解决方法,引用相关引用内容作为支持,例如引用1中的配置覆盖问题,引用4中的入口技能声明等。</think>在鸿蒙Stage模型中,图标/标签未更新但版本可修改的现象,本质上源于 **资源引用机制与配置覆盖规则的特殊性**。结合您提供的引用内容与代码示例,以下是系统性解析: --- ### 一、版本可修改而图标/标签不生效的核心原因 #### 1. **配置层级覆盖机制** [^1] ```json5 // app.json5(应用级) { "app": { "icon": "$media:app_icon", // 可能被覆盖 "versionName": "1.0.1" // 直接生效 } } // module.json5(组件级) { "module": { "abilities": [{ "icon": "$media:ability_icon" // 优先级更高 }] } } ``` - **关键规则**: `module.json5`中的Ability配置会覆盖`app.json5`的全局配置(如引用1所述) - **现象解释**: 若仅修改`app.json5`的图标/标签,但未同步更新`module.json5`中的Ability配置,则修改不生效,而版本号作为纯文本数值直接生效。 #### 2. **string.json的桥梁作用** ```json // entry/src/main/resources/base/element/string.json { "app_name": "MyApp", // 全局应用名称 "entry_label": "主页入口" // 被module.json5引用的标签 } ``` - **核心功能**: `string.json`通过键值对管理所有文本资源,标签(label)必须通过`$string:key`格式引用此处定义的内容[^2] - **常见错误**: - 未在`string.json`中定义对应键名 - 使用`$string:entry_label`但实际键名为`ability_label` --- ### 二、图标/标签未更新的5大具体原因与解决方案 #### 1. **多级配置覆盖未适配**(引用1场景) - **触发条件**: 同时存在`app.json5`和`module.json5`的图标/标签配置 - **验证方法**: 检查`module.json5`是否包含`icon`或`label`字段 - **修复方案**: ```diff // 方案1:统一使用app.json5配置(删除module配置) - "icon": "$media:ability_icon" // 方案2:显式更新module.json5 + "icon": "$media:new_icon" ``` #### 2. **资源引用路径错误** - **典型表现**: ```json5 // 错误示例:未匹配实际路径 "icon": "$media:icon" // 实际文件名为app_icon.png ``` - **定位工具**: 使用DevEco Studio的**Resource Manager**(快捷键`Ctrl+Shift+R`)验证资源ID映射关系[^3]。 #### 3. **入口技能(Skills)声明缺失**(引用4关联) ```json5 // module.json5必须包含桌面入口声明 "skills": [{ "entities": ["entity.system.home"], // 关键实体 "actions": ["action.system.home"] // 必须声明 }] ``` - **影响**:缺少此配置将导致系统不创建桌面图标。 #### 4. **缓存未清理** - **操作流程**: ```bash # 1. Clean Project(菜单File > Clean Project) # 2. 卸载旧版应用 # 3. 重启IDE与设备 ``` #### 5. **动态资源未刷新**(引用3场景) - **自定义组件冲突**: 若使用类似引用3的`<SvgIcon>`自定义图标组件,需检查是否与系统图标配置冲突: ```xml <!-- 错误案例:同时配置系统图标和自定义组件 --> <ability icon="$media:app_icon"/> <!-- 系统图标 --> <Page> <SvgIcon icon-class="custom_icon"/> <!-- 自定义图标覆盖 --> </Page> ``` - **解决方案**: 统一资源加载方式,或通过`ohos.resourceManager` API强制刷新资源[^2]。 --- ### 三、配置验证流程图 ```mermaid graph TD A[图标/标签未更新] --> B{检查module.json5配置} B -->|存在配置| C[确认是否覆盖app.json5] B -->|无配置| D[检查app.json5资源路径] C --> E[同步修改对应配置文件] D --> F[验证string.json键值匹配] F --> G[清理缓存后重编译] G --> H[使用hdc shell检查资源] ``` --- ### 四、扩展:微信相关配置的特殊性 若涉及微信小程序等混合开发场景,需注意: 1. **独立资源目录**:微信原生图标可能存放在`resources/wechat`目录 2. **动态更新限制**:微信容器内图标通常需通过微信开发者工具更新 3. **冲突规避**:确保鸿蒙配置与微信`app.json`中的`window.navigationBarTitleText`无命名冲突 --- **最后验证步骤**: 1. 执行`hdc shell bm dump -n [包名]`查看实际加载资源 2. 过滤日志`hilog | grep ResourceManager`追踪加载过程 3. 使用DevEco Studio的**Previewer**实时预览效果[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值