在移动应用开发中,通知不仅仅是文字信息的传递,声音和震动反馈同样是用户体验的重要组成部分。Expo 作为 React Native 生态中广受欢迎的框架,提供了 expo-notifications 模块来简化通知功能的集成。然而,声音播放和震动触发的配置涉及平台权限、通知通道、音频文件格式等多个层面,稍有不慎就会导致功能在真机上表现异常。本文将从基础配置入手,逐步讲解如何在 Expo 应用中完整实现声音和震动通知。

一、Expo Notifications 模块安装与权限配置
要实现通知功能,首先需要在项目中安装 expo-notifications 包。这个模块封装了 Android 和 iOS 平台的原生通知能力,提供了统一的 JavaScript API。安装命令非常简单,在项目根目录下执行即可。安装完成后,还需要在 app.json 配置文件中声明相关插件,确保 Expo 在构建时能正确链接原生模块。
权限配置是通知功能能否正常工作的前提条件。iOS 平台要求在 app.json 的 ios.infoPlist 字段中声明 NSUserNotificationUsageDescription 等权限描述字符串,向用户说明为什么需要通知权限。Android 平台从 Android 13(API 33)开始也强制要求运行时申请通知权限,需要在 android.permissions 中声明 android.permission.POST_NOTIFICATIONS。如果遗漏这些配置,应用在真机上请求权限时会直接崩溃或静默失败。
下面是 app.json 中通知相关配置的完整示例,包含了插件声明和权限描述:
{
"expo": {
"name": "NotificationDemo",
"plugins": [
[
"expo-notifications",
{
"color": "#ffffff",
"sounds": ["./assets/sounds/notification.wav"]
}
]
],
"android": {
"permissions": ["android.permission.POST_NOTIFICATIONS"]
},
"ios": {
"infoPlist": {
"NSUserNotificationUsageDescription": "我们需要发送通知来提醒您重要事项"
}
}
}
}
上述配置中,plugins 数组里的 sounds 字段用于注册自定义通知声音文件,这个文件路径是相对于项目根目录的。需要注意的是,iOS 支持的音频格式主要是 wav、aiff 和 caf,而 Android 则支持 mp3、wav、ogg 等格式。为了保证双平台兼容,建议统一使用 wav 格式的音频文件,采样率建议为 44100Hz,时长控制在 30 秒以内。
配置完成后,在应用启动时还需要通过代码请求通知权限。expo-notifications 提供了 requestPermissionsAsync 方法来统一处理权限申请,该方法会根据当前平台自动弹出对应的系统权限对话框。开发者应该检查返回的权限状态,只有在用户授权后才执行后续的通知调度逻辑。
二、实现声音通知:默认提示音与自定义音频
声音通知的实现分为两个层次:使用系统默认提示音和使用自定义音频文件。系统默认提示音是最简单的方式,只需要在创建通知时设置 sound 参数为 true,设备就会在收到通知时播放系统默认的通知音效。这种方式无需额外配置音频文件,适合对声音没有特殊要求的应用场景。
然而,大多数应用希望使用独特的提示音来增强品牌识别度,这就需要配置自定义声音。在 Expo 中,自定义声音的实现依赖于通知通道(Android)和声音文件注册(iOS)两个机制。Android 平台通过通知通道的 sound 属性指定音频文件,iOS 平台则通过通知内容的 sound 属性引用在 app.json 中注册的声音文件名。
下面是创建带有自定义声音的本地通知的完整代码示例:
import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
// 配置通知处理器,设置前台通知的显示行为
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
// 创建带自定义声音的通知通道(Android专用)
async function createSoundChannel() {
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('custom-sound-channel', {
name: '自定义声音通知',
importance: Notifications.AndroidImportance.HIGH,
sound: 'notification.wav', // 对应app.json中注册的声音文件
vibrationPattern: [0, 250, 250, 250], // 震动模式
enableVibration: true,
});
}
}
// 发送本地通知
async function scheduleNotification() {
await createSoundChannel();
await Notifications.scheduleNotificationAsync({
content: {
title: '新消息提醒',
body: '您有一条新的未读消息,请及时查看',
sound: 'notification.wav', // iOS引用注册的声音文件名
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.TimeInterval,
seconds: 5,
channelId: 'custom-sound-channel', // Android指定通道
},
});
}
上述代码中有几个关键点需要特别注意。首先,setNotificationHandler 中的 shouldPlaySound 控制的是应用在前台时收到通知是否播放声音,默认值为 false,如果不设置为 true,前台通知将不会有声音反馈。其次,Android 的通知通道一旦创建,其声音和震动配置就无法修改,用户只能在系统设置中关闭但无法通过代码更改。因此,如果需要更换声音,必须创建一个新的通道。
iOS 平台的自定义声音引用方式与 Android 不同。在 iOS 上,content.sound 字段直接填写在 app.json 中 sounds 数组注册过的文件名(不含路径),系统会自动匹配。如果填写 true 则使用系统默认声音,填写 null 或不设置则静默。需要特别注意的是,iOS 设备在静音模式下不会播放任何通知声音,这是系统级行为,应用层面无法绕过。
三、震动反馈的实现与平台差异处理
震动反馈是通知功能的另一重要维度,它能在静音或嘈杂环境中为用户提供触觉层面的感知。在 Expo 中,震动通知的实现方式因平台而异。Android 平台的震动通过通知通道的 vibrationPattern 和 enableVibration 属性控制,而 iOS 平台的通知震动则是系统自动触发的,开发者无法自定义震动模式。
Android 的震动模式通过一个整数数组定义,数组中的偶数索引元素表示静止时长(毫秒),奇数索引元素表示震动时长(毫秒)。例如 [0, 250, 250, 250] 表示立即开始震动 250 毫秒,然后静止 250 毫秒,再震动 250 毫秒。这种模式可以创建有节奏的震动序列,帮助用户区分不同类型的通知。需要注意的是,震动功能还需要 android.permission.VIBRATE 权限,不过这个权限在 Expo 中默认已经包含,无需额外声明。
如果需要在非通知场景下触发震动,比如按钮点击反馈或操作确认,可以使用 expo-haptics 模块。这个模块提供了更丰富的触觉反馈选项,包括轻触、中等力度、重击等不同强度的震动模式,并且支持 iOS 平台。下面是 expo-haptics 的使用示例:
import * as Haptics from 'expo-haptics';
// 触发通知型震动反馈
async function triggerHapticFeedback() {
await Haptics.notificationAsync(
Haptics.NotificationFeedbackType.Success
);
}
// 触发冲击型震动
async function triggerImpactFeedback() {
await Haptics.impactAsync(
Haptics.ImpactFeedbackStyle.Medium
);
}
// 触发选择型震动(用于滚动选择器等场景)
async function triggerSelectionFeedback() {
await Haptics.selectionAsync();
}
expo-haptics 模块提供了三种震动类型:impactAsync 用于模拟物理碰撞的触感,支持 Light、Medium、Heavy 三种力度;notificationAsync 用于通知场景,提供 Success、Warning、Error 三种语义化震动模式;selectionAsync 则用于用户选择操作时的轻微反馈。在 iOS 上,这些震动利用的是 Taptic Engine 硬件,体验比传统马达震动更加细腻。在 Android 上,模块会自动降级为系统震动马达的震动效果。
在实际开发中,通知震动和 Haptics 震动各有适用场景。通知场景下,应通过通知通道配置震动模式,这样即使应用在后台或被杀死,通知到达时仍能触发震动。而交互场景下的即时反馈,则应使用 expo-haptics,因为它不依赖通知系统,响应更即时。两者结合使用,能为用户提供全方位的触觉反馈体验。
最后需要提醒的是,震动和声音的测试必须在真机上进行。模拟器无法模拟震动硬件,部分模拟器也无法播放通知声音。建议在 Android 和 iOS 真机上分别测试通知的到达、声音播放和震动触发,并检查不同系统版本和不同静音模式下的表现。特别是 Android 的通知通道行为在不同厂商定制系统上可能存在差异,建议在小米、华为、三星等主流品牌设备上进行兼容性测试,确保通知反馈在所有目标设备上都能正常工作。