由于项目的需求,需要在一个Hybrid项目中集成Firebase的推送,当然可以选择成熟的三分库https://github.com/invertase/react-native-firebase,但由于它存在一些限制,以及当前的项目中是将react native作为一个framework来使用的,并非单纯的rn项目,只能自己动手了,其中也参考了一些该三方库的实现。
Firebase 的使用
有关配置相关的内容这里只做简单描述,具体的步骤可参考官方文档。
- 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'
...
}
- 创建FirebaseMessagingService,用于接受推送消息,该service也需要在manifest进行声明
public class MyFirebaseMessagingService extends FirebaseMessagingService {
...
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
Map data = remoteMessage.getData();
Log.d("FirebaseMessaging", data.toString());
// 可在此处添加处理函数
}
}
...
...
在这里有两点需要说明:
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();
// 可在此处添加处理函数
}
}
...
...
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 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 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的区别:
- Android 中的 Activity Launch Mode 详解
- 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);
...
在OtherActivity
的onNewIntent
中,即可获得我们传入的参数:
...
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String extra = intent.getStringExtra("Key");
// handle
}
...
至此,我们也就完成了Hybrid项目中集成Firebase推送的native部分。