Flutter 鸿蒙适配实战:用 ringer_mode 三方库在 OpenHarmony 上实现设备铃声模式查询与监听

Flutter 社区地址: https://atomgit.com/CPF-Flutter/flutter_flutter
github三方库地址:https://github.com/alxmoroz/flutter_ringer_mode
pub地址:https://pub.dev/packages/ringer_mode
适配后地址:https://atomgit.com/oh-flutter/ringer_mode

库版本:ringer_mode 1.0.2(OpenHarmony 适配版)|验证环境:Flutter 鸿蒙 SDK 3.44.9(oh-3.44.9-dev)|DevEco Studio 26.0.0.821 | 设备:DevEco 模拟器 Pura X View | HarmonyOS 7.0.0.106(API 26)

ringer_mode 用于查询和监听设备的铃声模式(静音 / 振动 / 响铃)。它原本是 Android-only 的三方库(iOS 没有对应 API,鸿蒙侧也长期空白)。本文基于 ringer_mode 1.0.2 补上 OpenHarmony 实现,在 ArkTS 层调 @ohos.multimedia.audio.AudioVolumeGroupManager.getRingerMode() 读取当前模式 + 监听 ringerModeChange 事件流。文中的代码与效果均在 DevEco 模拟器(Pura X View,HarmonyOS 7.0.0.106 / API 26)上验证通过。
在这里插入图片描述
在这里插入图片描述

一、环境搭建

本章不重复展开,直接引用官方文档:Flutter OH 开发环境搭建指导

完成后用 flutter doctor -v 验证,FlutterHarmonyOS toolchain 两项均为 [√] 即可。本文实际使用的版本:Flutter OH oh-3.44.9-dev(commit 77e0c8d13b)、DevEco Studio 26.0.0.821、HarmonyOS SDK API 26。
在这里插入图片描述
在这里插入图片描述

二、应用背景

2.1 当前的应用场景与痛点

  • 企业会议应用:检测设备是否处于静音/振动模式,自动提示用户检查
  • 媒体/游戏类应用:根据铃声模式做差异化引导(静音时不弹通知音)
  • 个性化设置引导:检测到设备静音时主动告知"开启铃声以获得完整体验"
  • Do Not Disturb 类工具:核心功能就是检测 + 切换系统铃声模式

痛点:Flutter 生态里 ringer_mode 仅支持 Android(iOS 没有系统级 ringer API,鸿蒙侧长期无适配)。任何在鸿蒙上做铃声模式感知/引导的需求都没现成方案

2.2 为什么需要这个库

自己写插件要处理 ArkTS 系统音频管理 API、Ability 生命周期、EventChannel 桥接。ringer_mode 的鸿蒙适配版由 CPF-Flutter 社区维护,一行 git 依赖即可获得与 Android 完全一致的 API。

2.3 解决什么问题

