此 Codelab 是“使用 Kotlin 进行高级 Android 开发”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘课程的价值,但并不强制要求这样做。“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页列出了所有课程 Codelab。
简介
在上一个 Codelab 中,您为鸡蛋计时器添加了在应用内创建和触发的通知。通知的另一个重要使用情形是远程发送推送通知,即使应用未运行,用户也可以收到这些通知。
什么是推送通知?
推送通知是指服务器“推送”到移动设备的通知。无论您的应用是否正在运行,它们都可以传递到设备。
推送通知是告知用户有关更新或提醒用户有关任务或功能的绝佳方式。想象一下,您正在等待某款商品再次上架。借助推送通知,购物应用可以告知您商品目录更新,而无需您每天查看商品目录状态。
推送通知采用发布/订阅模式,可让后端应用向感兴趣的客户端推送相关内容。如果没有发布/订阅模型,应用的用户就需要定期检查应用是否有更新。对于用户来说,这个过程既繁琐又不可靠。此外,随着客户端数量的增加,这些定期检查会给应用服务器和用户设备的网络和处理资源带来过大的负荷。
与所有其他类型的通知一样,请确保您通过推送通知尊重用户。如果通知内容对用户来说不有趣或不及时,他们可以轻松关闭来自您应用的所有通知。
什么是 Firebase Cloud Messaging?
Firebase 云消息传递是 Firebase 移动开发平台的一部分。通常,您需要从头开始设置一个可以与移动设备通信以触发通知的服务器。借助 Firebase Cloud Messaging,您可以向所有已安装应用的用户或部分用户发送通知,而无需设置服务器。例如,您可以向用户发送提醒或提供特别促销优惠(例如免费礼物)。您可以远程向单个设备或多个设备推送通知。
您还可以使用 Firebase Cloud Message 将数据从后端应用或 Firebase 项目传输给用户。
在此 Codelab 中,您将学习如何使用 Firebase Cloud Messaging 为 Android 应用发送推送通知,以及如何发送数据。
如果在此 Codelab 操作期间遇到任何问题(代码错误、语法错误、措辞含义不明等),都可以通过 Codelab 左下角的报告错误链接报告相应问题。
您应当已掌握的内容
您应熟悉以下内容:
- 如何使用 Kotlin 创建 Android 应用。尤其是使用 Android SDK。
- 如何使用架构组件和数据绑定来设计应用。
- 对广播接收器有基本的了解。
- 对 AlarmManager 有基本的了解。
- 如何使用 NotificationManager 创建和发送通知。
学习内容
- 如何通过 Firebase Cloud Messaging 向用户推送消息。
- 如何使用数据消息(Firebase Cloud Messaging 的一部分)从后端向应用发送数据。
您将执行的操作
- 向 starter 应用添加推送通知。
- 在应用运行时处理 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 中打开。
第 1 步:创建 Firebase 项目
您必须先创建一个 Firebase 项目,并将其关联到您的 Android 应用,然后才能将 Firebase 添加到您的 Android 应用。
- 登录 Firebase 控制台。
- 点击添加项目,然后选择或输入项目名称。将项目命名为 fcm-codelab。
- 点击继续。
- 您可以关闭为此项目启用 Google Analytics 按钮,跳过设置 Google Analytics 的步骤。
- 点击创建项目以完成 Firebase 项目的设置。
第 2 步:在 Firebase 中注册您的应用
现在,您已经创建了 Firebase 项目,接下来就可以向其中添加 Android 应用了。
- 在 Firebase 控制台的项目概览页面的中心位置,点击 Android 图标以启动设置工作流。
- 在 Android 软件包名称字段中,输入
com.example.android.eggtimernotifications
。 - 点击注册应用。
重要提示: 请务必为应用输入正确的 ID,因为在向 Firebase 项目注册应用后,您将无法添加或修改此值。
第 3 步:将 Firebase 配置文件添加到项目中
将 Firebase Android 配置文件添加到您的应用。
- 点击下载 google-services.json 以获取 Firebase Android 配置文件 (
google-services.json
)。请确保该配置文件未附加其他字符,并且名称完全为google-services.json
。 - 将配置文件移到应用的模块(应用级)目录中。
第 4 步:配置 Android 项目以启用 Firebase 产品
如需在应用中启用 Firebase 产品,您必须将 google-services 插件添加到 Gradle 文件中。
- 在根级(项目级)Gradle 文件 (
build.gradle
) 中,检查您是否拥有 Google 的 Maven 制品库。 - 然后,添加规则以包含 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
// ...
}
}
- 在您的模块(应用级)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 控制台网站中撰写和发送消息。
- 打开
MyFirebaseMessagingService.kt
- 检查该文件,尤其是以下函数:
onNewToken()
- 如果您的服务已在 Android 清单中注册,则会自动调用。当您首次运行应用时,以及每次 Firebase 为您的应用发布新令牌时,系统都会调用此函数。令牌是 Firebase 后端项目的访问密钥。它是为您的特定客户端设备生成的。有了此令牌,Firebase 便知道后端应将消息发送给哪个客户端。Firebase 还会知道相应客户端是否有效,以及是否有权访问此 Firebase 项目。onMessageReceived
- 当应用正在运行且 Firebase 向应用发送消息时调用。此函数会接收一个RemoteMessage
对象,该对象可以携带通知或数据消息载荷。本 Codelab 后面会详细介绍通知和数据消息载荷之间的区别。
第 1 步:向单个设备发送 FCM 通知
借助通知控制台,您可以测试发送通知。如需使用控制台向特定设备发送消息,您需要知道该设备的注册令牌。
当 Firebase 后端生成新的或刷新后的令牌时,系统会调用 onNewToken()
函数,并将新令牌作为实参传入。如果您希望指定单一目标设备或者创建设备组以向其发送广播消息,需要扩展 FirebaseMessagingService
并重写 onNewToken()
来获取此令牌。
- 打开
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 推送通知。
- 打开
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)
)
- 打开
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]
- 运行鸡蛋计时器应用。
- 观察 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。
- 打开 Firebase 控制台,然后选择您的项目。
- 接下来,从左侧导航栏中选择 Cloud Messaging。
- 点击发送您的第一条消息。
- 输入
Time for Breakfast!
作为通知标题,输入Don't forget to eat eggs!
作为通知文本,然后选择发送测试消息。系统随即会显示一个弹出式对话框,其中包含在设备上测试,并要求您提供 FCM 注册令牌。
- 从 Logcat 中复制应用令牌。
- 将此令牌粘贴到弹出式窗口中的添加 FCM 注册令牌字段中,然后点击令牌旁边的添加按钮。
- 在随即显示的复选框列表中,选择相应令牌。测试按钮应变为启用状态。
- 在设备上,将“鸡蛋计时器”应用置于后台 。
- 在弹出式窗口中,点击测试。
- 在您点击测试后,目标客户端设备(应用在其后台运行)应该会在系统通知栏中收到通知。(稍后您将详细了解如何在应用位于前台时处理 FCM 消息。)
任务:向主题发送 FCM 通知
FCM 主题消息传递功能基于发布/订阅模式。
即时通讯应用是发布/订阅模式的一个很好的示例。假设某个应用每 10 秒检查一次新消息。这不仅会消耗手机电池电量,还会使用不必要的网络资源,并对应用的服务器造成不必要的负载。相反,客户端设备可以订阅,并在通过您的应用传送新消息时收到通知。
借助主题,您可以将消息发送至已经选择订阅特定主题的多台设备。对于客户端,主题是客户端感兴趣的特定数据源。对于服务器,主题是指选择接收特定数据源更新的一组设备。主题可用于表示通知类别,例如新闻、天气预报和体育赛事结果。在此 Codelab 的这一部分中,您将创建一个“早餐”主题,以提醒感兴趣的应用用户在早餐时吃鸡蛋。
如需订阅某个主题,客户端应用需使用主题名称 breakfast
调用 Firebase 云消息传递的 subscribeToTopic(
)
函数。此调用可能会产生两种结果。如果调用方成功,系统将使用订阅的消息调用 OnCompleteListener
回调。如果客户端订阅失败,回调将收到错误消息。
在您的应用中,您将自动让用户订阅“早餐”主题。不过,在大多数生产应用中,最好让用户控制订阅哪些主题。
- 打开
EggTimerFragment.kt
并找到空的subscribeTopic()
函数。 - 获取
FirebaseMessaging
的实例,并使用主题名称调用subscibeToTopic()
函数。 - 添加
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]
}
- 在应用启动时调用
subscribeTopic()
函数以订阅主题。向上滚动至onCreateView()
并添加对subscribeTopic()
的调用。
// EggTimerFragment.kt
// TODO: Step 3.4 call subscribe topics on start
subscribeTopic()
return binding.root
- 如需订阅早餐主题,请再次运行应用。您应该会看到一条显示“已订阅主题”的消息框。
现在,您可以测试向主题发送消息:
- 打开通知编辑器,然后选择撰写通知。
- 像之前一样设置通知的通知标题和通知文本。
- 这次,我们不向单个设备发送消息,而是点击目标下的主题,然后输入
breakfast
作为消息主题。
- 选择立即安排。
- 确保应用在测试设备上于后台运行。
- 点击检查,然后点击发布。如果您可以在多部设备上运行该应用,则可以测试并观察到订阅了相应主题的所有设备都收到了通知。
该应用现在有以下通知渠道:鸡蛋和早餐。在客户端设备上,长按应用图标,选择信息,然后点击通知。您应该会看到 Egg 和 Breakfast 通知渠道,如以下屏幕截图所示。如果您取消选择 Breakfast 频道,您的应用将不会收到通过此频道发送的任何通知。
使用通知时,请务必记住,用户可以随时关闭任何通知渠道。
第 1 步:数据消息
FCM 消息还可以包含在客户端应用中处理消息的数据载荷,使用数据消息而不是通知消息。
为了处理数据消息,您需要在 MyFirebaseMessagingService
的 onMessageReceived()
函数中处理数据载荷。载荷存储在 remoteMessage
对象的 data
属性中。remoteMessage
对象和 data
属性都可以是 null
。
- 打开“
MyFirebaseMessagingService.
” - 检查
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]
如需测试代码,您可以再次使用通知撰写器。
- 打开通知编辑器,创建一条新消息,并将其目标 设置为“早餐”主题。
- 这次,当您到达第 4 步其他选项时,请按如下方式设置自定义数据键和值属性:
- 键:
eggs
- 值:
3
- 确保您的应用在前台运行。如果应用在后台运行,FCM 消息将触发自动通知,并且当用户点击通知时,
onMessageReceived()
函数将仅接收remoteMessage
对象。 - 从 Notifications Composer 发送消息,并观察 logcat 中显示的数据消息日志。
第 2 步:在前台和后台处理消息
当运行您的应用的客户端设备收到同时包含通知载荷和数据载荷的消息时,应用的行为取决于您的应用在该设备上是在后台还是前台运行:
- 如果应用在后台运行,并且消息包含通知载荷,则通知会自动显示在通知面板中。如果消息还包含数据载荷,那么当用户点按通知时,应用将处理该数据载荷。
- 如果应用在前台运行,并且消息通知具有通知载荷,则通知不会自动显示。应用需要在
onMessageReceived()
函数中决定如何处理通知。如果消息还包含数据载荷,应用将处理这两个载荷。
在此 Codelab 中,您希望提醒应用用户吃鸡蛋作为早餐。您不打算发送任何数据,但您也希望确保无论应用是在前台还是后台运行,提醒通知始终会显示。
当您向安装了计时器应用的设备发送 FCM 消息时,如果该应用未运行或在后台运行,系统会自动显示通知消息。不过,如果应用在前台运行,则不会自动显示通知;而是由应用的代码决定如何处理消息。如果应用在前台运行时收到 FCM 消息,系统会自动使用该 FCM 消息触发 onMessageReceived()
函数。在此处,应用可以静默处理通知和数据载荷,或触发通知。
对于您的应用,您需要确保用户在应用位于前台时收到提醒,因此我们来实现一些代码来触发通知:
- 再次打开
MyFirebaseMessagingService
中的onMessageReceived()
函数。 - 在您最近添加的用于检查数据消息的代码之后,立即添加以下代码,该代码使用通知框架发送通知。
// 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)
}
- 如果您再次运行应用并使用通知撰写器发送通知,则无论应用是在前台还是后台运行,您都应该会看到与 Codelab 第一部分中看到的通知一样的通知。
解决方案代码位于所下载代码的 master 分支中。
- 通过扩展
FirebaseMessagingService
实现 FCM BroadcastReceiver。 - 设置 Firebase 云消息传递 (FCM) 项目,并将 FCM 添加到您的 Android 应用。
- 通过从 Notifications Composer 发送推送通知来测试应用。
- 通过调用
FirebaseMessaging
类的subscribeToTopic()
函数,订阅 FCM 主题。 - 使用
RemoteMessage
对象发送数据载荷。 - 在
onMessageReceived()
函数中处理数据。 - 添加逻辑以在应用位于前台和后台时处理 FCM。
Udacity 课程:
Firebase 文档:
如需本课程中其他 Codelab 的链接,请参阅“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页。