React Native中如何将远程通知里的手机号替换为设备联系人姓名?
React Native后台/退出状态下替换FCM通知中的手机号为联系人姓名
因为App处于后台或完全退出时,React Native的JS代码不会执行,所以必须依赖原生层拦截并修改通知内容,核心流程是:拦截FCM远程通知 → 从本地通讯录匹配手机号对应的姓名 → 修改通知内容后再展示。以下分iOS和Android平台分别说明实现方案:
iOS 实现步骤
1. 配置基础依赖与权限
- 确保已集成Firebase Messaging,且
AppDelegate中配置了远程通知权限。 - 在
Info.plist中添加通讯录权限描述:<key>NSContactsUsageDescription</key> <string>需要访问通讯录以识别通知中的联系人</string>
2. 拦截并修改通知内容
在AppDelegate.swift中实现通知拦截逻辑,覆盖退出和后台两种状态:
import FirebaseMessaging import Contacts import UserNotifications @main class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 初始化FCM FirebaseApp.configure() Messaging.messaging().delegate = self // 请求通知权限 UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in } application.registerForRemoteNotifications() // 处理退出状态下的通知 if let remoteNotification = launchOptions?[.remoteNotification] as? [String: Any] { handleRemoteNotification(remoteNotification) } return true } // 后台状态下接收通知 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { handleRemoteNotification(userInfo) completionHandler(.newData) } // 统一处理通知逻辑 private func handleRemoteNotification(_ userInfo: [AnyHashable: Any]) { guard let aps = userInfo["aps"] as? [String: Any], var alert = aps["alert"] as? [String: Any], var body = alert["body"] as? String else { return } // 提取手机号(适配带国家码、带分隔符的格式) let phoneRegex = "\\[(\\+?\\d{1,3}-?\\d{9,15})\\]" guard let regex = try? NSRegularExpression(pattern: phoneRegex), let match = regex.firstMatch(in: body, range: NSRange(body.startIndex..., in: body)) else { return } let phoneNumber = (body as NSString).substring(with: match.range(at: 1)) // 查询联系人姓名 if let contactName = fetchContactName(for: phoneNumber) { // 替换手机号为姓名 body = body.replacingOccurrences(of: "\\[\\$phone\\]", with: contactName) alert["body"] = body // 展示修改后的通知 let content = UNMutableNotificationContent() content.title = alert["title"] as? String ?? "App Name" content.body = body content.sound = UNNotificationSound.default let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request) } } // 查询通讯录中对应手机号的姓名 private func fetchContactName(for phoneNumber: String) -> String? { let store = CNContactStore() let keys = [CNContactGivenNameKey, CNContactFamilyNameKey] as [CNKeyDescriptor] let predicate = CNContact.predicateForContacts(matchingPhoneNumber: phoneNumber) do { let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys) guard let contact = contacts.first else { return nil } return "\(contact.givenName) \(contact.familyName)".trimmingCharacters(in: .whitespaces) } catch { print("通讯录查询失败: \(error.localizedDescription)") return nil } } } extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { // 处理FCM Token更新 } }
Android 实现步骤
1. 配置基础依赖与权限
- 在
AndroidManifest.xml中注册自定义FCM服务,并添加通讯录权限:<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <application> <service android:name=".MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> </application>
2. 自定义FCM服务拦截通知
创建MyFirebaseMessagingService.kt,注意:必须发送FCM数据消息而非通知消息,否则系统会直接展示通知,无法拦截。
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import android.Manifest import android.content.ContentResolver import android.content.pm.PackageManager import android.os.Build import android.provider.ContactsContract import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat class MyFirebaseMessagingService : FirebaseMessagingService() { private val CHANNEL_ID = "app_notification_channel" override fun onMessageReceived(remoteMessage: RemoteMessage) { super.onMessageReceived(remoteMessage) // 从数据字段中获取通知内容(必须用数据消息格式) val title = remoteMessage.data["title"] ?: "App Name" var body = remoteMessage.data["body"] ?: "" // 提取手机号 val phoneRegex = "\\[(\\+?\\d{1,3}-?\\d{9,15})\\]".toRegex() val matchResult = phoneRegex.find(body) matchResult?.let { result -> val phoneNumber = result.groupValues[1] // 查询联系人姓名 val contactName = fetchContactName(phoneNumber) // 替换手机号 contactName?.let { name -> body = body.replace("[$phone]", name) } } // 展示修改后的通知 showNotification(title, body) } // 查询通讯录中对应手机号的姓名 private fun fetchContactName(phoneNumber: String): String? { // 检查权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission( this, Manifest.permission.READ_CONTACTS ) != PackageManager.PERMISSION_GRANTED ) { return null } val contentResolver: ContentResolver = contentResolver val projection = arrayOf(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) val selection = "${ContactsContract.CommonDataKinds.Phone.NUMBER} = ?" val selectionArgs = arrayOf(phoneNumber) contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, selection, selectionArgs, null )?.use { cursor -> if (cursor.moveToFirst()) { return cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)) } } return null } // 创建并展示通知 private fun showNotification(title: String, body: String) { // 创建通知渠道(Android 8.0+ 必需) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = android.app.NotificationChannel( CHANNEL_ID, "应用通知", android.app.NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "用于展示应用更新通知" } val notificationManager = getSystemService(android.app.NotificationManager::class.java) notificationManager.createNotificationChannel(channel) } val builder = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) // 替换为你的通知图标 .setContentTitle(title) .setContentText(body) .setPriority(NotificationCompat.PRIORITY_DEFAULT) with(NotificationManagerCompat.from(this)) { notify(System.currentTimeMillis().toInt(), builder.build()) } } }
3. FCM消息格式要求
Android后台/退出状态下,必须发送数据消息,示例格式:
{ "data": { "title": "App Name", "body": "[$phone] changed something on the app" }, "to": "设备FCM Token" }
通用注意事项
- 权限提前申请:在React Native层提前用
react-native-permissions等库申请通讯录权限,否则原生层无法查询联系人。 - 手机号格式统一:对提取的手机号做格式化处理(比如去掉空格、横杠,统一国家码格式),避免因格式不一致导致查询不到联系人。
- 场景测试:分别测试App后台运行、完全退出两种状态,确保通知修改逻辑正常生效。
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

