根据设备的状态,传入消息的处理方式不同。要了解这些场景以及如何将 FCM 集成到您自己的应用程序中,首先要确定设备可能处于的各种状态:
状态 | 描述 |
---|---|
前景 | 当应用程序打开、处于查看和使用状态时。 |
背景 | 当应用程序打开但在后台(最小化)时。这通常发生在用户按下设备上的“主页”按钮、使用应用程序切换器切换到另一个应用程序或在不同选项卡(Web)中打开应用程序时。 |
终止 | 当设备被锁定或应用程序未运行时。 |
在应用程序可以通过 FCM 接收消息有效负载之前,必须满足一些先决条件:
- 该应用程序必须至少打开过一次(以允许在 FCM 注册)。
- 在 iOS 上,如果用户将应用程序从应用程序切换器上滑开,则必须手动重新打开它,背景消息才能再次开始工作。
- 在 Android 上,如果用户从设备设置中强制退出应用程序,则必须手动重新打开它才能使消息开始工作。
- 在 Web 上,您必须使用 Web 推送证书请求令牌(使用
getToken()
)。
请求接收消息的权限(Apple 和 Web)
在 iOS、macOS 和 Web 上,在您的设备上接收 FCM 负载之前,您必须先征求用户的许可。
firebase_messaging
包提供了一个简单的 API,用于通过requestPermission
方法请求权限。此 API 接受许多命名参数,这些参数定义了您想要请求的权限类型,例如包含通知有效负载的消息传递是否可以触发声音或通过 Siri 读出消息。默认情况下,该方法请求合理的默认权限。参考 API 提供了关于每个权限的完整文档。
首先,从您的应用程序调用该方法(在 iOS 上将显示本机模式,在 Web 上将触发浏览器的本机 API 流):
FirebaseMessaging messaging = FirebaseMessaging.instance;
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
print('User granted permission: ${settings.authorizationStatus}');
从请求返回的NotificationSettings
对象的authorizationStatus
属性可用于确定用户的总体决策:
-
authorized
:用户授予权限。 -
denied
:用户拒绝权限。 -
notDetermined
:用户尚未选择是否授予权限。 -
provisional
:用户授予临时权限
NotificationSettings
上的其他属性返回当前设备是否启用、禁用或不支持特定权限。
授予权限并了解不同类型的设备状态后,您的应用程序现在可以开始处理传入的 FCM 负载。
消息处理
根据应用程序的当前状态,不同消息类型的传入有效负载需要不同的实现来处理它们:
前台消息
要在您的应用程序位于前台时处理消息,请收听onMessage
流。
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
if (message.notification != null) {
print('Message also contained a notification: ${message.notification}');
}
});
该流包含一个RemoteMessage
,详细说明有关负载的各种信息,例如它来自哪里、唯一 ID、发送时间、它是否包含通知等等。由于消息是在您的应用程序处于前台时检索到的,因此您可以直接访问 Flutter 应用程序的状态和上下文。
前台和通知消息
在应用程序处于前台时到达的通知消息默认情况下不会在 Android 和 iOS 上显示可见通知。但是,可以覆盖此行为:
- 在 Android 上,您必须创建一个“高优先级”通知通道。
- 在 iOS 上,您可以更新应用程序的显示选项。
后台消息
处理后台消息的过程在本地(Android 和 Apple)和基于 Web 的平台上是不同的。
苹果平台和安卓
通过注册onBackgroundMessage
处理程序来处理后台消息。当收到消息时,会生成一个隔离(仅限 Android,iOS/macOS 不需要单独的隔离),即使您的应用程序未运行,您也可以处理消息。
关于后台消息处理程序,需要牢记以下几点:
- 它不能是匿名函数。
- 它必须是顶级函数(例如,不是需要初始化的类方法)。
- 它必须在函数声明的正上方用
@pragma('vm:entry-point')
注释(否则它可能会在发布模式的摇树优化过程中被删除)。
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp();
print("Handling a background message: ${message.messageId}");
}
void main() {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
由于处理程序在您的应用程序上下文之外独立运行,因此无法更新应用程序状态或执行任何影响 UI 的逻辑。但是,您可以执行 HTTP 请求等逻辑、执行 IO 操作(例如更新本地存储)、与其他插件通信等。
还建议尽快完成您的逻辑。运行长时间、密集的任务会影响设备性能,并可能导致操作系统终止进程。如果任务运行时间超过 30 秒,设备可能会自动终止进程。
网络
在 Web 上,编写一个在后台运行的 JavaScript Service Worker 。使用 service worker 来处理后台消息。
首先,在您的web
目录中创建一个新文件,并将其firebase-messaging-sw.js
:
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js");
firebase.initializeApp({
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "...",
});
const messaging = firebase.messaging();
// Optional:
messaging.onBackgroundMessage((message) => {
console.log("onBackgroundMessage", message);
});
该文件必须导入应用程序和消息传递 SDK,初始化 Firebase 并公开messaging
变量。
接下来,必须注册工人。在入口文件中,加载main.dart.js
文件后,注册您的 worker:
<html>
<body>
...
<script src="main.dart.js" type="application/javascript"></script>
<script>
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// ADD THIS LINE
navigator.serviceWorker.register('/firebase-messaging-sw.js');
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
// ...
});
}
</script>
接下来重新启动您的 Flutter 应用程序。工作人员将被注册,任何后台消息都将通过此文件处理。
处理交互
由于通知是一种可见的提示,因此用户通常会与它们进行交互(通过按下)。 Android 和 iOS 上的默认行为是打开应用程序。如果应用程序被终止,它将被启动;如果它在后台,它将被带到前台。
根据通知的内容,您可能希望在应用程序打开时处理用户的交互。例如,如果一条新的聊天消息通过通知发送并且用户按下它,您可能希望在应用程序打开时打开特定的对话。
firebase-messaging
包提供了两种处理这种交互的方法:
-
getInitialMessage()
:如果应用程序从终止状态打开,将返回包含RemoteMessage
的Future
。一旦消费,RemoteMessage
将被删除。 -
onMessageOpenedApp
:当应用程序从后台状态打开时发布RemoteMessage
的Stream
。
建议处理这两种情况,以确保为您的用户提供流畅的用户体验。下面的代码示例概述了如何实现这一点:
class Application extends StatefulWidget {
@override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
// It is assumed that all messages contain a data field with the key 'type'
Future<void> setupInteractedMessage() async {
// Get any messages which caused the application to open from
// a terminated state.
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
// If the message also contains a data property with a "type" of "chat",
// navigate to a chat screen
if (initialMessage != null) {
_handleMessage(initialMessage);
}
// Also handle any interaction when the app is in the background via a
// Stream listener
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
}
void _handleMessage(RemoteMessage message) {
if (message.data['type'] == 'chat') {
Navigator.pushNamed(context, '/chat',
arguments: ChatArguments(message),
);
}
}
@override
void initState() {
super.initState();
// Run code required to handle interacted messages in an async function
// as initState() must not be async
setupInteractedMessage();
}
@override
Widget build(BuildContext context) {
return Text("...");
}
}
您如何处理交互取决于您的应用程序设置。上面的示例显示了使用 StatefulWidget 的基本说明。
本地化消息
您可以通过两种不同的方式发送本地化字符串:
- 将每个用户的首选语言存储在您的服务器中,并为每种语言发送自定义通知
- 在您的应用程序中嵌入本地化字符串并使用操作系统的本地区域设置
下面介绍第二种方法的使用方法:
安卓
在
resources/values/strings.xml
中指定您的默认语言消息:<string name="notification_title">Hello world</string> <string name="notification_message">This is a message</string>
在
values- language
目录中指定翻译后的消息。例如,在resources/values-fr/strings.xml
中指定法语消息:<string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
在服务器负载中,不要使用
title
、message
和body
键,而是对本地化消息使用title_loc_key
和body_loc_key
,并将它们设置为要显示的消息的name
属性。消息有效负载如下所示:
{ "data": { "title_loc_key": "notification_title", "body_loc_key": "notification_message" }, }
iOS
在
Base.lproj/Localizable.strings
中指定您的默认语言消息:"NOTIFICATION_TITLE" = "Hello World"; "NOTIFICATION_MESSAGE" = "This is a message";
在
language .lproj
目录中指定翻译后的消息。例如,在fr.lproj/Localizable.strings
中指定法语消息:"NOTIFICATION_TITLE" = "Bonjour le monde"; "NOTIFICATION_MESSAGE" = "C'est un message";
消息有效负载如下所示:
{ "data": { "title_loc_key": "NOTIFICATION_TITLE", "body_loc_key": "NOTIFICATION_MESSAGE" }, }
启用消息传递数据导出
您可以将消息数据导出到 BigQuery 中以供进一步分析。 BigQuery 允许您使用 BigQuery SQL 分析数据,将其导出到另一个云提供商,或将数据用于您的自定义 ML 模型。向 BigQuery 的导出包括消息的所有可用数据,无论消息类型如何或消息是通过 API 还是通知编辑器发送的。
要启用导出,请先按照此处描述的步骤操作,然后按照以下说明操作:
安卓
您可以使用以下代码: dart await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);
iOS
对于 iOS,您需要使用以下内容更改AppDelegate.m
。
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Firebase/Firebase.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:userInfo];
}
@end
网络
对于 Web,您需要更改您的 service worker 才能使用 v9 版本的 SDK。 v9 版本需要捆绑,因此您需要使用像esbuild
这样的捆绑器来让 service worker 工作。请参阅示例应用程序以了解如何实现这一目标。
迁移到 v9 SDK 后,您可以使用以下代码:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
不要忘记运行yarn build
以将新版本的 service worker 导出到web
文件夹。