一句话总结:让 Flutter 应用在鸿蒙上以与 Android 一致的 API 查询和监听设备铃声模式。具体提供:

  1. 当前模式查询(getRingerMode
  2. 模式变化事件流(ringerModeStream,用户去系统设置切换时自动推送)
  3. 模式切换(setRingerMode)— 见 FAQ/已知限制

三、功能介绍

功能API说明适用场景
模式查询getRingerMode()返回 Future<RingerMode>(silent/vibrate/normal)显示当前模式、引导用户去系统设置
事件流ringerModeStreamStream<RingerMode>,订阅后自动推送实时监听用户在系统设置里切换铃声模式
模式切换setRingerMode(mode)返回 Future<bool>(鸿蒙侧已知限制:返回 false,详见 §5)引导用户去系统设置

在这里插入图片描述

四、使用方法

4.1 在应用中引入三方库(AtomGit 链接方式)

dependencies:
  flutter:
    sdk: flutter
  ringer_mode:
    git:
      url: https://atomgit.com/weixin_52908342/ringer_mode.git
      ref: <commit>

执行 flutter pub get。注意:上游主包(Dart 层)原生就是 platform-agnostic 的(Dart 代码不区分 Android/鸿蒙),不需要 dependency_overrides

4.2 调用接口实现功能

4.2.1 RingerMode 枚举与字符串协议

上游 Dart 端定义 RingerMode { silent, vibrate, normal },与 Android AudioManager.RINGER_MODE_* 数值一一对应(silent=0, vibrate=1, normal=2)。本适配在 ArkTS 层将 AudioRingMode 枚举直接映射到相同的小写字符串,跨端一致性无需 Dart 任何改动

4.2.2 getRingerMode():查询当前模式

功能说明:异步 getter,返回当前铃声模式枚举值。

final mode = await RingerModeService.getRingerMode();
print('当前模式:${mode.name}'); // "silent" / "vibrate" / "normal"

运行效果

getRingerMode 首次启动
demo 首屏:橙色 AppBar + 绿色响铃图标 + 大字"响铃" + RingerMode.normal + getRingerMode() 副标题。下方三个按钮(静音/振动/响铃,"响铃"高亮表示当前选中)。底部暗色事件流卡显示 [19:36:56] getRingerMode() → normal(订阅时立即收到一次首播事件)

4.2.3 ringerModeStream:事件流

功能说明Stream<RingerMode>,订阅一次永久推送。用户去系统设置 → 声音和振动 切换铃声模式时,无需手动刷新即自动更新。

StreamSubscription<RingerMode>? _sub;


void initState() {
  super.initState();
  RingerModeService.getRingerMode().then((m) => setState(() => _mode = m));
  _sub = RingerModeService.ringerModeStream.listen((m) {
    setState(() => _mode = m);
  });
}


void dispose() {
  _sub?.cancel();
  super.dispose();
}

运行效果

ringerModeStream 真实触发
在鸿蒙系统设置 → 声音和振动 切换为"静音"后回到 demo:顶部大字"静音" + 灰色铃铛+斜线图标 + RingerMode.silent + 状态栏右上角铃铛符号消失。事件流日志新增一行 [19:39:43] ringerModeStream → silent —— 真实的 ringerModeChange 事件(无需任何手动刷新就推送到 Dart 层)

4.2.4 setRingerMode(mode):切换模式(已知限制)

功能说明:尝试设置设备铃声模式为指定值。

final ok = await RingerModeService.setRingerMode(RingerMode.vibrate);
// 鸿蒙端固定返回 false

运行效果

setRingerMode 已知限制
点击"振动"按钮后:顶部大字仍是"响铃"(未切换成功),状态卡显示 setRingerMode(vibrate) → false(可能需要授权或当前设备不支持),事件流日志新增 [19:37:43] setRingerMode(vibrate) → false。详见 §5 Q1

4.3 完整示例代码

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:ringer_mode/ringer_mode.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ringer_mode · OpenHarmony',
      theme: ThemeData(colorSchemeSeed: const Color(0xFFDB6D28)),
      home: const RingerPage(),
    );
  }
}

class RingerPage extends StatefulWidget {
  const RingerPage({super.key});
  
  State<RingerPage> createState() => _RingerPageState();
}

class _RingerPageState extends State<RingerPage> {
  RingerMode _mode = RingerMode.normal;
  final List<String> _events = [];
  StreamSubscription<RingerMode>? _sub;
  String _lastSetResult = '';

  
  void initState() {
    super.initState();
    RingerModeService.getRingerMode().then((m) => setState(() => _mode = m));
    _sub = RingerModeService.ringerModeStream.listen((m) {
      _log('ringerModeStream → ${m.name}');
      setState(() => _mode = m);
    });
  }

  
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }

  void _log(String msg) {
    final now = DateTime.now();
    _events.insert(
        0,
        '[${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}] $msg');
    if (_events.length > 20) _events.removeLast();
  }

  Future<void> _set(RingerMode mode) async {
    final ok = await RingerModeService.setRingerMode(mode);
    if (!mounted) return;
    setState(() => _lastSetResult =
        'setRingerMode(${mode.name}) → $ok${ok ? '' : '(可能需要授权或当前设备不支持)'}');
    _log('setRingerMode(${mode.name}) → $ok');
  }

  // 省略 build:与 §4.2 三张图展示的 UI 一致(顶部大字 + 三按钮 + 事件流卡 + 刷新按钮)
  // 完整工程见 https://atomgit.com/weixin_52908342/ringer_mode/example
}

