推送通知设置
概述
为iOS和Android应用实现全面的推送通知系统,使用Firebase Cloud Messaging和原生平台服务。
使用场景
- 向用户发送实时通知
- 实施用户参与功能
- 从通知到特定屏幕的深度链接
- 处理静默/后台通知
- 跟踪通知分析
指令
1. Firebase Cloud Messaging 设置
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';
export async function initializeFirebase() {
try {
if (Platform.OS === 'ios') {
const permission = await messaging().requestPermission();
if (permission === messaging.AuthorizationStatus.AUTHORIZED) {
console.log('iOS通知权限已授予');
}
}
const token = await messaging().getToken();
console.log('FCM Token:', token);
await saveTokenToBackend(token);
messaging().onTokenRefresh(async (newToken) => {
await saveTokenToBackend(newToken);
});
messaging().onMessage(async (remoteMessage) => {
console.log('Notification received:', remoteMessage);
showLocalNotification(remoteMessage);
});
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
if (remoteMessage.data?.type === 'sync') {
syncData();
}
});
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage) {
handleNotificationOpen(remoteMessage);
}
});
messaging().onNotificationOpenedApp((remoteMessage) => {
handleNotificationOpen(remoteMessage);
});
} catch (error) {
console.error('Firebase initialization failed:', error);
}
}
export async function saveTokenToBackend(token) {
try {
const response = await fetch('https://api.example.com/device-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
platform: Platform.OS,
timestamp: new Date().toISOString()
})
});
if (!response.ok) {
console.error('Failed to save token');
}
} catch (error) {
console.error('Error saving token:', error);
}
}
function handleNotificationOpen(remoteMessage) {
const { data } = remoteMessage;
if (data?.deepLink) {
navigationRef.navigate(data.deepLink, JSON.parse(data.params || '{}'));
}
}
2. iOS原生设置与Swift
import UIKit
import UserNotifications
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
requestNotificationPermission()
if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
handlePushNotification(remoteNotification)
}
return true
}
func requestNotificationPermission() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge]
) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("Device Token: \(token)")
saveTokenToBackend(token: token)
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Failed to register: \(error)")
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void
) {
let userInfo = notification.request.content.userInfo
if #available(iOS 14.0, *) {
completionHandler([.banner, .sound, .badge])
} else {
completionHandler([.sound, .badge])
}
handlePushNotification(userInfo)
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
handlePushNotification(userInfo)
completionHandler()
}
private func handlePushNotification(_ userInfo: [AnyHashable: Any]) {
if let deepLink = userInfo["deepLink"] as? String {
NotificationCenter.default.post(
name: NSNotification.Name("openDeepLink"),
object: deepLink
)
}
}
private func saveTokenToBackend(token: String) {
let urlString = "https://api.example.com/device-tokens"
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = ["token": token, "platform": "ios"]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request).resume()
}
}
3. Android设置与Kotlin
// AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".services.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
println("FCM Token: $token")
saveTokenToBackend(token)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
val title = remoteMessage.notification?.title ?: "Notification"
val body = remoteMessage.notification?.body ?: ""
val deepLink = remoteMessage.data["deepLink"] ?: ""
if (remoteMessage.notification != null) {
showNotification(title, body, deepLink)
}
}
private fun showNotification(title: String, message: String, deepLink: String = "") {
val channelId = "default_channel"
createNotificationChannel(channelId)
val intent = Intent(this, MainActivity::class.java).apply {
if (deepLink.isNotEmpty()) {
data = android.net.Uri.parse(deepLink)
}
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
}
private fun createNotificationChannel(channelId: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"Default Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun saveTokenToBackend(token: String) {
println("Saving token to backend: $token")
}
}
4. Flutter实现
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
class NotificationHandler {
static Future<void> initialize(NavigatorState navigator) async {
final settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
sound: true,
badge: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('Notification permission granted');
}
final token = await FirebaseMessaging.instance.getToken();
print('FCM Token: $token');
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Received: ${message.notification?.title}');
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
_handleDeepLink(navigator, message.data);
});
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
_handleDeepLink(navigator, initialMessage.data);
}
}
static void _handleDeepLink(NavigatorState navigator, Map<String, dynamic> data) {
final deepLink = data['deepLink'] as String?;
if (deepLink != null) {
navigator.pushNamed(deepLink);
}
}
}
最佳实践
✅ 执行
- 请求发送通知前的权限
- 实现令牌刷新处理
- 根据优先级使用不同的通知渠道
- 定期验证令牌
- 跟踪通知交付
- 实施深度链接
- 在所有应用状态下处理通知
- 使用静默通知进行数据同步
- 在后端安全地存储令牌
- 提供用户通知偏好设置
- 在真实设备上进行测试
❌ 不要
- 发送过多通知
- 未经许可发送
- 不安全地存储令牌
- 忽略通知失败
- 在有效载荷中发送敏感数据
- 使用通知进行垃圾邮件发送
- 忘记处理后台通知
- 在处理程序中进行阻塞调用
- 发送重复通知
- 忽略用户偏好设置