Hybrid項(xiàng)目中集成Firebase推送

由于項(xiàng)目的需求,需要在一個(gè)Hybrid項(xiàng)目中集成Firebase的推送念赶,當(dāng)然可以選擇成熟的三分庫(kù)https://github.com/invertase/react-native-firebase础钠,但由于它存在一些限制,以及當(dāng)前的項(xiàng)目中是將react native作為一個(gè)framework來(lái)使用的叉谜,并非單純的rn項(xiàng)目旗吁,只能自己動(dòng)手了,其中也參考了一些該三方庫(kù)的實(shí)現(xiàn)停局。

Firebase 的使用

有關(guān)配置相關(guān)的內(nèi)容這里只做簡(jiǎn)單描述很钓,具體的步驟可參考官方文檔

  1. Gradle中添加依賴(lài)
buildscript {
...
    dependencies {
        ...
        classpath 'com.google.gms:google-services:4.3.3'
    }
}
...
apply plugin: 'com.google.gms.google-services'
...
dependencies {
    ...
    implementation 'com.google.firebase:firebase-analytics:17.3.0'
    implementation 'com.google.firebase:firebase-messaging:20.1.5'
    ...
}
  1. 創(chuàng)建FirebaseMessagingService董栽,用于接受推送消息码倦,該service也需要在manifest進(jìn)行聲明
public class MyFirebaseMessagingService extends FirebaseMessagingService {
    ...
    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        Map<String, String> data = remoteMessage.getData();
        Log.d("FirebaseMessaging", data.toString());
        // 可在此處添加處理函數(shù)
    }
}
...
        <service
            android:name=".notification.MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
...

在這里有兩點(diǎn)需要說(shuō)明:
a. 對(duì)于接受消息,可以采用兩種方式锭碳,可任選一種袁稽,一種是Firebase文檔所提的方式,也是上方代碼中所使用的工禾;另一種运提,可采用broadcast receiver:

public class MessagingReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        RemoteMessage remoteMessage = new RemoteMessage(intent.getExtras());
        RemoteMessage.Notification notification = remoteMessage.getNotification();
        // 可在此處添加處理函數(shù)
    }
}
...
        <receiver
            android:name=".notification.MessagingReceiver"
            android:exported="true"
            android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            </intent-filter>
        </receiver>
...

b. Firebase的推送有兩種類(lèi)型Notification message, Data message,兩者的定義可參考官方文檔闻葵,我們看一下payload的區(qū)別民泵,使用postman可觸發(fā)Firebase發(fā)送通知

curl --location --request POST 'https://fcm.googleapis.com/fcm/send' \
--header 'Authorization: key=${REPLACE_BY_YOUR_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{ 
    "data": {
            "title": "Firebase notification",
            "detail": "I am firebase notification. you can customise me. enjoy"
    },
    "notification": {
         "title": "title", 
         "body": "body",
         "event_time": "123"
    },
    "to" : "${REPLACE_BY_YOUR_DEVICE_TOKEN}"
}'

在payload的中包含“data”則是Data message,包含“notification”則是Notification message槽畔,不管使用哪一種栈妆,都可以在回調(diào)中獲取到完整的payload,但在調(diào)試過(guò)程中厢钧,會(huì)發(fā)現(xiàn)鳞尔,Notification message會(huì)自動(dòng)產(chǎn)生一個(gè)notification,通過(guò)官方示例中的注釋也說(shuō)明了這一點(diǎn):

public void onMessageReceived(RemoteMessage remoteMessage) {
        // [START_EXCLUDE]
        // There are two types of messages data messages and notification messages. Data messages
        // are handled
        // here in onMessageReceived whether the app is in the foreground or background. Data
        // messages are the type
        // traditionally used with GCM. Notification messages are only received here in
        // onMessageReceived when the app
        // is in the foreground. When the app is in the background an automatically generated
        // notification is displayed.
        // When the user taps on the notification they are returned to the app. Messages
        // containing both notification
        // and data payloads are treated as notification messages. The Firebase console always
        // sends notification
        // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
        // [END_EXCLUDE]
...
}

