React Native应用点击通知Snooze按钮崩溃问题求助
我开发了一个简单的React Native应用,包含“Set Reminder”按钮。点击按钮15秒后弹出带Snooze按钮的通知,同时设备持续振动10秒。预期点击Snooze按钮时,通知关闭、振动停止,5分钟后再次弹出相同通知并触发振动,但实际点击后应用崩溃,Logcat报错如下:
Invalid ID 0x00000000. 2024-07-16 09:20:13.583 15520-15615 RNPushNotification com.stickynotes W Unable to find com.dieam.reactnativepushnotification.notification_color in manifest. Falling back to default 2024-07-16 09:20:17.949 15520-15520 RNPushNotification com.stickynotes I RNPushNotificationBootEventReceiver loading scheduled notifications 2024-07-16 09:20:17.966 15520-15520 AndroidRuntime com.stickynotes D Shutting down VM 2024-07-16 09:20:17.972 15520-15520 AndroidRuntime com.stickynotes E FATAL EXCEPTION: main Process: com.stickynotes, PID: 15520 java.lang.RuntimeException: Unable to start receiver com.dieam.reactnativepushnotification.modules.RNPushNotificationActions: java.lang.SecurityException: Permission Denial: android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from com.stickynotes (pid=15520, uid=10599) requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS. at android.app.ActivityThread.handleReceiver(ActivityThread.java:4506) at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2239) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:204) at android.os.Looper.loop(Looper.java:291) at android.app.ActivityThread.main(ActivityThread.java:8134) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:601) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1019) Caused by: java.lang.SecurityException: Permission Denial: android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from com.stickynotes (pid=15520, uid=10599) requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS. at android.os.Parcel.createExceptionOrNull(Parcel.java:3021) at android.os.Parcel.createException(Parcel.java:3005) at android.os.Parcel.readException(Parcel.java:2988) at android.os.Parcel.readException(Parcel.java:2930) at android.app.IActivityManager$Stub$Proxy.broadcastIntentWithFeature(IActivityManager.java:5937) at android.app.ContextImpl.sendBroadcast(ContextImpl.java:1228) at android.content.ContextWrapper.sendBroadcast(ContextWrapper.java:500) at android.content.ContextWrapper.sendBroadcast(ContextWrapper.java:500) at com.dieam.reactnativepushnotification.modules.RNPushNotificationActions.onReceive(RNPushNotificationActions.java:65) at android.app.ActivityThread.handleReceiver(ActivityThread.java:4497) at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2239) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:204) at android.os.Looper.loop(Looper.java:291) at android.app.ActivityThread.main(ActivityThread.java:8134) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:601) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1019) Caused by: android.os.RemoteException: Remote stack trace: at com.android.server.wm.ActivityTaskManagerService.checkCanCloseSystemDialogs(ActivityTaskManagerService.java:3398) at com.android.server.wm.ActivityTaskManagerService$LocalService.checkCanCloseSystemDialogs(ActivityTaskManagerService.java:6079) at com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:15304) at com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:14819) at com.android.server.am.ActivityManagerService.broadcastIntentWithFeature(ActivityManagerService.java:15777)
我的App.js代码如下:
import React, { useEffect } from 'react'; import { View, Text, Button, StyleSheet, Vibration } from 'react-native'; import PushNotification from 'react-native-push-notification'; const App = () => { useEffect(() => { PushNotification.configure({ onNotification: function (notification) { if (notification.action === 'Snooze') { handleSnoozeAction(); } }, }); }, []); const handleSendReminder = () => { // Schedule notification after 15 seconds setTimeout(() => { PushNotification.localNotification({ channelId: 'default-channel', title: 'Reminder Notification', message: "Don't forget to take a break!", actions: ['Snooze'], }); // Start continuous vibration for 10 seconds const vibrationInterval = setInterval(() => { Vibration.vibrate(1000); // Vibrate for 1 second }, 1000); // Stop vibration after 10 seconds setTimeout(() => { clearInterval(vibrationInterval); Vibration.cancel(); }, 10000); // 10 seconds }, 15000); // 15 seconds }; const handleSnoozeAction = () => { // Clear all notifications and vibrations PushNotification.cancelAllLocalNotifications(); Vibration.cancel(); // Schedule a new notification and vibration after 2 minutes setTimeout(() => { PushNotification.localNotification({ channelId: 'default-channel', title: 'Reminder Notification', message: "Don't forget to take a break!", actions: ['Snooze'], }); // Start continuous vibration for 10 seconds const vibrationInterval = setInterval(() => { Vibration.vibrate(1000); // Vibrate for 1 second }, 1000); // Stop vibration after 10 seconds setTimeout(() => { clearInterval(vibrationInterval); Vibration.cancel(); }, 10000); // 10 seconds }, 120000); // 2 minutes }; return ( <View style={styles.container}> <Text style={styles.title}>Welcome to My App</Text> <Button title="Set Reminder" onPress={handleSendReminder} /> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, title: { fontSize: 24, marginBottom: 20, }, }); export default App;
解决方案
核心问题分析
崩溃的直接原因是应用缺少BROADCAST_CLOSE_SYSTEM_DIALOGS权限,react-native-push-notification库在处理通知按钮点击时会发送系统广播,Android要求必须显式声明该权限才能执行此操作。另外日志中的警告是因为未配置通知颜色,也需要处理。
修复步骤
添加权限声明
在android/app/src/main/AndroidManifest.xml中添加以下权限:<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />配置通知颜色
在AndroidManifest.xml的<application>标签内添加元数据:<meta-data android:name="com.dieam.reactnativepushnotification.notification_color" android:resource="@color/white" />若
android/app/src/main/res/values/colors.xml不存在则创建,添加颜色值:<?xml version="1.0" encoding="utf-8"?> <resources> <color name="white">#FFFFFF</color> </resources>优化定时器清理逻辑
原代码中定时器未在组件卸载时清理,可能导致内存泄漏,修改useEffect添加清理函数:useEffect(() => { let timers = { reminder: null, snooze: null, vibrationStop: null, vibrationInterval: null }; PushNotification.configure({ onNotification: function (notification) { if (notification.action === 'Snooze') { handleSnoozeAction(timers); } }, }); return () => { // 清理所有定时器 Object.values(timers).forEach(timer => { if (timer) { if (typeof timer === 'number') clearTimeout(timer); else clearInterval(timer); } }); Vibration.cancel(); PushNotification.cancelAllLocalNotifications(); }; }, []);同时修改
handleSendReminder和handleSnoozeAction,将定时器存入timers对象以便统一清理。使用原生调度替代setTimeout
原生localNotificationSchedule比setTimeout更可靠,尤其是应用在后台时,替换后的代码:const handleSendReminder = (timers) => { // 15秒后触发通知 PushNotification.localNotificationSchedule({ channelId: 'default-channel', title: 'Reminder Notification', message: "Don't forget to take a break!", actions: ['Snooze'], date: new Date(Date.now() + 15000), }); // 振动逻辑 timers.vibrationInterval = setInterval(() => { Vibration.vibrate(1000); }, 1000); timers.vibrationStop = setTimeout(() => { clearInterval(timers.vibrationInterval); Vibration.cancel(); }, 10000); }; const handleSnoozeAction = (timers) => { PushNotification.cancelAllLocalNotifications(); Vibration.cancel(); // 2分钟后重新调度通知 PushNotification.localNotificationSchedule({ channelId: 'default-channel', title: 'Reminder Notification', message: "Don't forget to take a break!", actions: ['Snooze'], date: new Date(Date.now() + 120000), }); // 振动逻辑 timers.vibrationInterval = setInterval(() => { Vibration.vibrate(1000); }, 1000); timers.vibrationStop = setTimeout(() => { clearInterval(timers.vibrationInterval); Vibration.cancel(); }, 10000); };
内容的提问来源于stack exchange,提问作者michael

