Tuỳ thuộc vào trạng thái của thiết bị, thư đến sẽ được xử lý theo cách khác nhau. Để hiểu rõ các tình huống này và cách tích hợp FCM vào ứng dụng của riêng bạn, trước tiên, bạn cần thiết lập nhiều trạng thái mà thiết bị có thể ở trong đó:
Tiểu bang | Mô tả |
---|---|
Nền trước | Khi ứng dụng đang mở, đang xem và đang sử dụng. |
Nền | Khi ứng dụng đang mở nhưng ở chế độ nền (được thu nhỏ). Điều này thường xảy ra khi người dùng nhấn nút "màn hình chính" trên thiết bị, chuyển sang một ứng dụng khác bằng trình chuyển đổi ứng dụng hoặc mở ứng dụng trong một thẻ khác (web). |
Đã chấm dứt | Khi thiết bị bị khoá hoặc ứng dụng không chạy. |
Có một số điều kiện tiên quyết phải được đáp ứng thì ứng dụng mới có thể nhận được tải trọng thông báo qua FCM:
- Ứng dụng phải được mở ít nhất một lần (để cho phép đăng ký với FCM).
- Trên iOS, nếu người dùng vuốt ứng dụng ra khỏi trình chuyển đổi ứng dụng, thì ứng dụng đó phải được mở lại theo cách thủ công để thông báo ở chế độ nền bắt đầu hoạt động trở lại.
- Trên Android, nếu người dùng buộc thoát ứng dụng khỏi phần cài đặt thiết bị, thì ứng dụng đó phải được mở lại theo cách thủ công để tin nhắn bắt đầu hoạt động.
- Trên web, bạn phải yêu cầu mã thông báo (bằng
getToken()
) bằng chứng chỉ đẩy web.
Yêu cầu cấp quyền nhận tin nhắn
Trên iOS, macOS, web và Android 13 (trở lên), trước khi có thể nhận được tải trọng FCM trên thiết bị, trước tiên, bạn phải yêu cầu người dùng cấp quyền.
Gói firebase_messaging
cung cấp một API đơn giản để yêu cầu cấp quyền thông qua phương thức requestPermission
.
API này chấp nhận một số đối số được đặt tên xác định loại quyền mà bạn muốn yêu cầu, chẳng hạn như liệu tính năng nhắn tin chứa tải trọng thông báo có thể kích hoạt âm thanh hoặc đọc to tin nhắn qua Siri hay không. Theo mặc định, phương thức này yêu cầu các quyền mặc định hợp lý. API tham khảo cung cấp tài liệu đầy đủ về mục đích của từng quyền.
Để bắt đầu, hãy gọi phương thức từ ứng dụng của bạn (trên iOS, một cửa sổ bật lên gốc sẽ hiển thị, trên web, luồng API gốc của trình duyệt sẽ được kích hoạt):
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}');
Bạn có thể sử dụng thuộc tính authorizationStatus
của đối tượng NotificationSettings
được trả về từ yêu cầu để xác định quyết định tổng thể của người dùng:
authorized
: Người dùng đã cấp quyền.denied
: Người dùng từ chối cấp quyền.notDetermined
: Người dùng chưa chọn có cấp quyền hay không.provisional
: Người dùng đã cấp quyền tạm thời
Các thuộc tính khác trên NotificationSettings
trả về thông tin về việc một quyền cụ thể có được bật, tắt hay không được hỗ trợ trên thiết bị hiện tại hay không.
Sau khi cấp quyền và hiểu được các loại trạng thái thiết bị, ứng dụng của bạn hiện có thể bắt đầu xử lý các tải trọng FCM sắp tới.
Xử lý thông báo
Dựa trên trạng thái hiện tại của ứng dụng, tải trọng đến của nhiều loại thông báo sẽ yêu cầu các phương thức triển khai khác nhau để xử lý:
Thông báo trên nền trước
Để xử lý thông báo trong khi ứng dụng đang chạy trên nền trước, hãy theo dõi luồng 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}');
}
});
Luồng này chứa một RemoteMessage
, trình bày chi tiết nhiều thông tin về tải trọng, chẳng hạn như nguồn gốc, mã nhận dạng duy nhất, thời gian gửi, liệu tải trọng có chứa thông báo hay không, v.v. Vì thông báo được truy xuất trong khi ứng dụng của bạn đang ở nền trước, nên bạn có thể truy cập trực tiếp vào trạng thái và ngữ cảnh của ứng dụng Flutter.
Thông báo và thông báo trên nền trước
Theo mặc định, các thông báo đến trong khi ứng dụng đang chạy trên nền trước sẽ không hiển thị thông báo rõ ràng trên cả Android và iOS. Tuy nhiên, bạn có thể ghi đè hành vi này:
- Trên Android, bạn phải tạo một kênh thông báo "Mức độ ưu tiên cao".
- Trên iOS, bạn có thể cập nhật các tuỳ chọn trình bày cho ứng dụng.
Thông báo ở chế độ nền
Quy trình xử lý thông báo ở chế độ nền khác nhau trên các nền tảng gốc (Android và Apple) và nền tảng dựa trên web.
Nền tảng Apple và Android
Xử lý thông báo trong nền bằng cách đăng ký trình xử lý onBackgroundMessage
. Khi nhận được thông báo, một môi trường tách biệt sẽ được tạo (chỉ dành cho Android, iOS/macOS không yêu cầu môi trường tách biệt riêng) cho phép bạn xử lý thông báo ngay cả khi ứng dụng không chạy.
Có một vài điều cần lưu ý về trình xử lý thông báo ở chế độ nền:
- Hàm này không được là hàm ẩn danh.
- Đó phải là một hàm cấp cao nhất (ví dụ: không phải là phương thức lớp cần khởi chạy).
- Khi sử dụng Flutter phiên bản 3.3.0 trở lên, bạn phải chú thích trình xử lý thông báo bằng
@pragma('vm:entry-point')
ngay phía trên phần khai báo hàm (nếu không, trình xử lý này có thể bị xoá trong quá trình loại bỏ mã không dùng đến cho chế độ phát hành).
@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());
}
Vì trình xử lý chạy trong vùng chứa riêng bên ngoài ngữ cảnh ứng dụng, nên bạn không thể cập nhật trạng thái ứng dụng hoặc thực thi bất kỳ logic nào ảnh hưởng đến giao diện người dùng. Tuy nhiên, bạn có thể thực hiện logic như các yêu cầu HTTP, thực hiện các thao tác IO (ví dụ: cập nhật bộ nhớ cục bộ), giao tiếp với các trình bổ trợ khác, v.v.
Bạn cũng nên hoàn tất logic càng sớm càng tốt. Việc chạy các tác vụ dài và chuyên sâu ảnh hưởng đến hiệu suất của thiết bị và có thể khiến hệ điều hành chấm dứt quá trình. Nếu các tác vụ chạy trong hơn 30 giây, thiết bị có thể tự động chấm dứt quá trình.
Web
Trên web, hãy viết một Trình chạy dịch vụ JavaScript chạy trong nền. Sử dụng worker dịch vụ để xử lý thông báo ở chế độ nền.
Để bắt đầu, hãy tạo một tệp mới trong thư mục web
và đặt tên là 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);
});
Tệp này phải nhập cả ứng dụng và SDK nhắn tin, khởi chạy Firebase và hiển thị biến messaging
.
Tiếp theo, bạn phải đăng ký worker. Trong tệp index.html
, hãy đăng ký worker bằng cách sửa đổi thẻ <script>
khởi động Flutter:
<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>
Nếu vẫn đang sử dụng hệ thống tạo mẫu cũ, bạn có thể đăng ký worker bằng cách sửa đổi thẻ <script>
khởi động Flutter như sau:
<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>
Tiếp theo, hãy khởi động lại ứng dụng Flutter. Worker sẽ được đăng ký và mọi thông báo ở chế độ nền sẽ được xử lý thông qua tệp này.
Xử lý lượt tương tác
Vì thông báo là một tín hiệu hiển thị, nên người dùng thường tương tác với thông báo (bằng cách nhấn). Hành vi mặc định trên cả Android và iOS là mở ứng dụng. Nếu ứng dụng bị chấm dứt, ứng dụng sẽ được khởi động; nếu ứng dụng đang chạy ở chế độ nền, ứng dụng sẽ được đưa lên nền trước.
Tuỳ thuộc vào nội dung của thông báo, bạn có thể muốn xử lý hoạt động tương tác của người dùng khi ứng dụng mở ra. Ví dụ: nếu một tin nhắn trò chuyện mới được gửi qua thông báo và người dùng nhấn vào thông báo đó, bạn có thể muốn mở cuộc trò chuyện cụ thể khi ứng dụng mở ra.
Gói firebase-messaging
cung cấp hai cách để xử lý hoạt động tương tác này:
getInitialMessage()
: Nếu ứng dụng được mở từ trạng thái đã chấm dứt, thìFuture
chứaRemoteMessage
sẽ được trả về. Sau khi được sử dụng,RemoteMessage
sẽ bị xoá.onMessageOpenedApp
:Stream
đăngRemoteMessage
khi ứng dụng được mở từ trạng thái nền.
Bạn nên xử lý cả hai trường hợp để đảm bảo người dùng có trải nghiệm người dùng suôn sẻ. Mã ví dụ bên dưới trình bày cách thực hiện việc này:
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("...");
}
}
Cách bạn xử lý hoạt động tương tác phụ thuộc vào chế độ thiết lập ứng dụng. Ví dụ trên minh hoạ một ví dụ cơ bản về cách sử dụng StatefulWidget.
Bản địa hoá ứng dụng Tin nhắn
Bạn có thể gửi chuỗi đã bản địa hoá theo hai cách:
- Lưu trữ ngôn ngữ ưu tiên của từng người dùng trong máy chủ và gửi thông báo tuỳ chỉnh cho từng ngôn ngữ
- Nhúng các chuỗi đã bản địa hoá vào ứng dụng và tận dụng chế độ cài đặt ngôn ngữ gốc của hệ điều hành
Dưới đây là cách sử dụng phương thức thứ hai:
Android
Chỉ định thông báo bằng ngôn ngữ mặc định trong
resources/values/strings.xml
:<string name="notification_title">Hello world</string> <string name="notification_message">This is a message</string>
Chỉ định các tin nhắn đã dịch trong thư mục
values-language
. Ví dụ: chỉ định thông báo bằng tiếng Pháp trongresources/values-fr/strings.xml
:<string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
Trong tải trọng máy chủ, thay vì sử dụng khoá
title
,message
vàbody
, hãy sử dụngtitle_loc_key
vàbody_loc_key
cho thông báo đã bản địa hoá và đặt các khoá này thành thuộc tínhname
của thông báo mà bạn muốn hiển thị.Tải trọng của thông báo sẽ có dạng như sau:
{ "data": { "title_loc_key": "notification_title", "body_loc_key": "notification_message" } }
iOS
Chỉ định thông báo bằng ngôn ngữ mặc định trong
Base.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Hello World"; "NOTIFICATION_MESSAGE" = "This is a message";
Chỉ định các tin nhắn đã dịch trong thư mục
language.lproj
. Ví dụ: chỉ định thông báo bằng tiếng Pháp trongfr.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Bonjour le monde"; "NOTIFICATION_MESSAGE" = "C'est un message";
Tải trọng của thông báo sẽ có dạng như sau:
{ "data": { "title_loc_key": "NOTIFICATION_TITLE", "body_loc_key": "NOTIFICATION_MESSAGE" } }
Bật tính năng xuất dữ liệu phân phối tin nhắn
Bạn có thể xuất dữ liệu tin nhắn sang BigQuery để phân tích thêm. BigQuery cho phép bạn phân tích dữ liệu bằng BigQuery SQL, xuất dữ liệu sang một nhà cung cấp dịch vụ đám mây khác hoặc sử dụng dữ liệu cho các mô hình học máy tuỳ chỉnh của bạn. Dữ liệu xuất sang BigQuery bao gồm tất cả dữ liệu có sẵn cho thông báo, bất kể loại thông báo hoặc thông báo được gửi qua API hay trình soạn thông báo.
Để bật tính năng xuất, trước tiên, hãy làm theo các bước mô tả tại đây, sau đó làm theo hướng dẫn sau:
Android
Bạn có thể sử dụng mã sau:
await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);
iOS
Đối với iOS, bạn cần thay đổi AppDelegate.m
bằng nội dung sau.
#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
Đối với Web, bạn cần thay đổi worker dịch vụ để sử dụng phiên bản v9 của SDK.
Phiên bản v9 cần được đóng gói, vì vậy, bạn cần sử dụng trình đóng gói như esbuild
để trình chạy dịch vụ hoạt động.
Hãy xem ứng dụng mẫu để biết cách thực hiện việc này.
Sau khi di chuyển sang SDK phiên bản 9, bạn có thể sử dụng mã sau:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
Đừng quên chạy yarn build
để xuất phiên bản mới của worker dịch vụ vào thư mục web
.
Hiển thị hình ảnh trong thông báo trên iOS
Trên thiết bị Apple, để Thông báo FCM đến hiển thị hình ảnh từ tải trọng FCM, bạn phải thêm một tiện ích dịch vụ thông báo bổ sung và định cấu hình ứng dụng để sử dụng tiện ích đó.
Nếu đang sử dụng tính năng xác thực qua điện thoại Firebase, bạn phải thêm nhóm Firebase Auth vào Podfile.
Bước 1 – Thêm tiện ích dịch vụ thông báo
- Trong Xcode, hãy nhấp vào File > New > Target... (Tệp > Mới > Mục tiêu...).
- Một cửa sổ bật lên sẽ hiển thị danh sách các mục tiêu có thể; hãy di chuyển xuống hoặc sử dụng bộ lọc để chọn Notification Service Extension (Tiện ích dịch vụ thông báo). Nhấp vào Tiếp theo.
- Thêm tên sản phẩm (sử dụng "ImageNotification" để làm theo hướng dẫn này), đặt ngôn ngữ thành Objective-C rồi nhấp vào Finish (Hoàn tất).
- Bật giao thức bằng cách nhấp vào Kích hoạt.
Bước 2 – Thêm mục tiêu vào Podfile
Đảm bảo rằng tiện ích mới của bạn có quyền truy cập vào nhóm Firebase/Messaging
bằng cách thêm tiện ích đó vào Podfile:
Trong Trình điều hướng, hãy mở Podfile: Pods > Podfile (Gói > Tệp Pod)
Di chuyển xuống cuối tệp rồi thêm:
target 'ImageNotification' do use_frameworks! pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication pod 'Firebase/Messaging' end
Cài đặt hoặc cập nhật các nhóm của bạn bằng
pod install
từ thư mụcios
hoặcmacos
.
Bước 3 – Sử dụng trình trợ giúp tiện ích
Tại thời điểm này, mọi thứ vẫn đang chạy bình thường. Bước cuối cùng là gọi trình trợ giúp tiện ích.
Trong trình điều hướng, hãy chọn tiện ích ImageNotification
Mở tệp
NotificationService.m
.Ở đầu tệp, hãy nhập
FirebaseMessaging.h
ngay sauNotificationService.h
như minh hoạ bên dưới.Thay thế nội dung của
NotificationService.m
bằng:#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
Bước 4 – Thêm hình ảnh vào tải trọng
Giờ đây, bạn có thể thêm hình ảnh vào tải trọng thông báo. Xem tài liệu về iOS về cách tạo yêu cầu gửi. Xin lưu ý rằng thiết bị sẽ thực thi kích thước hình ảnh tối đa là 300 KB.