MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin推送通知Firebase Cloud Messaging

2024-02-144.9k 阅读

简介

在移动应用开发中,推送通知是一种强大的工具,它可以帮助开发者与用户保持互动,向用户及时传达重要信息。Firebase Cloud Messaging (FCM) 是一个跨平台的消息传递解决方案,可让您免费可靠地向 Android、iOS 和 Web 应用发送消息。Kotlin 作为 Android 开发的首选语言,与 FCM 集成可以轻松实现推送通知功能。本文将详细介绍如何在 Kotlin 项目中集成 FCM 实现推送通知。

准备工作

  1. 创建 Firebase 项目
  2. 将 Firebase 添加到 Android 项目
    • 在 Android Studio 项目中,打开项目级别的 build.gradle 文件,确保 Google 的 Maven 仓库和 Gradle 插件依赖配置正确。例如:
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.4.2'
        classpath 'com.google.gms:google-services:4.3.15'
    }
}
- 在应用级别的 `build.gradle` 文件中,添加 Firebase Messaging 依赖。例如:
dependencies {
    implementation 'com.google.firebase:firebase - messaging - ktx:23.0.6'
}
apply plugin: 'com.google.gms.google - services'
  1. 配置 Firebase 云消息传递
    • 在 Firebase 控制台中,进入项目设置,下载 google-services.json 文件,并将其复制到 Android 项目的 app 目录下。
    • 在 AndroidManifest.xml 文件中,添加以下权限:
<uses - permission android:name="android.permission.INTERNET"/>
<uses - permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
- 同时,注册 `FirebaseMessagingService` 和 `FirebaseInstanceIdService`(在 Kotlin 中它们是扩展的服务类)。例如:
<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent - filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent - filter>
</service>
<service
    android:name=".MyFirebaseInstanceIdService"
    android:exported="false">
    <intent - filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent - filter>
</service>

实现 Firebase Cloud Messaging 服务

  1. 创建 FirebaseMessagingService 子类
    • 继承 FirebaseMessagingService 类,该类负责处理接收到的推送消息。
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService : FirebaseMessagingService() {
    private val TAG = "MyFirebaseMsgService"

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // 处理接收到的消息
        Log.d(TAG, "From: ${remoteMessage.from}")

        // 检查消息是否包含通知内容
        remoteMessage.notification?.let {
            Log.d(TAG, "Message Notification Body: ${it.body}")
            sendNotification(it.body.toString())
        }

        // 处理自定义数据
        remoteMessage.data.isNotEmpty().let {
            Log.d(TAG, "Message data payload: ${remoteMessage.data}")
        }
    }

    private fun sendNotification(messageBody: String) {
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE)

        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(this, getString(R.string.default_notification_channel_id))
           .setSmallIcon(R.drawable.ic_notification)
           .setContentTitle(getString(R.string.app_name))
           .setContentText(messageBody)
           .setAutoCancel(true)
           .setSound(defaultSoundUri)
           .setContentIntent(pendingIntent)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // 创建通知渠道(Android 8.0 及以上需要)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(getString(R.string.default_notification_channel_id),
                "FCM Notifications",
                NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
    }
}
  1. 创建 FirebaseInstanceIdService 子类(可选,但推荐)
    • 继承 FirebaseInstanceIdService 类,用于获取设备的 FCM 令牌。FCM 令牌是设备在 FCM 服务中的唯一标识符,在向特定设备发送推送通知时会用到。
import android.util.Log
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.iid.FirebaseInstanceIdService

class MyFirebaseInstanceIdService : FirebaseInstanceIdService() {
    private val TAG = "MyFirebaseIIDService"

    override fun onTokenRefresh() {
        // 获取最新的 FCM 令牌
        val refreshedToken = FirebaseInstanceId.getInstance().token
        Log.d(TAG, "Refreshed token: $refreshedToken")

        // 将令牌发送到您的服务器(如果需要)
        sendRegistrationToServer(refreshedToken)
    }

