Flutter 3.47 鸿蒙三方库适配实战:从 0 到 1 处理 app_badge_plus

应用角标是提升回访率最直接的手段:未读消息数、待办数、红点。但这件事在安卓上是出了名的碎片化——上游 app_badge_plus 为此写了一整套启动器适配层,impl/ 目录下按 Apex / Asus / Huawei / Hihonor / MiUI / OPPO / Samsung / Sony / Vivo 等近 20 个厂商分别处理。

鸿蒙这边反而简单:只有一个系统接口,不需要按厂商分支。本文以 app_badge_plus 1.3.5 为例,记录我如何把这套碎片化的逻辑收敛成一次 setBadgeNumber 调用,并在 HarmonyOS API 26 真机上验证到桌面图标真实显示红色角标。

适配仓库: oh-flutter/app_badge_plus

适配分支: feat/ohos_app_badge_plus_1.3.5

适配 TAG: 1.3.5-ohos-1.0.0-beta.1

一、先看最终效果

在这里插入图片描述

图 1:HarmonyOS API 26 真机启动自检台。顶部绿色显示 isSupported: true(可显示角标),下方是 +1 / -1 / 清零 / 设 5 / 设 120(99+)/ 重新查 isSupported 六个按钮。

在这里插入图片描述

图 2:点击「设 5」后,界面显示「界面记录的角标值:5」「最近一次调用:updateBadge(5) → 成功」,日志区同时打出 updateBadge(5) → 成功。

在这里插入图片描述

图 3:按 Home 键回到桌面,应用图标 app_badge_pl... 右上角显示红色角标「5」,与微信的 99+、QQ 的 99 并排显示。

验证点实测结果证据
Flutter OH 3.47 工程构建通过entry-default-signed.hap
安装、启动(真机)通过图 1
isSupported()返回 true图 1
updateBadge(5)调用成功图 2
桌面图标角标真实显示红色「5」图 3

二、这个库原来是怎么工作的

app_badge_plus 的通道面只有两个方法,但安卓侧的实现量很大:

Dart AppBadgePlus
  -> MethodChannel('app_badge_plus')
       updateBadge({count})   // count == 0 表示清除
       isSupported()          // 当前设备/桌面是否支持角标

Android: 近 20 个启动器各自的私有广播或 ContentProvider
iOS/macOS: UIApplication.applicationIconBadgeNumber / NSApp.dockTile.badgeLabel

碎片化的根源在于:安卓没有统一的角标 API,每家启动器各搞一套。上游的做法是逐个厂商适配——这也是这个库存在的主要价值。

而鸿蒙不需要这一层。 @ohos.notificationManager 提供了统一的 setBadgeNumber,所有设备走同一条路径。

三、实际环境

Flutter 鸿蒙环境搭建不在本文重复展开,直接参考 Flutter OH 环境搭建指南。

组件版本
Flutter OH3.47.4-ohos-1.0.4
Dart3.13.3
DevEco Studio26.0.0 Release
HarmonyOS SDKAPI 26
测试设备华为真机 6UMBB26319007180,HarmonyOS 7.0.0.105(API 26)
目标库app_badge_plus 1.3.5
flutter --version
hdc list targets

四、确认上游版本并同步到 AtomGit

上游包地址是 https://github.com/windows7lake/app_badge_plus,版本 1.3.5,使用 MIT License。适配前我在 AtomGit 目标组织中搜索了同名库,没有找到可以直接使用的 OHOS 实现。

git clone https://atomgit.com/oh-flutter/app_badge_plus.git
cd app_badge_plus
git switch feat/ohos_app_badge_plus_1.3.5
git log --oneline -5

五、创建适配分支并检查工程结构

git switch -c feat/ohos_app_badge_plus_1.3.5

关键目录如下:

app_badge_plus/
├── lib/
│   └── app_badge_plus_method_channel.dart          # 上游通道定义,一行未改
├── android/                                        # 上游的近 20 个启动器适配层,保持原样
├── ohos/
│   ├── index.ets                                   # OHOS 插件导出入口
│   └── src/main/ets/components/plugin/
│       └── AppBadgePlusPlugin.ets                  # 通道实现
├── example/lib/main.dart                           # 真机验证自检台
├── example/ohos/                                   # Flutter OH 宿主工程
├── pubspec.yaml                                    # 声明 ohos 平台
└── README.OpenHarmony_CN.md                        # 鸿蒙适配说明

注意 android/ 目录原封不动保留——鸿蒙适配只新增一个平台实现,不改动其他平台任何一行。

六、适配过程:一个接口取代 20 个分支

6.1 通道与方法名

const METHOD_CHANNEL: string = 'app_badge_plus';
const METHOD_UPDATE_BADGE: string = 'updateBadge';
const METHOD_IS_SUPPORTED: string = 'isSupported';

6.2 设置角标:setBadgeNumber

import { notificationManager } from '@kit.NotificationKit';

case 'updateBadge':
  const count = call.argument('count') as number;
  notificationManager.setBadgeNumber(count ?? 0)
    .then(() => result.success(null))
    .catch((e: BusinessError) => result.error(`${e.code}`, e.message, null));
  return;

count == 0 表示清除,> 99 系统显示为 99+——这两个语义都由系统统一处理,实现里不需要特判。

6.3 isSupported 不是无脑 true

这一点我觉得是这个库适配里最有意思的地方。鸿蒙的角标是通知角标:如果用户在系统里把通知总开关关掉,角标就不会显示。所以 isSupported() 必须如实反映这个前提:

private async isSupported(): Promise<boolean> {
  const enabled = await notificationManager.isNotificationEnabled();
  if (!enabled) {
    return false;      // 通知总开关关了,角标必然不显示
  }
  const setting = await notificationManager.getNotificationSetting();
  return setting.badgeNumberEnabled === true;
}

这里刻意没有返回无条件 true。 理由很直接:如果通知被关掉却告诉调用方「支持角标」,调用方就会以为设置成功了,但用户桌面上什么都不会出现——这是最糟糕的那种「假成功」。

6.4 双次回读:对抗异步落库

系统的角标状态是异步落库的,设置完立刻读可能拿到旧值。所以实现里做了两次回读:

const READBACK_SETTLE_MS: number = 300;

private async updateBadge(count: number): Promise<void> {
  await notificationManager.setBadgeNumber(count);
  // 立刻回读一次
  await notificationManager.getBadgeNumber();
  // 等状态落库,再回读一次拿"最终值"
  await new Promise<void>((resolve) => setTimeout(resolve, READBACK_SETTLE_MS));
  await notificationManager.getBadgeNumber();
}

这不是多余的操作——第一次回读能发现「参数根本没生效」这类立刻失败,第二次回读拿到的是落库后的真实值。两次结果都会写进 hilog,排查时能看出进度。

方法OpenHarmony 实现
updateBadge(count)notificationManager.setBadgeNumber(count) + 两次回读
isSupported()isNotificationEnabled() && getNotificationSetting().badgeNumberEnabled

七、构建配置与签名

同批库共有的两个坑。

7.1 compatibleSdkVersion 必须写 "26.0.0"

00306004 Specification Limit Violation
The project's compatibleSdkVersion: 18 cannot be lower than
the minimum compatible version 24 required by the dependencies: @ohos/flutter_ohos.

正确写法是不带括号的 "26.0.0"。

7.2 签名证书绑定了 bundleName

00303074 Configuration Error
The bundleName in app.json5/hvigorfile.ts does not match the bundleName
in the generated SigningConfigs.

本库示例原本是 me.liolin.app_badge_plus_example,改成证书对应的 com.example.flutter_oh_demo 后通过。

八、通过 AtomGit 引入适配版