由于自動(dòng)產(chǎn)生的notification的contentIntent只是啟動(dòng)launcher activity早直,并不可對(duì)其進(jìn)行修改寥假,所以更建議使用Data message,從而可以自己定義notification的樣式以及其他action霞扬,這里給出了一個(gè)簡(jiǎn)單的示例:

public class MyFirebaseMessagingService extends FirebaseMessagingService {
...
    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        Map<String, String> data = remoteMessage.getData();
        showNotification(data.get("title"), data.get("detail"));
    }

    private void showNotification(String title, String body) {
        Context context = App.INSTANCE;
        Intent intent = new Intent(context, OtherActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);

        Notification notification = new NotificationCompat.Builder(context, "CHANNEL_ID")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle(title)
                .setContentText(body)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setContentIntent(pendingIntent)
                .build();
        notificationManager.notify(32, notification);
    }
}

判斷App的是否在前臺(tái)

當(dāng)我們要顯示notification時(shí)糕韧,通常會(huì)根據(jù)App是否在前臺(tái)做不同的處理枫振,這里給一個(gè)簡(jiǎn)單的場(chǎng)景,當(dāng)App在前臺(tái)時(shí)萤彩,顯示一個(gè)Toast粪滤,在后臺(tái)時(shí)則顯示notification,那問(wèn)題又來(lái)了雀扶,如何判斷App當(dāng)前的狀態(tài)呢杖小?

react-native-firebase中,它使用的方式為:

   public static boolean isAppInForeground() {
        ActivityManager activityManager = (ActivityManager)
                getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        String packageName = getApplicationContext().getPackageName();
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager
                .getRunningAppProcesses();
        if (appProcesses == null)
            return false;
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.processName.equals(packageName)
                    && appProcess.importance == ActivityManager
                    .RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                return true;
            }
        }
        return false;
  }

這也是一種較為常規(guī)的方式愚墓,但查閱了一下資料予权,該方式存在一些潛在的適配問(wèn)題,某些機(jī)型判斷結(jié)果和預(yù)期相反转绷,所以不推薦使用伟件。取而代之,可以使用registerActivityLifecycleCallbacks议经,具體代碼如下:

public class App extends Application {
    public static App INSTANCE;
    public static Activity currentActivity;

    @Override
    public void onCreate() {
        super.onCreate();
        INSTANCE = this;
        setupActivityListener();
    }

    private void setupActivityListener() {
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {
                Log.d("Application", "onActivityCreated: " + activity.getLocalClassName());
            }

            @Override
            public void onActivityStarted(@NonNull Activity activity) {
                Log.d("Application", "onActivityStarted: " + activity.getLocalClassName());
            }

            @Override
            public void onActivityResumed(@NonNull Activity activity) {
                Log.d("Application", "onActivityResumed: " + activity.getLocalClassName());
                currentActivity = activity;
            }

            @Override
            public void onActivityPaused(@NonNull Activity activity) {
                Log.d("Application", "onActivityPaused: " + activity.getLocalClassName());
                if (currentActivity == activity) {
                    currentActivity = null;
                }
            }

            @Override
            public void onActivityStopped(@NonNull Activity activity) {
                Log.d("Application", "onActivityStopped: " + activity.getLocalClassName());
            }

            @Override
            public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {

            }

            @Override
            public void onActivityDestroyed(@NonNull Activity activity) {
                Log.d("Application", "onActivityDestroyed: " + activity.getLocalClassName());
            }
        });
    }
}

通過(guò)判斷currentActivity是否為null即可得知當(dāng)前app的狀態(tài)斧账,當(dāng)然也可以封裝到一個(gè)工具類(lèi)中,使用起來(lái)更加直接煞肾。

onNewIntent接受消息