    private fun sendRegistrationToServer(token: String?) {
        // 这里实现将令牌发送到您的服务器逻辑
    }
}

处理不同类型的推送消息

  1. 通知消息
    • 通知消息由 FCM 直接处理并显示给用户,不需要应用在前台运行。在 MyFirebaseMessagingServiceonMessageReceived 方法中,remoteMessage.notification 包含了通知的标题、内容等信息。例如:
remoteMessage.notification?.let {
    Log.d(TAG, "Message Notification Body: ${it.body}")
    sendNotification(it.body.toString())
}
  1. 数据消息
    • 数据消息需要应用在前台运行时自行处理。在 MyFirebaseMessagingServiceonMessageReceived 方法中,remoteMessage.data 包含了自定义的数据。例如:
remoteMessage.data.isNotEmpty().let {
    Log.d(TAG, "Message data payload: ${remoteMessage.data}")
    // 在这里根据数据执行相应的逻辑
}
  1. 混合消息
    • 混合消息既包含通知部分又包含数据部分。同样在 onMessageReceived 方法中,可以分别处理通知和数据部分。例如:
remoteMessage.notification?.let {
    Log.d(TAG, "Message Notification Body: ${it.body}")
    sendNotification(it.body.toString())
}
remoteMessage.data.isNotEmpty().let {
    Log.d(TAG, "Message data payload: ${remoteMessage.data}")
    // 处理数据
}

发送推送通知

  1. 使用 Firebase 控制台发送测试通知
    • 在 Firebase 控制台中,进入“云消息传递”选项卡。
    • 选择“发送测试消息”,可以选择目标设备(通过 FCM 令牌或主题),输入通知标题和内容,然后点击“发送”。
  2. 从服务器发送推送通知
    • 如果您有自己的服务器,可以使用 FCM 的 HTTP v1 API 来发送推送通知。以下是一个简单的使用 cURL 发送通知的示例:
curl -X POST -H "Authorization: Bearer <server - key>" -H "Content - Type: application/json" -d '{
  "message": {
    "token": "<device - token>",
    "notification": {
      "title": "Test Notification",
      "body": "This is a test push notification"
    }
  }
}' "https://fcm.googleapis.com/v1/projects/<project - id>/messages:send"
- 其中 `<server - key>` 可以在 Firebase 控制台的“项目设置” - “云消息传递”中找到,`<device - token>` 是目标设备的 FCM 令牌,`<project - id>` 是您的 Firebase 项目 ID。

主题消息推送

  1. 订阅主题
    • 应用可以让用户订阅特定的主题,这样就可以向所有订阅该主题的设备发送推送通知。在 Kotlin 代码中,可以使用以下方式订阅主题:
FirebaseMessaging.getInstance().subscribeToTopic("news")
   .addOnCompleteListener { task ->
        var msg = "Subscribed"
        if (!task.isSuccessful) {
            msg = "Subscribe failed"
        }
        Log.d(TAG, msg)
    }
  1. 发送主题消息
    • 从服务器发送主题消息时,将目标设置为主题名称。例如,使用 FCM HTTP v1 API:
curl -X POST -H "Authorization: Bearer <server - key>" -H "Content - Type: application/json" -d '{
  "message": {
    "topic": "news",
    "notification": {
      "title": "New News",
      "body": "There is a new news article available"
    }
  }
}' "https://fcm.googleapis.com/v1/projects/<project - id>/messages:send"