dependencies:
  app_badge_plus:
    git:
      url: https://atomgit.com/oh-flutter/app_badge_plus.git
      ref: 1.3.5-ohos-1.0.0-beta.1
flutter pub get

本库不需要任何权限声明——setBadgeNumber 从 API 10 起对普通应用开放,不需要额外权限。

九、构建、安装和真机验证

cd example
flutter pub get
flutter analyze
flutter build hap --debug
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_oh_demo

真机操作顺序与结果:

操作界面显示hilog
启动isSupported: true(可显示角标)isSupported() → true
点「设 5」界面记录的角标值:5
最近一次调用:updateBadge(5) → 成功
updateBadge(5) → 成功
按 Home 回桌面图标右上角红色角标 5—

第三张截图(图 3)是最有说服力的证据:我们的应用图标和微信(99+)、QQ(99)、抖音(10)排在一起,右上角清清楚楚是红色「5」。这证明 setBadgeNumber 真的改到了桌面图标,而不是只在应用内部记了个数字。

十、适配结果和当前边界

两个方法全部接通,Dart 层一行未改,安卓侧近 20 个启动器分支原样保留。

已知边界:

  • 角标依赖通知总开关:鸿蒙的角标是通知角标,用户在系统设置里关掉通知总开关后,isSupported() 会返回 false,此时 updateBadge 仍会记录数字但桌面不显示——这是平台语义,不是实现缺陷;
  • 上限 99+ 由系统处理:传 120 时系统显示 99+,实现里不做截断;
  • 桌面布局影响可见性:如果应用被放进文件夹或者被其他角标应用遮挡,截图时可能看不到,但角标本身已设置成功;
  • isSupported 依赖系统查询:isNotificationEnabled 或 getNotificationSetting 查询失败时,实现选择返回 false(宁可说「不支持」也不说「支持但实际不显示」)。

十一、FAQ

Q1:为什么鸿蒙不需要像安卓那样适配 20 个厂商?

  • 现象: 上游安卓侧有一整套 impl/ 启动器适配层。
  • 解决方法: 鸿蒙 @ohos.notificationManager 提供统一的 setBadgeNumber,所有设备走同一条路径,不需要分支。
  • 验证结果: 一次调用成功,桌面真实显示角标。

Q2:isSupported 为什么可能返回 false?

  • 现象: 设备明明支持角标,但 isSupported() 返回了 false。
  • 解决方法: 检查系统设置里的通知总开关,以及 badgeNumberEnabled 是否开启。鸿蒙角标依附于通知,通知关闭则角标不显示。
  • 验证结果: 本机通知开启,返回 true(图 1)。

Q3:设置完角标为什么要回读两次?

  • 现象: 设置后立刻读可能拿到旧值。
  • 解决方法: 立刻回读一次抓「即时失败」,等 300ms 状态落库后再回读一次拿最终值。
  • 验证结果: 两次回读都记进 hilog,updateBadge(5) 最终值正确。

Q4:传 0 是清除还是显示 0?

  • 现象: 不确定 updateBadge(0) 的行为。
  • 解决方法: 鸿蒙约定 0 表示清除角标,不是显示「0」。
  • 验证结果: 界面上的「清零」按钮走的就是这条路径。

十二、总结

app_badge_plus 1.3.5 已接入鸿蒙:用统一的 notificationManager.setBadgeNumber 取代了安卓的 20 个启动器分支,isSupported() 如实反映「通知开关 + badgeNumberEnabled」两个前提而不是无脑返回 true。真机上桌面图标真实显示红色角标「5」,与微信、QQ 的角标并排。

这个库最值得记的一条经验是:鸿蒙在很多能力上比安卓「统一」得多,把碎片化的兼容代码整段砍掉,反而是最正确的适配。app_badge_plus 上游 20 个启动器分支在鸿蒙侧一行都用不上,实现只需要一个系统接口——这种情况下,少写代码就是好适配。

十三、参考资料和仓库地址

欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