??本來是沒有這篇文章的計(jì)劃的痊焊,因?yàn)橹靶马?xiàng)目直接就用上了Flutter2.0空安全,所有的第三方庫也升級空安全了,所以之前項(xiàng)目中做消息推送使用的firebase_messaging第三方庫也得升級空安全坦冠,結(jié)果升級完后,各種以前的方法都沒了拓萌。一臉懵逼,兩臉懵逼~
(這就是flutter第三方庫的坑升略,兼容是不存在的微王,看著不爽直接干掉O(∩_∩)O哈哈~)
??本篇文章就直接聊聊在flutter端firebase_messaging的更新使用,想其他的原生端配置呀品嚣,推送環(huán)境的區(qū)分呀以前的文章都有炕倘,這里就不一一細(xì)說了。
??以前的文章參見:
1腰根、推送文章鏈接激才,基于firebase_messaging 7.0.3
《Flutter中如何使用Firebase 做消息推送(Notification)》
2、Firebase環(huán)境分離鏈接
《Flutter 中為Firebase提供多個(gè)構(gòu)建環(huán)境分離配置》
注:基于firebase_messaging 9.0.0以上版本额嘿,因?yàn)檫@個(gè)版本后就支持空安全了瘸恼。
一、Android原生端修改
??在7.0.3以前册养,Android原生端需要注冊和配置FirebaseMessagingService东帅,現(xiàn)在都不需要了,全部干掉球拦。
//import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin
//import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService
class MaintenanceApplication : FlutterApplication(), PluginRegistry.PluginRegistrantCallback{
override fun onCreate() {
super.onCreate()
Embrace.getInstance().start(this);
// FlutterFirebaseMessagingService.setPluginRegistrant(this)
}
override fun registerWith(registry: PluginRegistry?) {
// GeneratedPluginRegistrant.registerWith(registry)
// FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"))
}
}
二靠闭、Flutter端的變化
大家可以參看pub.dev,不過那個(gè)Example確實(shí)有點(diǎn)惡心坎炼。繞的很~
可以看看這篇文章https://firebase.flutter.dev/docs/messaging/notifications/#foreground-notifications
2.1 在7.0.3以前的話愧膀,在Flutter端接收數(shù)據(jù)有四種方法:
firebaseMessaging.configure(
onMessage: (message) => handleMessage(message),
onLaunch: (message) => startToRedirectByNotification(message, source: 'onLaunch'),
onResume: (message) => startToRedirectByNotification(message, source: 'onResume'),
onBackgroundMessage: backgroundMessageHandler
);
onMessage: 對應(yīng)應(yīng)用在前臺,不會發(fā)出系統(tǒng)Notification谣光,只發(fā)送消息檩淋,如需系統(tǒng)Notification。則需要手動通過flutter_local_notifications第三方庫發(fā)送萄金。
onLaunch:系統(tǒng)在被殺掉蟀悦,既從歷史中劃掉時(shí),點(diǎn)擊Notification后氧敢,跳轉(zhuǎn)調(diào)用的方法日戈。
onResume:系統(tǒng)在后臺時(shí),點(diǎn)擊Notification后孙乖,跳轉(zhuǎn)調(diào)用的方法浙炼。
2.2 在9.0.0以后的話份氧,在Flutter端接收數(shù)據(jù)全部改掉,全部9呐 0牖稹!
2.2.1 應(yīng)用在前臺
當(dāng)應(yīng)用在前臺時(shí)季俩,收到消息需要通過FirebaseMessaging中的onMessage,它是一個(gè)Stream梅掠,我們通過listen監(jiān)聽酌住,它返回的是RemoteMessage對象。同時(shí)應(yīng)用在前臺時(shí)阎抒,也不會發(fā)出系統(tǒng)Notification酪我,業(yè)務(wù)需要的話,需手動發(fā)出且叁。(該方法同7.0.3 onMessage)
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification notification = message.notification;
AndroidNotification android = message.notification?.android;
// If `onMessage` is triggered with a notification, construct our own
// local notification to show to users using the created channel.
if (notification != null && android != null) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channel.description,
icon: android?.smallIcon,
// other properties...
),
));
}
});
2.2.2 應(yīng)用在后臺或被殺掉
這時(shí)候在9.0.0中就沒有onLaunch或者onResume方法了都哭,對應(yīng)的是onMessageOpenedApp,看這貨的名字也知道逞带,應(yīng)該是點(diǎn)擊了Notification欺矫,打開App的數(shù)據(jù)流了。它是一個(gè)Stream展氓,也是通過listen監(jiān)聽它穆趴,獲取相應(yīng)的數(shù)據(jù),并跳轉(zhuǎn)到對應(yīng)頁面遇汞。(這一般是應(yīng)用在后臺未妹,并沒有沒殺掉時(shí)調(diào)用)
// Also handle any interaction when the app is in the background via a
// Stream listener
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data['type'] == 'chat') {
Navigator.pushNamed(context, '/chat',
arguments: ChatArguments(message));
}
});
當(dāng)App被殺掉后,onMessageOpenedApp方法就涼涼了空入,這時(shí)候該getInitialMessage方法登場了(對應(yīng)onLaunch方法)络它。它會獲取最近一次的Remote消息。
// 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 && initialMessage.data['type'] == 'chat') {
Navigator.pushNamed(context, '/chat',
arguments: ChatArguments(initialMessage));
}
2.3 給ios加點(diǎn)權(quán)限
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: true,
);
三歪赢、遇到的坑兒
3.1 onMessage和onMessageOpenedApp
坑兒一:
??上面說了在監(jiān)聽onMessage,它是一個(gè)流轨淌。當(dāng)應(yīng)用在前臺時(shí)迂烁,F(xiàn)irebase原生端就會通過MethodChannel,發(fā)送notification數(shù)據(jù)递鹉,不再發(fā)送系統(tǒng)Notification了盟步。然而,實(shí)際測試在iOS端躏结,App在前端也會發(fā)送系統(tǒng)Notification却盘,如果iOS在監(jiān)聽onMessage流時(shí)再次發(fā)送本地Notification的話,就會有兩條Notification消息,這里我們就得區(qū)分對待了黄橘,只在Android平臺監(jiān)聽onMessage流兆览。
if (Platform.isAndroid) {
FirebaseMessaging.onMessage.listen((event) {
handleMessage(event);
});
}
坑兒二:
??因?yàn)樯厦嬉擦倪^,onMessage和onMessageOpenedApp實(shí)際上是流數(shù)據(jù)塞关,如果我們需要做操作就需要監(jiān)聽它們抬探。
??那么問題來了,比如說在設(shè)置頁面帆赢,我們有控制Notification打開關(guān)閉的開關(guān)小压,一般做法無非是打開時(shí),獲取firebase token椰于,與后臺進(jìn)行綁定怠益;關(guān)閉時(shí)刪除Firebase token,與后臺進(jìn)行解綁瘾婿,這樣就能收到或者不能收到后臺的推送消息了蜻牢。
??但是,我們多開關(guān)幾次后偏陪,也許再次打開時(shí)抢呆,我們收到后臺推送,但是在onMessage里會多次發(fā)送本地Notificition竹挡。開始很詫異镀娶,后來想明白了。
因?yàn)橹氨O(jiān)聽onMessage和onMessageOpenedApp流時(shí)揪罕,關(guān)閉推送沒有關(guān)閉梯码,造成了會同時(shí)有多個(gè)監(jiān)聽器在監(jiān)聽流,所以在我們關(guān)閉或者解綁fireabse_messaging時(shí)好啰,我們需要將監(jiān)聽流的監(jiān)聽器也關(guān)閉轩娶。
static StreamSubscription? streamSubscription;
static StreamSubscription? onAppOpenStreamSubscription;
static clearStreamSubscription() {
streamSubscription?.cancel();
onAppOpenStreamSubscription?.cancel();
streamSubscription = null;
onAppOpenStreamSubscription = null;
}
initFirebase(){
if (Platform.isAndroid) {
streamSubscription = FirebaseMessaging.onMessage.listen((event) {
handleMessage(event);
});
}
onAppOpenStreamSubscription = FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
startToRedirectByNotification(message.data);
});
}
3.2 onMessage手動發(fā)本地Notification庫的問題
??這里用的發(fā)送本地Notification的第三方庫為:flutter_local_notifications: 5.0.0+4。這次不是已經(jīng)升級了版本嗎框往,我認(rèn)為應(yīng)該沒啥問題鳄抒。可是測試發(fā)現(xiàn)心塞椰弊。
坑兒三:
??這里有什么問題呢许溅,這個(gè)問題主要發(fā)生在Android端,iOS沒啥問題秉版。
問題描述:App在前臺贤重,監(jiān)聽onMessage收到firebase發(fā)來的消息后,通過flutter_local_notifications發(fā)出本地Notificaiton清焕。這里并沒有什么問題并蝗。當(dāng)你退出App祭犯,并在歷史中kill掉App。在點(diǎn)擊通知欄的Notification時(shí)滚停,只會打開App沃粗,并不會執(zhí)行onSelectNotification參數(shù)配置的跳轉(zhuǎn)方法。
??這就尷尬了键畴,我以前沒問題的呀最盅,后面就對比了下以前改過的flutter_local_notifications: 2.0.0+1版本。發(fā)現(xiàn)了為什么不調(diào)用onSelectNotification的問題了镰吵。也是需要修改flutter_local_notifications: 5.0.0+4庫的Android端源碼檩禾。
??這也是需要改完后,發(fā)布到私有的pub.dev上疤祭,要不就提issue。
修改路徑:flutter_local_notifications-5.0.0+4/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationPlugin.java
在initilalize方法中添加如下代碼可以解決該問題饵婆。
private void initialize(MethodCall call, Result result) {
...
//Add for when app kill can not receive notification Data
if (mainActivity != null && !launchedActivityFromHistory(mainActivity.getIntent())) {
sendNotificationPayloadMessage(mainActivity.getIntent());
}
//end
...
result.success(true);
}
3.3 事實(shí)證明使用firebase庫如升級打怪
這里碰到的問題是firebase_messaging: 10.0.4的問題勺馆,這里要區(qū)分iOS和Android端的修改,兩邊都有問題侨核。
問題重現(xiàn)描述:當(dāng)App置于后臺或者被kill下的情況草穆,后臺發(fā)出推送,前臺收到了系統(tǒng)Notification搓译,點(diǎn)擊會響應(yīng)onMessageOpenedApp或者getInitialMessage方法悲柱,并處理應(yīng)用層設(shè)置的邏輯,當(dāng)我們退出登錄也就是Sign out后些己,在登錄結(jié)果豌鸡,發(fā)現(xiàn)有重新調(diào)用了 onMessageOpenedApp或者getInitialMessage方法。懵逼了~
沒得辦法段标,還是得去兩端源碼中看看是為什么涯冠,
坑兒四:(Android端)
修改路徑:firebase_messaging-10.0.4/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingPlugin.java
首先,我這里稍稍聊聊逼庞,firebase_messaging庫Android端如何緩存Notification的蛇更,
它有三級緩存:
1、在FlutterFirebaseMessagingPlugin.java中用全局變量initialMessage緩存赛糟;
2派任、在FlutterFirebaseMessagingReceiver.java中收到推送消息后,使用一個(gè)Map -> HashMap<String, RemoteMessage> notifications = new HashMap<>()緩存璧南;
3掌逛、在FlutterFirebaseMessagingStore.java中使用SharedPreferences緩存;
每次在onNewIntent和getInitialMessage中都會進(jìn)行三級緩存取數(shù)據(jù)穆咐,因?yàn)榫彺鎲栴}颤诀,會導(dǎo)致重復(fù)調(diào)用字旭。
解決方案:
因?yàn)樵赟ign Out時(shí),我們會調(diào)用firebase_messaging的deleteToken方法崖叫,那我們就可以在deleteToken時(shí)遗淳,將三級緩存清理一波:(在FlutterFirebaseMessagingPlugin.java中修改)
private Task<Void> deleteToken() {
return Tasks.call(
cachedThreadPool,
() -> {
//Add for clear cache
clearFirebaseNotification();
//end
Tasks.await(FirebaseMessaging.getInstance().deleteToken());
return null;
});
}
//clear Cache Notification when deleteToken
private void clearFirebaseNotification(){
//reset initialMessage
initialMessage = null;
//Clear FlutterFirebaseMessagingStore cache
FlutterFirebaseMessagingStore.getInstance().removeAllFirebaseMessage();
//Clear FlutterFirebaseMessagingReceiver.notifications cache
FlutterFirebaseMessagingReceiver.notifications.clear();
}
在onNewIntent方法中,因?yàn)榱硪粋€(gè)問題:App在后臺沒被干掉心傀,收到推送后打開屈暗,然后在kill掉App,再次進(jìn)入App脂男,依然會重新調(diào)用getInitialMessage方法养叛,后面查了下,發(fā)現(xiàn)在getInitialMessage方法中宰翅,會查找三級緩存弃甥,前兩個(gè)都是null,而SharedPreferences中的緩存還在汁讼,沒有被清理淆攻。
(也是在FlutterFirebaseMessagingPlugin.java中修改)
@Override
public boolean onNewIntent(Intent intent) {
....
//Add 20210729 for Teminated status will open twice getInitMessage()
FlutterFirebaseMessagingStore.getInstance().removeFirebaseMessage(messageId);
//end
...
// Store this message for later use by getInitialMessage.
initialMessage = remoteMessage;
...
}
官方注釋是說不要刪除SharedPreferences中的緩存,我的業(yè)務(wù)需求是需要刪嘿架,大家具體看看自己的業(yè)務(wù)需求吧瓶珊。
iOS端修改
問題也就和上面Android是一樣的。
修改路徑:firebase_messaging-10.0.4/ios/Classes/FLTFirebaseMessagingPlugin.m
在iOS端就沒有像Android那樣有三級緩存了耸彪,它直接接受iOS系統(tǒng)的NotificationCenter打開時(shí)發(fā)來的消息伞芹,
1、直接賦值給了變量NSDictionary *_initialNotification蝉娜。然后通過onMessageOpenedApp向Flutter端發(fā)出消息唱较。
2、如果應(yīng)用調(diào)用了getInitialMessage方法蜀肘,則會通過copyInitialNotification方法拷貝一份_initialNotification绊汹,并將其發(fā)出,并將_initialNotification置為空扮宠。
if ([@"Messaging#getInitialMessage"isEqualToString:call.method]) {
NSLog(@"Messaging#getInitialMessage");
methodCallResult.success([self copyInitialNotification]);
}
- (nullable NSDictionary *)copyInitialNotification { @synchronized(self) {
if (_initialNotification != nil) {
NSDictionary *initialNotificationCopy = [_initialNotification copy];
_initialNotification = nil;
return initialNotificationCopy;
}
}
return nil;
}
在所以這里的原因也是因?yàn)橥顺龅卿浾{(diào)用deleteToken西乖,但是_initialNotification變量并不為空,所以在重新登錄并綁定Firebase時(shí)候調(diào)用getInitialMessage方法時(shí)候就會得到_initialNotification坛增,并發(fā)出Notification获雕。
所以修改就變得很簡單了,在deleteToken方法中修改:
else if ([@"Messaging#deleteToken" isEqualToString:call.method]) {
//將_initialNotification置為空
_initialNotification = nil;
[self messagingDeleteToken:call.arguments withMethodCallResult:methodCallResult];
}
四收捣、總結(jié)
好了届案,到此坑坑總算填完,至于還有什么坑罢艾,源碼已看楣颠,還有啥不能填的尽纽,盡管來,還有誰~~~~??