高级功能和优化

  1. 消息优先级
    • FCM 支持设置消息优先级,高优先级的消息会优先发送并立即显示给用户,而低优先级的消息可能会在设备处于节能模式等情况下延迟发送。在发送消息时,可以通过设置 priority 字段来指定优先级。例如,在服务器端发送通知时:
{
  "message": {
    "token": "<device - token>",
    "notification": {
      "title": "High Priority Notification",
      "body": "This is a high priority push notification"
    },
    "priority": "high"
  }
}
  1. 消息有效负载大小限制
    • FCM 对推送消息的有效负载大小有限制。对于通知消息,总大小限制为 4096 字节,对于数据消息,限制为 4096 字节(如果使用可折叠标头,则为 2048 字节)。在构建消息时,需要确保不超过这些限制。
  2. 处理多语言通知
    • 如果您的应用面向多个语言区域的用户,可以在通知中提供多语言支持。可以通过在服务器端根据设备的语言设置发送不同语言的通知内容,或者在应用端根据设备语言动态处理通知内容。例如,在 MyFirebaseMessagingService 中,可以根据设备语言设置选择不同的通知字符串资源:
private fun sendNotification(messageBody: String) {
    val context = applicationContext
    val resources = context.resources
    val appName = resources.getString(R.string.app_name)
    val intent = Intent(this, MainActivity::class.java)
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
        PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE)

    val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
    val notificationBuilder = NotificationCompat.Builder(this, getString(R.string.default_notification_channel_id))
       .setSmallIcon(R.drawable.ic_notification)
       .setContentTitle(appName)
       .setContentText(messageBody)
       .setAutoCancel(true)
       .setSound(defaultSoundUri)
       .setContentIntent(pendingIntent)

    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    // 创建通知渠道(Android 8.0 及以上需要)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(getString(R.string.default_notification_channel_id),
            "FCM Notifications",
            NotificationManager.IMPORTANCE_DEFAULT)
        notificationManager.createNotificationChannel(channel)
    }

    notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
}

在这个例子中,appName 是从字符串资源中获取的,根据设备语言设置会加载相应语言的字符串。

  1. 优化电池使用
    • 为了减少推送通知对设备电池的消耗,可以尽量避免频繁发送不必要的推送。对于低优先级的消息,可以考虑批量发送或者在设备处于充电状态等合适时机发送。同时,在处理推送消息时,尽量减少长时间运行的任务,避免过度占用系统资源。例如,在 MyFirebaseMessagingService 中,如果接收到数据消息需要进行一些数据处理,可以使用 WorkManager 等工具在后台异步处理,并且设置合适的约束条件,如在设备充电、有网络连接等情况下执行任务。

常见问题及解决方法

  1. 无法接收推送通知
    • 检查网络连接:确保设备已连接到网络,并且没有网络限制阻止 FCM 服务器与设备通信。
    • 检查权限:确认应用已正确声明所需的权限,如 INTERNET 权限。
    • 检查 Firebase 配置:检查 google - services.json 文件是否正确配置并放置在 app 目录下,同时检查应用级和项目级的 build.gradle 文件中 Firebase 依赖是否正确添加。
    • 检查 FCM 令牌:确保设备获取到了正确的 FCM 令牌,可以在 MyFirebaseInstanceIdServiceonTokenRefresh 方法中打印令牌进行检查。如果令牌不正确,可能导致无法接收推送。
  2. 推送通知显示异常
    • 检查通知渠道设置:在 Android 8.0 及以上,通知渠道的设置非常重要。确保在 MyFirebaseMessagingService 中正确创建了通知渠道,并且设置了合适的重要性级别。
    • 检查自定义布局:如果使用了自定义通知布局,确保布局文件正确无误,并且在 NotificationCompat.Builder 中正确设置了自定义布局。
  3. 服务器发送推送失败
    • 检查服务器密钥:确保在服务器端使用的 Firebase 服务器密钥正确,该密钥可以在 Firebase 控制台的“项目设置” - “云消息传递”中获取。
    • 检查 API 调用:确认使用的 FCM HTTP v1 API 的调用格式正确,包括请求 URL、请求方法、请求头和请求体等。可以参考 FCM 官方文档进行检查。

通过以上步骤和内容,您可以在 Kotlin 项目中成功集成 Firebase Cloud Messaging 实现推送通知功能,并根据实际需求进行扩展和优化。在开发过程中,要密切关注官方文档的更新,以确保应用能够充分利用 FCM 的最新特性和功能。