Hybrid项目中集成Firebase推送

由于项目的需求,需要在一个Hybrid项目中集成Firebase的推送,当然可以选择成熟的三分库https://github.com/invertase/react-native-firebase,但由于它存在一些限制,以及当前的项目中是将react native作为一个framework来使用的,并非单纯的rn项目,只能自己动手了,其中也参考了一些该三方库的实现。

Firebase 的使用

有关配置相关的内容这里只做简单描述,具体的步骤可参考官方文档

  1. Gradle中添加依赖
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. 创建FirebaseMessagingService,用于接受推送消息,该service也需要在manifest进行声明
public class MyFirebaseMessagingService extends FirebaseMessagingService {
    ...
    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        Map<String, String> data = remoteMessage.getData();
        Log.d("FirebaseMessaging", data.toString());
        // 可在此处添加处理函数
    }
}
...
        <service
            android:name=".notification.MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
...

在这里有两点需要说明:
a. 对于接受消息,可以采用两种方式,可任选一种,一种是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();
        // 可在此处添加处理函数
    }
}
...
        <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的推送有两种类型Notification message, Data message,两者的定义可参考官方文档,我们看一下payload的区别,使用postman可触发Firebase发送通知

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,不管使用哪一种,都可以在回调中获取到完整的payload,但在调试过程中,会发现,Notification message会自动产生一个notification,通过官方示例中的注释也说明了这一点:

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]
...
}

由于自动产生的notification的contentIntent只是启动launcher activity,并不可对其进行修改,所以更建议使用Data message,从而可以自己定义notification的样式以及其他action,这里给出了一个简单的示例:

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的是否在前台

当我们要显示notification时,通常会根据App是否在前台做不同的处理,这里给一个简单的场景,当App在前台时,显示一个Toast,在后台时则显示notification,那问题又来了,如何判断App当前的状态呢?

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;
  }

这也是一种较为常规的方式,但查阅了一下资料,该方式存在一些潜在的适配问题,某些机型判断结果和预期相反,所以不推荐使用。取而代之,可以使用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());
            }
        });
    }
}

通过判断currentActivity是否为null即可得知当前app的状态,当然也可以封装到一个工具类中,使用起来更加直接。

onNewIntent接受消息

当App退到后台后还没有被系统kill掉前,我们通过pendingIntent可以打开我们想要的activity(这个activity也没有被destroy),同时又需要传入我们想要的参数,需要怎么处理呢?这里就涉及到activity launch mode的概念了,这里推荐两篇文章,详细的描述了如何设置launch mode,以及不同launch mode的区别:

  1. Android 中的 Activity Launch Mode 详解
  2. ActivityRecord、TaskRecord、ActivityStack以及Activity启动模式详解

这里我们使用了singleTask模式,当我们想要传入参数时,只需要在pendinIntent中添加我们想要的参数即可,这个参数便是payload中我们传入的:

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

OtherActivityonNewIntent中,即可获得我们传入的参数:

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

至此,我们也就完成了Hybrid项目中集成Firebase推送的native部分。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 所选依赖: 工具类:一、 package com.xxx.core.util.push.enums; /** 配置...
    Y_LY阅读 1,882评论 0 2
  • 前言 在Android开发中,消息推送功能的使用非常常见。 推送消息截图 为了降低开发成本,使用第三方推送是现今较...
    BillyLu1994阅读 4,407评论 0 2
  • 原文原文是基于Firebase 2.X构筑的匿名聊天室Demo,我在根据原文构筑时FireBase已经更新到3.X...
    茄子星人阅读 10,005评论 3 26
  • 本文重点介绍应用程序的启动过程,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细...
    天宇sonny阅读 399评论 1 0
  • 他走在回家的路上,周围寂静无声。枯叶被踩踏时发出的沙沙声如此悦耳,若是有儿童的嬉笑声,似乎会引起某些思绪。 夜...
    爱编故事的杨太阳阅读 166评论 0 1