如何在Expo笔记应用中实现带音效、震动的持久到期通知?
问题解决:笔记到期通知的音效、震动及持久化横幅实现
核心错误分析
- 未监听通知到达事件:仅监听了用户点击通知的
addNotificationResponseReceivedListener,但通知主动弹出时的触发逻辑缺失,导致音效/震动无法在通知到达时启动 - 音效触发时机错误:修改后的代码在组件挂载时直接播放音效,而非绑定到通知到达事件
- 震动逻辑未关联通知:没有在通知到达时调用震动API
- 持久化横幅未配置:创建本地通知时未设置对应系统的持久化属性(iOS用
sticky,Android用ongoing) - 应用焦点处理错误:误用通知响应监听来控制音频,应使用
AppState监听应用前后台状态
修正后的完整代码
import { useEffect, useState } from "react"; import * as Notifications from "expo-notifications"; import { Alert, Vibration, AppState } from "react-native"; import { useNavigation } from "@react-navigation/native"; import { Audio } from 'expo-av'; // 配置通知处理器 Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldPlaySound: false, // 关闭系统默认音效,使用自定义音效 shouldSetBadge: false, shouldShowAlert: true, }), }); const HandleNotifications = () => { const navigation = useNavigation(); const [sound, setSound] = useState(null); const [appState, setAppState] = useState(AppState.currentState); // 加载自定义音效 async function loadCustomSound() { try { const { sound: soundObject } = await Audio.Sound.createAsync( require('../assets/sonido.mp3') ); setSound(soundObject); } catch (error) { console.error("加载音效失败:", error); } } // 请求通知权限并加载音效 useEffect(() => { async function configureNotifications() { const { status } = await Notifications.getPermissionsAsync(); let finalStatus = status; if (finalStatus !== "granted") { const { status: newStatus } = await Notifications.requestPermissionsAsync(); finalStatus = newStatus; } if (finalStatus !== "granted") { Alert.alert( "权限要求", "需要本地通知权限才能接收笔记到期提醒" ); return; } await loadCustomSound(); } configureNotifications(); }, []); // 监听通知到达事件:触发音效和震动 useEffect(() => { const notificationReceivedSubscription = Notifications.addNotificationReceivedListener(async () => { // 触发震动(持续1秒,可自定义时长或震动模式) Vibration.vibrate(1000); // 播放自定义音效 if (sound) { await sound.replayAsync(); // 重播音效,避免重复加载资源 } }); // 监听通知点击事件:导航到对应页面并停止震动 const notificationResponseSubscription = Notifications.addNotificationResponseReceivedListener((response) => { Vibration.cancel(); const reminderId = response.notification.request.content.data.reminderId; if (reminderId) { navigation.navigate("ModifyReminder", { reminderId }); } }); return () => { notificationReceivedSubscription.remove(); notificationResponseSubscription.remove(); }; }, [sound, navigation]); // 监听应用前后台状态,处理音效资源 useEffect(() => { const appStateSubscription = AppState.addEventListener('change', nextAppState => { if (appState.match(/inactive|background/) && nextAppState === 'active') { // 应用回到前台,停止播放中的音效 if (sound) { sound.stopAsync(); } } setAppState(nextAppState); }); return () => { appStateSubscription.remove(); if (sound) { sound.unloadAsync(); } }; }, [sound, appState]); // ------------------------------ // 注意:创建本地通知时需添加持久化横幅配置 // 示例创建通知的代码(需在你的笔记到期逻辑中调用): // async function scheduleReminderNotification(reminderId, title, body) { // // 提前创建Android通知渠道(仅需执行一次) // await Notifications.setNotificationChannelAsync('reminder-channel', { // name: '笔记到期提醒', // importance: Notifications.AndroidImportance.MAX, // }); // await Notifications.scheduleNotificationAsync({ // content: { // title, // body, // data: { reminderId }, // }, // trigger: { seconds: 10 }, // 测试用10秒后触发,替换为你的到期时间 // // 配置持久化横幅 // ios: { // sticky: true, // iOS:通知横幅保持到手动关闭 // }, // android: { // channelId: "reminder-channel", // ongoing: true, // Android:通知为持续通知,需手动清除 // priority: Notifications.AndroidNotificationPriority.MAX, // }, // }); // } // ------------------------------ return null; }; export default HandleNotifications;
关键修改说明
- 新增通知到达监听:通过
addNotificationReceivedListener在通知弹出时立即触发震动和自定义音效 - 修正音效触发逻辑:在通知到达时调用
sound.replayAsync(),避免组件挂载就自动播放 - 添加震动控制:通知到达时触发震动,用户点击通知时停止震动
- 持久化横幅配置:创建通知时,iOS设置
sticky: true,Android设置ongoing: true并提前配置通知渠道 - 应用状态监听修复:使用
AppState监听应用前后台切换,合理管理音效资源 - 关闭系统默认音效:将
shouldPlaySound设为false,避免和自定义音效冲突
内容的提问来源于stack exchange,提问作者Miguel Espeso
相关产品推荐
相关产品推荐

