使用 Kotlin 进行高级 Android 开发 01.2:Android Firebase Cloud Messaging

此 Codelab 是“使用 Kotlin 进行高级 Android 开发”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘课程的价值,但并不强制要求这样做。“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页列出了所有课程 Codelab。

简介

上一个 Codelab 中,您向卵子计时器添加了在应用中创建和触发的通知。通知的另一个重要用例是远程发送推送通知,即便您的应用未运行,这些通知也可以接收。

什么是推送通知?

推送通知是服务器“推送”到移动设备的通知。无论您的应用是否正在运行,它们都可以传送到设备。

推送通知是让用户了解更新或提醒他们任务或功能的绝佳方式。想象一下,当某件商品再次有货时,借助推送通知,购物应用可让您了解股票动态,而无需每天查看股票状态。

推送通知使用发布/订阅模式,以允许后端应用将相关内容推送给感兴趣的客户端。如果没有发布/订阅模式,您的应用的用户需要定期检查应用中的更新。此过程对于用户来说是繁琐而又不可靠的。此外,随着客户端数量的增加,这些定期检查将会给网络和服务器资源(包括应用服务器和用户设备)带来过大的负载。

与所有其他类型的通知一样,务必通过推送通知来尊重用户。如果用户对通知内容不感兴趣或及时看到通知,他们可以轻松关闭来自您的应用的所有通知。

什么是 Firebase Cloud Messaging?

Firebase Cloud Messaging 是 Firebase 移动应用开发的一部分。通常情况下,您需要从头开始设置可与移动设备通信的服务器,以触发通知。借助 Firebase Cloud Messaging,您可以向所有已安装应用用户或其子集用户发送通知,而无需设置服务器。例如,您可以向用户发送提醒或给予特殊促销,如赠品。您可以远程向一台或多台设备发送通知。

您还可以使用 Firebase Cloud Messaging 将数据从后端应用或 Firebase 项目转移给用户。

在此 Codelab 中,您将学习如何使用 Firebase Cloud Messaging 为 Android 应用发送推送通知,以及如何发送数据。

如果在此 Codelab 操作期间遇到任何问题(代码错误、语法错误、措辞含义不明等),都可以通过 Codelab 左下角的报告错误链接报告相应问题。

您应当已掌握的内容

您应熟悉以下内容/操作:

  • 如何使用 Kotlin 创建 Android 应用?特别是,应使用 Android SDK。
  • 如何使用架构组件和数据绑定来设计应用?
  • 对广播接收器有基本的了解。
  • 对 AlarmManager 有基本的了解。
  • 如何使用 NotificationManager 创建和发送通知。

学习内容

  • 如何通过 Firebase Cloud Messaging 向用户推送消息。
  • 如何使用数据消息(Firebase Cloud Messaging 的一部分)从后端向您的应用发送数据。

您将执行的操作

  • 向起始应用添加推送通知。
  • 在应用运行时处理 Firebase Cloud Messaging。
  • 使用 Firebase Cloud Messaging 转移数据。

在此 Codelab 中,您将处理之前在 Android 应用中使用通知 Codelab 中的代码。在上一个 Codelab 中,您构建了一个鸡蛋计时器应用,该应用可在烹饪计时器到时发送通知。在此 Codelab 中,您将添加 Firebase Cloud Messaging,以向您的应用用户发送推送通知以提醒他们吃鸡蛋。

如需获取示例应用,您可以执行以下任一操作:

从 GitHub 克隆代码库并切换到 starter 分支:

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications-fcm


或者,您也可以下载 Zip 文件并将其解压缩,然后在 Android Studio 中打开代码库。

下载 Zip 文件

第 1 步:创建 Firebase 项目

您需要先创建一个要关联到 Android 应用的 Firebase 项目,然后才能将 Firebase 添加到您的 Android 应用。

  1. 登录 Firebase 控制台
  2. 点击添加项目,然后选择或输入项目名称。将项目命名为 fcm-codelab
  3. 点击继续
  4. 通过关闭为此项目启用 Google Analytics(分析)按钮,您可以跳过设置 Google Analytics(分析)。
  5. 点击创建项目以完成 Firebase 项目的设置。

第 2 步:在 Firebase 中注册您的应用

现在您已经拥有一个 Firebase 项目,接下来就可以将您的 Android 应用添加到其中了。

  1. Firebase 控制台的项目概览页面的中心位置,点击 Android 图标以启动设置工作流。

  1. Android 软件包名称字段中,输入 com.example.android.eggtimernotifications
  2. 点击注册应用

重要提示:请确保为您的应用输入正确的 ID,因为在 Firebase 项目中注册您的应用后,您将无法添加或修改此值。

第 3 步:将 Firebase 配置文件添加到您的项目

