系統會根據裝置的狀態,以不同方式處理收到的訊息。目的地: 瞭解這些情境,以及如何將 FCM 整合至自己的應用程式。 是建立裝置可能處於下列幾種不同狀態的第一步:
狀態 | 說明 |
---|---|
前景 | 應用程式開啟時,可以看到和使用中。 |
背景 | 應用程式開啟時,在背景中執行 (最小化)。 這通常發生在使用者按下「主畫面」時按鈕 已使用應用程式切換器切換至其他應用程式, 或在另一個分頁 (網站) 中開啟應用程式。 |
已終止 | 裝置鎖定或應用程式未執行時。 |
應用程式必須先符合幾項先決條件 透過 FCM 接收訊息酬載:
- 應用程式必須至少開啟一次 (以便向 FCM 註冊)。
- 在 iOS 裝置上,當使用者從應用程式切換器滑開應用程式時,必須手動重新開啟應用程式,才能讓背景訊息再次開始運作。
- 在 Android 裝置上,當使用者透過裝置設定強制退出應用程式,就必須手動重新開啟應用程式,訊息才會開始運作。
- 在網站上,您必須透過網路推送憑證要求權杖 (使用
getToken()
)。
要求接收訊息的權限
在 iOS、macOS、網頁和 Android 13 (或以上版本) 上,FCM 酬載之前 則必須先徵得使用者的權限。
firebase_messaging
套件提供簡單的 API,可透過 requestPermission
方法要求權限。
此 API 接受多個已命名的引數,這些引數定義您要要求的權限類型,例如:
含有通知酬載的訊息可能會透過 Siri 觸發音效或朗讀訊息。根據預設
此方法會要求合理的預設權限。參考資料 API 針對各項權限的用途提供完整說明文件。
如要開始使用,請從您的應用程式呼叫 方法 (在 iOS 上,原生互動視窗會顯示在網路上) 瀏覽器的原生 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) 和網頁式平台。
Apple 平台和 Android
註冊 onBackgroundMessage
處理常式即可處理背景訊息。收到郵件時,
獨立隔離 (只有 Android 裝置,iOS/macOS 不需要另外隔離),因此即使應用程式未執行,您還是能處理訊息。
使用背景訊息處理常式時,請留意下列事項:
- 不得為匿名函式。
- 這必須是頂層函式 (例如非需要初始化的類別方法)。
- 使用 Flutter 3.3.0 以上版本時,訊息處理常式必須在函式宣告的正上方加上
@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 的 UI不過,您可以執行 HTTP 要求等邏輯、執行 IO 作業 (例如更新本機儲存空間)、與其他外掛程式通訊等等。
此外,也建議您盡快完成邏輯。執行長時間且密集的工作會影響裝置效能 且可能會導致 OS 終止程序。如果工作執行時間超過 30 秒,裝置可能會自動終止程序。
網路
在網頁上,編寫在背景執行的 JavaScript Service Worker。 使用 Service Worker 處理背景訊息。
如要開始使用,請在 web
目錄中建立新檔案,並命名為 firebase-messaging-sw.js
:
// Please see this file for the latest firebase-js-sdk version:
// https://github.com/firebase/flutterfire/blob/master/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.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
變數。
接下來,必須註冊工作站。在 index.html
檔案中,修改啟動 Flutter 的 <script>
標記,註冊 worker:
<script src="flutter_bootstrap.js" async>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('firebase-messaging-sw.js', {
scope: '/firebase-cloud-messaging-push-scope',
});
});
}
</script>
如果您仍在使用舊的範本系統,可以修改啟動 Flutter 的 <script>
標記來註冊工作站,如下所示:
<html>
<body>
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Register Firebase Messaging service worker.
navigator.serviceWorker.register('firebase-messaging-sw.js', {
scope: '/firebase-cloud-messaging-push-scope',
});
// 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;
navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing ?? reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.'
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
接著,重新啟動 Flutter 應用程式。系統會註冊 worker,並透過這個檔案處理所有背景訊息。
處理互動
通知是可見的提示,因此使用者常常需要與通知互動 (按下)。Android 和 iOS 裝置的預設行為都是開啟 應用程式。如果應用程式遭到終止,系統就會啟動應用程式;系統就會移到前景
根據通知內容,您可能需要在應用程式開啟時處理使用者互動。舉例來說,如果透過以下管道傳送新的即時通訊訊息: 通知,當使用者按下該通知時,建議您在應用程式開啟時開啟特定的會話群組。
firebase-messaging
套件提供兩種處理這類互動的方式:
getInitialMessage()
:如果應用程式是從終止狀態開啟,會傳回包含RemoteMessage
的Future
。用完後,系統會移除RemoteMessage
。onMessageOpenedApp
:Stream
,當從背景狀態開啟應用程式時,會發布RemoteMessage
。
建議您同時處理這兩種情況,確保使用者有順暢的使用者體驗。以下程式碼範例概述如何完成這項作業:
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 的基本插圖。
將「訊息」應用程式本地化
您可以透過下列兩種方式傳送本地化字串:
- 在伺服器中儲存每位使用者的偏好語言,並傳送每種語言的自訂通知
- 將本地化字串嵌入應用程式中,並使用作業系統的原生語言代碼設定
以下說明如何使用第二種方法:
Android
指定
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_loc_key
和body_loc_key
,並設為顯示訊息的name
屬性,而不是使用title
、message
和body
金鑰。訊息酬載應如下所示:
{ "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 分析資料 匯出至其他雲端服務供應商,或將資料用於自訂機器學習模型。匯出至 BigQuery 包含訊息的所有可用資料 (無論訊息類型為何),或訊息是否透過 一起管理 API 或通知編輯器
如要啟用匯出功能,請先按照這篇文章中的步驟操作, 然後按照下列指示操作:
Android
您可以使用以下程式碼:
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
網路
如果是網頁,您需要變更 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
資料夾。
在 iOS 通知中顯示圖片
在 Apple 裝置上,您必須新增其他通知服務擴充功能,並設定應用程式來使用,才能讓收到的 FCM 通知顯示來自 FCM 酬載的圖片。
如果您使用 Firebase 電話驗證功能,必須將 Firebase 驗證 Pod 新增至您的 Podfile。
步驟 1 - 新增通知服務擴充功能
- 在 Xcode 中按一下 檔案 >新增 >目標...
- 強制回應會顯示可能指定目標的清單;向下捲動,或使用篩選器選取「Notification Service Extension」。點選「下一步」。
- 新增產品名稱 (請依本教學課程使用「ImageNotification」操作)、將語言設為 Objective-C,然後按一下「Finish」。
- 按一下「啟用」來啟用配置。
步驟 2 - 將目標新增至 Podfile
在 Podfile 中加入 Firebase/Messaging
Pod,藉此確認新擴充功能可存取該 Pod:
在導覽器中,開啟 Podfile:Pods >Podfile
向下捲動至檔案底部,然後新增以下內容:
target 'ImageNotification' do use_frameworks! pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication pod 'Firebase/Messaging' end
使用
pod install
從ios
或macos
目錄安裝或更新 Pod。
步驟 3:使用擴充功能輔助程式
此時,一切應該都會恢復正常運作。最後一步是叫用擴充功能輔助程式。
在導覽工具中選取 ImageNotification 擴充功能
開啟
NotificationService.m
檔案。在檔案頂端的
NotificationService.h
後方匯入FirebaseMessaging.h
,如下所示。將
NotificationService.m
的內容替換為:#import "NotificationService.h" #import "FirebaseMessaging.h" #import "FirebaseAuth.h" // Add this line if you are using FirebaseAuth phone authentication #import <UIKit/UIKit.h> // Add this line if you are using FirebaseAuth phone authentication @interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @end @implementation NotificationService /* Uncomment this if you are using Firebase Auth - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { if ([[FIRAuth auth] canHandleURL:url]) { return YES; } return NO; } - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts { for (UIOpenURLContext *urlContext in URLContexts) { [FIRAuth.auth canHandleURL:urlContext.URL]; } } */ - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; // Modify the notification content here... [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler]; } - (void)serviceExtensionTimeWillExpire { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. self.contentHandler(self.bestAttemptContent); } @end
步驟 4 - 將映像檔新增至酬載
您現在可以在通知酬載中新增圖片。請參閱 iOS 說明文件,瞭解如何建立傳送要求。請注意,裝置會強制執行 300KB 的映像檔大小上限。