You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 17:04:58