Flutter使用Firebase做消息推送(版本更新使用)

??本來是沒有這篇文章的計(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,
);

三歪赢、遇到的坑兒

以前的標(biāo)題
現(xiàn)在的標(biāo)題化戳,還是太年輕了??
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é)

好了届案,到此坑坑總算填完,至于還有什么坑罢艾,源碼已看楣颠,還有啥不能填的尽纽,盡管來,還有誰~~~~??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末童漩,一起剝皮案震驚了整個(gè)濱河市弄贿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矫膨,老刑警劉巖差凹,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異侧馅,居然都是意外死亡危尿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門馁痴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谊娇,“玉大人,你說我怎么就攤上這事罗晕∮事蹋” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵攀例,是天一觀的道長。 經(jīng)常有香客問我顾腊,道長粤铭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任杂靶,我火速辦了婚禮梆惯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吗垮。我一直安慰自己垛吗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布烁登。 她就那樣靜靜地躺著怯屉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饵沧。 梳的紋絲不亂的頭發(fā)上锨络,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音狼牺,去河邊找鬼羡儿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛是钥,可吹牛的內(nèi)容都是我干的掠归。 我是一名探鬼主播缅叠,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼虏冻!你這毒婦竟也來了肤粱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤兄旬,失蹤者是張志新(化名)和其女友劉穎狼犯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體领铐,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悯森,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绪撵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓢姻。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖音诈,靈堂內(nèi)的尸體忽然破棺而出幻碱,到底是詐尸還是另有隱情,我是刑警寧澤细溅,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布褥傍,位于F島的核電站,受9級特大地震影響喇聊,放射性物質(zhì)發(fā)生泄漏恍风。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一誓篱、第九天 我趴在偏房一處隱蔽的房頂上張望朋贬。 院中可真熱鬧,春花似錦窜骄、人聲如沸锦募。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糠亩。三九已至,卻和暖如春党远,著一層夾襖步出監(jiān)牢的瞬間削解,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工沟娱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氛驮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓济似,卻偏偏與公主長得像矫废,于是被迫代替她去往敵國和親盏缤。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容