签名与构建(活动硬性要求 signingConfig: "default"):

flutter create --platforms ohos .
# 在 example/ohos/build-profile.json5 的 app.signingConfigs 段填入签名材料
flutter build hap --debug
hdc install example/build/ohos/hap/entry-default-signed.hap
hdc shell aa start -b <bundleName> -a EntryAbility

模拟器上验证 ringerModeStream:

  1. 运行 demo 看到当前模式(响铃/vibrate/silent)
  2. 回到桌面,打开设置 → 声音和振动 → 在"静音/振动/响铃"间切换
  3. 回到 demo:顶部大字实时变化 + 事件流日志新增推送

五、FAQ:适配过程与使用问题

Q1:setRingerMode 调用返回 false

真实原因:setRingerMode 宿主接口 AudioRingManager 在 SDK 中已 @deprecated since 9整体移除。现行 AudioVolumeGroupManager 仅提供 getRingerMode / on('ringerModeChange') / off,没有 set 方法。

处理方案

  • 应用层引导用户去系统设置修改(推荐):startAbility 跳到设置 app 的声音和振动页(鸿蒙可通过 Want with action: 'ohos.settings.sound',或简单 context.startAbility({ bundleName: 'com.huawei.hmos.settings' })
  • 直接跳设置首页hdc shell aa start -b com.huawei.hmos.settings 在 demo 中已演示此跳转路径

Q2:编译报 'audio' has no exported member named 'AudioRingManager'

确认 Q1 说明——SDK 已删除 AudioRingManager 命名空间引用。本适配直接返回 false 而不是尝试调旧 API,避免编译失败和运行时系统拒绝双失败。

Q3:编译报 'Namespace 'autoFillManager' has no exported member'

DevEco SDK 6.1.1 默认 API 24,与 3.44.9 不兼容。升级到 DevEco 26.0.0 Release(SDK API 26)。

Q4:getRingerMode 在某些设备返回 null 怎么兜底?

上游 Dart 端 getRingerMode 在 PlatformException 时返回 RingerMode.normal 兜底——本适配保留这一行为。

Q5:ringerModeStream 在小程序/Activity 切换时如何管理生命周期?

Flutter 侧在 dispose()_sub?.cancel()(demo 已演示)。鸿蒙侧 AudioVolumeGroupManager.off('ringerModeChange') 在插件 onDetachedFromEngine 时调用——已实现。

Q6:Demo 在 iOS 上表现如何?

上游 Dart 端 ringerModeStream 在 iOS 上返回固定 Stream.value(RingerMode.silent)(iOS 无 ringer API)。本适配不影响 iOS 行为,iOS 用户会一直看到 silent——这是上游设计,不是 bug。

Q7:示例工程编译报 image_picker 找不到 ohos 实现

本 demo 不依赖 image_picker(ringer_mode 与附件无关)。fork 主包 example 默认依赖 image_picker,但本次 example 重写时已去掉(参考 example/lib/main.dart)。

Q8:Q&A 怎么提 Issue 和 PR?

仓库地址:https://atomgit.com/weixin_52908342/ringer_mode(本期贡献仓库)。

  • 提 Issue(四要素):复现步骤 / 期望行为 / 实际行为 / flutter --version + hdc shell param get const.product.software.version + hdc shell hilog -t OHOSAbility -x 关键日志
  • 提 PR:Fork → 建 fix/... 分支 → 修改 → push → 在 AtomGit 发 PR,描述附鸿蒙真机/模拟器验证截图

六、其他内容

ringer_mode 1.0.2 鸿蒙适配版开箱即用:两行 git 依赖 + 一个 .listen() 即可查询和监听鸿蒙设备的铃声模式,与 Android 完全一致的 API 与字符串协议。Demo 中真实验证了 getRingerMode() 返回 normal/vibrate/silent 三种值、ringerModeStream 在系统设置切换时实时推送。唯一限制是 setRingerMode 不支持(SDK 已移除 AudioRingManager),应用层应引导用户到系统设置完成切换——这是诚实的已知限制,比强行在编译期 hack 旧 API 安全。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