将 Firebase Android 配置文件添加到您的应用。

  1. 点击下载 google-services.json 以获取 Firebase Android 配置文件 (google-services.json)。请确保该配置文件名未附加其他字符,且名称完全为 google-services.json
  2. 将配置文件移动到应用的模块(应用级)目录中。

第 4 步:配置 Android 项目以启用 Firebase 产品

要在您的应用中启用 Firebase 产品,您必须将 google-services 插件添加到 Gradle 文件中。

  1. 在根级(项目级)Gradle 文件 (build.gradle) 中,确认您有 Google' 的 Maven 代码库。
  2. 然后,添加规则以包含 Google 服务插件。

build.gradle

buildscript {

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
  }

  dependencies {
    // ...

    // Add the following line:
    classpath 'com.google.gms:google-services:4.3.2'  // Google Services plugin
  }
}

allprojects {
  // ...

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}
  1. 在您的模块(应用级)Gradle 文件(通常是 app/build.gradle)中添加一行代码,将插件应用到文件底部。

app/build.gradle

apply plugin: 'com.android.application'

android {
  // ...
}

// Add the following line to the bottom of the file:
apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin

在此任务中,您将向项目中添加 Firebase Cloud Messaging (FCM) 以使用推送通知。

此 Codelab 中适用于 FCM 的 Android 服务代码在 MyFirebaseMessagingService.kt 中给出。在以下步骤中,您将向 Android 应用添加代码。

您将使用 Notifications Composer 测试您的实现代码。Notifications Composer 是一款工具,可帮助您从 Firebase 控制台网站撰写和发送消息。

  1. 打开 MyFirebaseMessagingService.kt
  2. 检查该文件,尤其是以下函数:
  • onNewToken() - 如果您的服务已在 Android 清单中注册,则自动调用。此函数会在您首次运行应用时以及 Firebase 每次为您的应用颁发新的令牌时调用。令牌是 Firebase 后端项目的访问令牌。这是为您的特定客户端设备生成的。借助此令牌,Firebase 知道后端应向哪个客户端发送消息。Firebase 也知道此客户端是否有效以及是否可以访问此 Firebase 项目。
  • onMessageReceived - 当您的应用正在运行且 Firebase 向您的应用发送消息时调用。此函数接收 RemoteMessage 对象,该对象可包含通知或数据消息载荷。本 Codelab 后面会详细介绍通知和数据消息载荷的区别。

第 1 步:将 FCM 通知发送到单台设备

您可以利用 Notifications 控制台测试发送通知。如需使用控制台向特定设备发送消息,您需要知道该设备的注册令牌。

当 Firebase 后端生成新令牌或刷新令牌时,系统将调用 onNewToken() 函数,并以参数形式传递新令牌。如果您想要定位单个设备,或创建要向其发送广播消息的一组设备,则需要通过扩展 FirebaseMessagingService 并替换 onNewToken() 来获取此令牌。

  1. 打开 AndroidManifest.xml 并取消以下代码,以启用鸡蛋计时器应用的 MyFirebaseMessagingService。Android 清单中的服务元数据会将 MyFirebaseMessagingService 注册为服务并添加 Intent 过滤器,以便此服务将接收来自 FCM 的消息。元数据的最后一部分将 breakfast_notification_channel_id 声明为 Firebase 的 default_notification_channel_id。您将在下一步中使用此 ID。
