由于項(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)單描述很钓,具體的步驟可參考官方文檔。
- 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'
...
}
- 創(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ū)別:
這里我們使用了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);
...
在OtherActivity
的onNewIntent
中圆裕,即可獲得我們傳入的參數(shù):
...
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String extra = intent.getStringExtra("Key");
// handle
}
...
至此,我們也就完成了Hybrid項(xiàng)目中集成Firebase推送的native部分荆几。