當(dāng)App退到后臺(tái)后還沒(méi)有被系統(tǒng)kill掉前咧织,我們通過(guò)pendingIntent可以打開(kāi)我們想要的activity(這個(gè)activity也沒(méi)有被destroy),同時(shí)又需要傳入我們想要的參數(shù)籍救,需要怎么處理呢习绢?這里就涉及到activity launch mode的概念了,這里推薦兩篇文章蝙昙,詳細(xì)的描述了如何設(shè)置launch mode闪萄,以及不同launch mode的區(qū)別:

  1. Android 中的 Activity Launch Mode 詳解
  2. ActivityRecord、TaskRecord奇颠、ActivityStack以及Activity啟動(dòng)模式詳解

這里我們使用了singleTask模式败去,當(dāng)我們想要傳入?yún)?shù)時(shí),只需要在pendinIntent中添加我們想要的參數(shù)即可烈拒,這個(gè)參數(shù)便是payload中我們傳入的:

...
        Intent intent = new Intent(context, OtherActivity.class);
        intent.putExtra("Key", body);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
...

OtherActivityonNewIntent中圆裕,即可獲得我們傳入的參數(shù):

...
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        String extra = intent.getStringExtra("Key");
        // handle
    }
...

至此,我們也就完成了Hybrid項(xiàng)目中集成Firebase推送的native部分荆几。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吓妆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吨铸,更是在濱河造成了極大的恐慌行拢,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诞吱,死亡現(xiàn)場(chǎng)離奇詭異剂陡,居然都是意外死亡狈涮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)鸭栖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人握巢,你說(shuō)我怎么就攤上這事晕鹊。” “怎么了暴浦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵溅话,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我歌焦,道長(zhǎng)飞几,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任独撇,我火速辦了婚禮屑墨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纷铣。我一直安慰自己卵史,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布搜立。 她就那樣靜靜地躺著以躯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪啄踊。 梳的紋絲不亂的頭發(fā)上忧设,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音颠通,去河邊找鬼址晕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蒜哀,可吹牛的內(nèi)容都是我干的斩箫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼撵儿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼乘客!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起淀歇,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤易核,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后浪默,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體牡直,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缀匕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碰逸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乡小。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饵史,靈堂內(nèi)的尸體忽然破棺而出满钟,到底是詐尸還是另有隱情,我是刑警寧澤胳喷,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布湃番,位于F島的核電站,受9級(jí)特大地震影響吭露,放射性物質(zhì)發(fā)生泄漏吠撮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一讲竿、第九天 我趴在偏房一處隱蔽的房頂上張望泥兰。 院中可真熱鬧,春花似錦戴卜、人聲如沸逾条。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)师脂。三九已至,卻和暖如春江锨,著一層夾襖步出監(jiān)牢的瞬間吃警,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工啄育, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酌心,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓挑豌,卻偏偏與公主長(zhǎng)得像安券,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子氓英,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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

  • 所選依賴(lài): 工具類(lèi):一侯勉、 package com.xxx.core.util.push.enums; /** 配置...
    Y_LY閱讀 1,884評(píng)論 0 2
  • 前言 在Android開(kāi)發(fā)中,消息推送功能的使用非常常見(jiàn)铝阐。 推送消息截圖 為了降低開(kāi)發(fā)成本址貌,使用第三方推送是現(xiàn)今較...
    BillyLu1994閱讀 4,416評(píng)論 0 2
  • 原文原文是基于Firebase 2.X構(gòu)筑的匿名聊天室Demo,我在根據(jù)原文構(gòu)筑時(shí)FireBase已經(jīng)更新到3.X...
    茄子星人閱讀 10,009評(píng)論 3 26
  • 本文重點(diǎn)介紹應(yīng)用程序的啟動(dòng)過(guò)程,應(yīng)用程序的啟動(dòng)過(guò)程實(shí)際上就是應(yīng)用程序中的默認(rèn)Activity的啟動(dòng)過(guò)程,本文將詳細(xì)...
    天宇sonny閱讀 400評(píng)論 1 0
  • 他走在回家的路上练对,周?chē)澎o無(wú)聲遍蟋。枯葉被踩踏時(shí)發(fā)出的沙沙聲如此悅耳螟凭,若是有兒童的嬉笑聲虚青,似乎會(huì)引起某些思緒。 夜...
    愛(ài)編故事的楊太陽(yáng)閱讀 166評(píng)論 0 1