<!-- AndroidManifest.xml -->
<!-- TODO: Step 3.0 uncomment to start the service  -->

        <service
                android:name=".MyFirebaseMessagingService"
                android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
        <!-- [START fcm_default_icon] -->
        <!--
 Set custom default icon. This is used when no icon is set for incoming notification messages.
             See README(https://goo.gl/l4GJaQ) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_icon"
                android:resource="@drawable/common_google_signin_btn_icon_dark"/>
        <!--
 Set color used with incoming notification messages. This is used when no color is set for the incoming
             notification message. See README(https://goo.gl/6BKBk7) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_color"
                android:resource="@color/colorAccent"/> <!-- [END fcm_default_icon] -->
        <!-- [START fcm_default_channel] -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/breakfast_notification_channel_id" />
        <!-- [END fcm_default_channel] -->

最好为 FCM 创建新的通知渠道,因为您的用户可能需要单独启用/停用鸡蛋计时器或 FCM 推送通知。

  1. 打开 ui/EggTimerFragment.kt。在 onCreateView() 中,添加以下频道创建代码。
// EggTimerFragment.kt

   // TODO: Step 3.1 create a new channel for FCM
    createChannel(
        getString(R.string.breakfast_notification_channel_id),
        getString(R.string.breakfast_notification_channel_name)
    )
  1. 打开 MyFirebaseMessagingService.kt 并取消注释 onNewToken() 函数。系统会在生成新令牌时调用此函数。
// MyFirebaseMessagingService.kt

   // TODO: Step 3.2 log registration token
    // [START on_new_token]
    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the     
     * InstanceID token is initially generated so this is where you would retrieve     
     * the token.
     */
    override fun onNewToken(token: String?) {
        Log.d(TAG, "Refreshed token: $token")

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // Instance ID token to your app server.
        sendRegistrationToServer(token)
    }
    // [END on_new_token]
  1. 运行鸡蛋计时器应用。
  2. 观察 logcat(View > Tool Windows > Logcat)。您应该会看到类似于如下所示的令牌日志行。这是向此设备发送消息所需的令牌。只有在创建新令牌时才会调用此函数。
2019-07-23 13:09:15.243 2312-2459/com.example.android.eggtimernotifications D/MyFirebaseMsgService: Refreshed token: f2esflBoQbI:APA91bFMzNNFaIskjr6KIV4zKjnPA4hxekmrtbrtba2aDbh593WQnm11ed54Mv6MZ9Yeerver7pzgwfKx7R9BHFffLBItLEgPvrtF0TtX9ToCrXZ5y7Hd-m

注意:如果您在 logcat 消息中没有看到令牌,则表示您的应用可能之前已收到该令牌。在这种情况下,卸载应用可帮助您接收新令牌。

现在,您可以通过发送通知进行测试。为了发送通知,您需要使用 Notifications Composer

  1. 打开 Firebase 控制台,选择您的项目。
  2. 接下来,从左侧导航栏中选择云消息传递
  3. 点击发送您的第一条消息

  1. 输入Time for Breakfast!作为通知标题,Don't forget to eat eggs!作为通知文本,然后选择发送测试消息。系统会显示在设备上测试弹出式对话框,要求您提供 FCM 注册令牌。

  1. 从 logcat 复制您的应用令牌。

  1. 将此令牌粘贴到弹出式窗口中的添加 FCM 注册令牌字段,然后点击该令牌旁边的添加按钮。
  2. 在出现的复选框列表中,选择相应令牌。测试按钮应处于启用状态。

  1. 将鸡蛋计时器应用置于后台中。
  2. 在弹出式窗口中,点击测试
  1. 点击测试后,在后台运行您的应用的目标客户端设备应该会在系统通知栏中接收到通知。(稍后,当应用在前台运行时,您会了解更多关于处理 FCM 消息的信息。)

任务:将 FCM 通知发送到主题

FCM 主题消息传递基于发布/订阅模式。

即时通讯应用就是一个发布/订阅模型的优秀示例。假设应用每 10 秒检查一次新消息。这样不仅会耗尽手机电池,还会使用不必要的网络资源,还会给应用服务器造成不必要的负载。而客户端设备可以订阅您的应用,并在应用有新的消息时收到通知。

您可以使用主题向已启用该特定主题的多台设备发送消息。对客户来说,主题是指客户感兴趣的特定数据源。对于服务器,主题是指已选择在特定数据源上接收更新的设备组。主题可用于显示通知类别,例如新闻、天气预报和体育赛事结果。在此 Codelab 的这一部分中,您将创建一个“早餐”主题,以提醒感兴趣的应用用户使用早餐吃鸡蛋。

为了订阅某个主题,客户端应用需要使用主题名称 breakfast 调用 Firebase Cloud Messaging 的 subscribeToTopic() 函数。本次通话可能会有两个结果。如果调用方成功,系统将使用订阅的消息调用 OnCompleteListener 回调。如果客户端未能订阅,回调将收到一条错误消息。

在您的应用中,系统会自动为您的用户订阅早餐主题。不过,在大多数正式版应用中,最好让用户控制订阅哪些主题。

  1. 打开 EggTimerFragment.kt 并找到空的 subscribeTopic() 函数。
  2. 获取 FirebaseMessaging 的实例并调用带有主题名称的 subscibeToTopic() 函数。
  3. 添加 addOnCompleteListener 以接收来自 FCM 的通知,告知您订阅是否成功。
// EggTimerFragment.kt

   // TODO: Step 3.3 subscribe to breakfast topic
    private fun subscribeTopic() {
        // [START subscribe_topics]
        FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
            .addOnCompleteListener { task ->
                var msg = getString(R.string.message_subscribed)
                if (!task.isSuccessful) {
                    msg = getString(R.string.message_subscribe_failed)
                }
                Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
            }
        // [END subscribe_topics]
    }
  1. 在应用启动时调用 subscribeTopic() 函数来订阅某个主题。向上滚动到 onCreateView(),然后添加对 subscribeTopic() 的调用。
// EggTimerFragment.kt

   // TODO: Step 3.4 call subscribe topics on start
    subscribeTopic()

    return binding.root
  1. 如需订阅早餐主题,请再次运行应用。您应该会看到一条提示消息“已订阅主题”的消息框消息。

现在,您可以测试向主题发送消息:

  1. 打开 Notifications Composer,然后选择 Compose Notification
  2. 像以前一样设置通知通知标题通知文本
  3. 这次,不要点击 Target 下的 Topic,而是输入 breakfast 作为消息主题,而不是向单个设备发送消息。

  1. 为时间安排选择现在

  1. 确保您的应用在测试设备上在后台运行。
  1. 点击审核,然后点击发布。如果您可以在多部设备上运行该应用,则可以测试并观察到订阅此主题的所有设备上是否收到了通知。

该应用目前提供以下渠道的通知:鸡蛋早餐。在客户端设备上,长按应用图标,选择信息,然后点击通知。您应该会看到 EggBreakFast 通知渠道,如以下屏幕截图所示。如果您取消选中 早餐频道,您的应用将不会接收通过此频道发送的任何通知。

使用通知时,请始终谨记,用户可以随时关闭任何通知渠道。

第 1 步:数据消息

FCM 消息还可以包含一个数据载荷,用于在客户端应用中处理消息(使用数据消息而不是通知消息)。

为了处理数据消息,您需要在 MyFirebaseMessagingServiceonMessageReceived() 函数中处理数据载荷。载荷存储在 remoteMessage 对象的 data 属性中。remoteMessage 对象和 data 属性都可以为 null

  1. 打开 MyFirebaseMessagingService.
  2. 检查 remoteMessage 对象的 data 属性是否具有一些值,并将数据输出到日志中。
// MyFirebaseMessagingService.kt

    // [START receive_message]
    override fun onMessageReceived(remoteMessage: RemoteMessage?) {
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Log.d(TAG, "From: ${remoteMessage?.from}")
        
       // TODO: Step 3.5 check messages for data
        // Check if the message contains a data payload.
        remoteMessage?.data?.let {
            Log.d(TAG, "Message data payload: " + remoteMessage.data)
        }

    }
    // [END receive_message]

如需测试代码,您可以再次使用 Notifications Composer

  1. 打开 Notification Composer,创建新消息,将其 Target 设置为“breakfast”主题。
  2. 这次,当您转到第 4 步(其他选项)时,请按如下所示设置自定义数据键和值的属性:
  1. eggs
  2. 3

  1. 确保您的应用在前台运行。如果您的应用在后台运行,当用户点击通知时,FCM 消息会触发自动通知,而 onMessageReceived() 函数仅会收到 remoteMessage 对象。
  2. 从 Notifications Composer 发送消息,并观察 logcat 中显示的数据消息日志。

第 2 步:在前台和后台处理消息

当运行您的应用的客户端设备收到的消息同时包含通知和数据载荷时,该应用的行为取决于该应用是在后台还是在该设备的前台运行:

  • 如果应用在后台运行,如果消息具有通知载荷,该通知会自动显示在通知栏中。如果消息还包含数据载荷,那么当用户点按通知时,应用将处理数据载荷。
  • 如果应用在前台运行,如果消息通知具有通知载荷,该通知不会自动显示。应用需要确定如何处理 onMessageReceived() 函数中的通知。如果消息还包含数据载荷,则应用会同时处理这两种载荷。

在此 Codelab 中,您需要提醒应用用户吃一些鸡蛋做早餐。您不打算发送任何数据,但您也希望确保始终显示提醒通知,无论应用是在前台还是后台运行。

当您向安装过鸡蛋计时器应用的设备发送 FCM 消息时,如果该应用未运行或在后台运行,系统会自动显示通知消息。不过,如果应用在前台运行,系统不会自动显示通知,而是由代码决定如何处理消息。如果应用在收到 FCM 消息时在前台运行,onMessageReceived() 功能将随 FCM 消息自动触发。应用可以在此处静默处理通知和数据载荷或触发通知。

对于您的应用,您需要确保应用在前台运行时收到提醒,因此让我们实现一些代码来触发通知:

  1. MyFirebaseMessagingService 中再次打开 onMessageReceived() 函数。
  2. 在您最近添加用来检查数据消息的代码后,添加以下代码,以便使用通知框架发送通知。
// MyFirebaseMessagingService.kt

    // TODO: Step 3.6 check messages for notification and call sendNotification
    // Check if the message contains a notification payload.
    remoteMessage.notification?.let {
        Log.d(TAG, "Message Notification Body: ${it.body}")
        sendNotification(it.body as String)
    }
  1. 如果您再次运行该应用并使用通知编辑器发送通知,则无论应用是处于前台还是后台,您都应该会看到像在 Codelab 的第一部分中看到的通知一样。

解决方案代码位于已下载代码的主分支中。

Udacity 课程:

Firebase 文档:

如需本课程中其他 Codelab 的链接,请参阅“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页