簡述
本文主要講述Android Firebase Cloud Message(FCM) 推送機(jī)制.
架構(gòu)
主要流程:
- 業(yè)務(wù)backend Call FCM backend
- FCM backend 推消息到 Android transport layer(ATL)
- ATL 推消息到 APP上的FCM SDK
APP處理FCM SDK 回調(diào)
diagram-FCM.png
流程
1. 業(yè)務(wù)backend Call FCM backend
請參看Android Firebase Cloud Message(FCM) 推送機(jī)制分析(上)
2. FCM backend 推消息到 Android transport layer(ATL)
請參看Android Firebase Cloud Message(FCM) 推送機(jī)制分析(上)
3. ATL 推消息到 APP上的FCM SDK
3.1 總覽
GoogleMobile Service(GMS) -> Firebase Cloud Message (FCM) -> APP FCM SDK
FirebaseInstanceIdReceiver
繼承GMS
的CloudMessagingReceiver
, 之后通過 Binder 機(jī)制 IPC 到目前 APP 中的 FCM SDK
3.2 FirebaseInstanceIdReceiver 接收message
3.2.1 FirebaseInstanceIdReceiver
通過 onMessageReceive()
接收 message
可以看到FirebaseInstanceIdReceiver
通過 onMessageReceive()
接收 message , 那又是哪里會去調(diào)用到 FirebaseInstanceIdReceiver#onMessageReceive()
呢?我們可以看到這是一個Override方法,我們?nèi)ジ割?CloudMessagingReceiver
找答案
3.2.2 CloudMessagingReceiver
調(diào)用 onMessageReceive()
可以看到 CloudMessagingReceiver
是繼承 BroadcastReceiver
, 所以CloudMessagingReceiver
是個廣播接收器,所以我們先看 onReceive()
方法.
onReceive()
方法很短,重點(diǎn)在 new zzd(this, var2, var1, var3, var4)
這句代碼.
點(diǎn)進(jìn)去可以知道
zzd
是一個 Runnable
, 我們重點(diǎn)查看 run()
方法可知里面只有一行代碼, 就是 CloudMessagingReceiver#zza()
, 繼續(xù)追!因?yàn)檫@個
CloudMessagingReceiver#zza()
被隱藏了,萬幸我們在萬能的Github找到了答案. 里面會call 到 zza(@NonNull Context var1, @NonNull Intent var2)
和 zzb(@NonNull Context var1, @NonNull Intent var2)
方法, 我們先看 zzb()
方法.在
zzb()
我們終于如愿見到 onMessageReceive()
方法了!以上就是繼承
GMS
CloudMessagingReceiver
的 FirebaseInstanceIdReceiver
接收廣播后,調(diào)用 onMessageReceive()
的過程
3.3 IPC 到目前 APP 中的 FCM SDK
3.3.1 FirebaseInstanceIdReceiver
觸發(fā)IPC
3.3.2 FcmBroadcastProcessor
啟動 IPC
package com.google.firebase.iid;
/* compiled from: com.google.firebase:firebase-iid@@20.3.0 */
public class FcmBroadcastProcessor {
private static WithinAppServiceConnection fcmServiceConn;
private static final Object lock = new Object();
private final Context context;
private final Executor executor;
public Task<Integer> process(Intent intent) {
//準(zhǔn)備工作...
// 啟動 FirebaseMessagingService
return startMessagingService(this.context, intent);
}
public Task<Integer> startMessagingService(Context context2, Intent intent) {
//準(zhǔn)備工作...
// binder 綁定FirebaseMessagingService
return bindToMessagingService(context2, intent);
}
private static Task<Integer> bindToMessagingService(Context context2, Intent intent) {
// 劃重點(diǎn)!!!
// 重點(diǎn)在這里, 實(shí)例化 WithinAppServiceConnection 后, sendIntent(intent) 完成IPC通信
return getServiceConnection(context2, ServiceStarter.ACTION_MESSAGING_EVENT).sendIntent(intent).continueWith(FirebaseIidExecutors.directExecutor(), FcmBroadcastProcessor$$Lambda$3.$instance);
}
// binder 對象代理
private static WithinAppServiceConnection getServiceConnection(Context context2, String str) {
WithinAppServiceConnection withinAppServiceConnection;
synchronized (lock) {
if (fcmServiceConn == null) {
fcmServiceConn = new WithinAppServiceConnection(context2, str);
}
withinAppServiceConnection = fcmServiceConn;
}
return withinAppServiceConnection;
}
}
3.3.3 WithinAppServiceConnection#sendIntent()
package com.google.firebase.iid;
/* compiled from: com.google.firebase:firebase-iid@@20.3.0 */
public class WithinAppServiceConnection implements ServiceConnection {
private WithinAppServiceBinder binder;
private boolean connectionInProgress;
private final Intent connectionIntent;
private final Context context;
private final Queue<BindRequest> intentQueue;
private final ScheduledExecutorService scheduledExecutorService;
// IPC發(fā)送Intent
public synchronized Task<Void> sendIntent(Intent intent) {
BindRequest bindRequest;
if (Log.isLoggable("FirebaseInstanceId", 3)) {
Log.d("FirebaseInstanceId", "new intent queued in the bind-strategy delivery");
}
bindRequest = new BindRequest(intent);
bindRequest.arrangeTimeout(this.scheduledExecutorService);
// 添加bindRequest 到 隊(duì)列
this.intentQueue.add(bindRequest);
// 消化隊(duì)列
flushQueue();
return bindRequest.getTask();
}
// 處理隊(duì)列
private synchronized void flushQueue() {
if (Log.isLoggable("FirebaseInstanceId", 3)) {
Log.d("FirebaseInstanceId", "flush queue called");
}
// 循環(huán)清空隊(duì)列
while (!this.intentQueue.isEmpty()) {
if (Log.isLoggable("FirebaseInstanceId", 3)) {
Log.d("FirebaseInstanceId", "found intent to be delivered");
}
// binder 為空時,再次創(chuàng)建
if (this.binder == null || !this.binder.isBinderAlive()) {
startConnectionIfNeeded();
return;
}
if (Log.isLoggable("FirebaseInstanceId", 3)) {
Log.d("FirebaseInstanceId", "binder is alive, sending the intent.");
}
// 劃重點(diǎn)!!
// 關(guān)鍵地方,發(fā)送數(shù)據(jù)到APP FCM SDK
this.binder.send(this.intentQueue.poll());
}
}
private void startConnectionIfNeeded() {
if (Log.isLoggable("FirebaseInstanceId", 3)) {
StringBuilder sb = new StringBuilder(39);
sb.append("binder is dead. start connection? ");
sb.append(!this.connectionInProgress);
Log.d("FirebaseInstanceId", sb.toString());
}
if (!this.connectionInProgress) {
this.connectionInProgress = true;
try {
if (!ConnectionTracker.getInstance().bindService(this.context, this.connectionIntent, this, 65)) {
Log.e("FirebaseInstanceId", "binding to the service failed");
this.connectionInProgress = false;
finishAllInQueue();
}
} catch (SecurityException e) {
Log.e("FirebaseInstanceId", "Exception while binding the service", e);
}
}
}
}
4. APP處理FCM SDK 回調(diào) [2]
4.1 總覽
具體使用請參考 [在Android應(yīng)用中處理消息](需要翻墻)
FirebaseInstanceIdReceiver
收到廣播后,通過IPC start Service ,FirebaseMessagingService
處理收到的Intent
后, 經(jīng)過判斷當(dāng)前APP是否在前臺, 之后調(diào)用FirebaseMessagingService #onMessageReceived()
或 啟動 APP的launchActivity.
4.2 FirebaseMessagingService
接收 Intent
已知一般處理前臺消息時,繼承 FirebaseMessagingService
后,重寫 onMessageReceived()
方法.
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(msg: RemoteMessage) {
// 前臺收到消息,會回調(diào)此方法傳遞RemoteMessage
super.onMessageReceived(msg)
Log.d(TAG, Thread.currentThread().name + " " + msg.notification?.body)
}
}
接下來我們查看是如何接收并傳遞到這里的.
查看 FirebaseMessagingService
可知繼承 EnhancedIntentService
, 本質(zhì)是 Service
. 因?yàn)槭?Service
, 我們重點(diǎn)查看 onStartCommand()
方法,這個方式是 startService()
的時候會觸發(fā)的方法. FirebaseMessagingService
沒有這個重寫這個方法, 我們查看 EnhancedIntentService
中的這個方法.
package com.google.firebase.messaging;
/* compiled from: com.google.firebase:firebase-messaging@@20.3.0 */
public abstract class EnhancedIntentService extends Service {
private Binder binder;
final ExecutorService executor = FcmExecutors.newIntentHandleExecutor();
private int lastStartId;
private final Object lock = new Object();
private int runningTasks = 0;
// startService()的時候會call這個onStartCommand()方法
public final int onStartCommand(Intent intent, int i, int i2) {
synchronized (this.lock) {
this.lastStartId = i2;
this.runningTasks++;
}
// 獲取Intent
Intent startCommandIntent = getStartCommandIntent(intent);
if (startCommandIntent == null) {
finishTask(intent);
return 2;
}
// intent不為空就調(diào)用processIntent()處理Intent
Task<Void> processIntent = processIntent(startCommandIntent);
if (processIntent.isComplete()) {
finishTask(intent);
return 2;
}
processIntent.addOnCompleteListener(EnhancedIntentService$$Lambda$1.$instance, (OnCompleteListener<Void>) new EnhancedIntentService$$Lambda$2(this, intent));
return 3;
}
/* access modifiers changed from: protected */
public Intent getStartCommandIntent(Intent intent) {
// 看源碼可知, FirebaseMessagingService重寫了這個方法
return intent;
}
}
根據(jù) EnhancedIntentService#onStartCommand()
方法可以知道, 當(dāng) FirebaseMessagingService
被call startService時, 通過 getStartCommandIntent()
獲取Intent, 之后通過 processIntent(startCommandIntent)
處理Intent.
我們接著看 getStartCommandIntent()
是如何獲取Intent的.
package com.google.firebase.messaging;
/* compiled from: com.google.firebase:firebase-messaging@@20.3.0 */
public class FirebaseMessagingService extends EnhancedIntentService {
public static final String ACTION_DIRECT_BOOT_REMOTE_INTENT = "com.google.firebase.messaging.RECEIVE_DIRECT_BOOT";
private static final Queue<String> recentlyReceivedMessageIds = new ArrayDeque(10);
/* access modifiers changed from: protected */
public Intent getStartCommandIntent(Intent intent) {
// 通過ServiceStarter獲取Intent type的MessagingEvent
return ServiceStarter.getInstance().getMessagingEvent();
}
}
package com.google.firebase.messaging;
@KeepForSdk
public class ServiceStarter {
public static final int SUCCESS = -1;
@KeepForSdk
public static final int ERROR_UNKNOWN = 500;
private static ServiceStarter instance;
@GuardedBy("this")
@Nullable
private String firebaseMessagingServiceClassName = null;
private Boolean hasWakeLockPermission = null;
private Boolean hasAccessNetworkStatePermission = null;
private final Queue<Intent> messagingEvents;
static synchronized ServiceStarter getInstance() {
if (instance == null) {
ServiceStarter var0 = new ServiceStarter();
instance = var0;
}
return instance;
}
private ServiceStarter() {
ArrayDeque var1 = new ArrayDeque();
this.messagingEvents = var1;
}
@MainThread
Intent getMessagingEvent() {
// 獲取Messaging Event intent
return (Intent)this.messagingEvents.poll();
}
}
根據(jù)源碼可知, ServiceStarter
是一個單例,一個進(jìn)程只有一個 ServiceStarter
.而且只在MainThread做處理.
4.3 FirebaseMessagingService
處理 Intent
拿到Intent之后就是處理Intent了,由上面可以知道是通過 processIntent()
去處理獲取到的Intent
package com.google.firebase.messaging;
/* compiled from: com.google.firebase:firebase-messaging@@20.3.0 */
public abstract class EnhancedIntentService extends Service {
private Binder binder;
final ExecutorService executor = FcmExecutors.newIntentHandleExecutor();
private int lastStartId;
private final Object lock = new Object();
private int runningTasks = 0;
// 4. abstract 方法,看FirebaseMessagingService
public abstract void handleIntent(Intent intent);
public boolean handleIntentOnMainThread(Intent intent) {
return false;
}
/* access modifiers changed from: private */
// 1.處理Intent
public Task<Void> processIntent(Intent intent) {
// 默認(rèn)返回false,跳過
if (handleIntentOnMainThread(intent)) {
return Tasks.forResult(null);
}
TaskCompletionSource taskCompletionSource = new TaskCompletionSource();
// 2.重點(diǎn)是這里,執(zhí)行EnhancedIntentService$$Lambda$0 runnable
this.executor.execute(new EnhancedIntentService$$Lambda$0(this, intent, taskCompletionSource));
return taskCompletionSource.getTask();
}
/* access modifiers changed from: package-private */
public final /* synthetic */ void lambda$onStartCommand$1$EnhancedIntentService(Intent intent, Task task) {
finishTask(intent);
}
/* access modifiers changed from: package-private */
public final /* synthetic */ void lambda$processIntent$0$EnhancedIntentService(Intent intent, TaskCompletionSource taskCompletionSource) {
try {
// 3.匿名函數(shù),繞個圈之后會到這里, handleIntent(intent)
handleIntent(intent);
} finally {
taskCompletionSource.setResult(null);
}
}
}
processIntent()
之后會去調(diào)用 handleIntent()
, FirebaseMessagingService
實(shí)現(xiàn)了 handleIntent()
方法.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.google.firebase.messaging;
public class FirebaseMessagingService extends EnhancedIntentService {
@NonNull
public static final String ACTION_DIRECT_BOOT_REMOTE_INTENT = "com.google.firebase.messaging.RECEIVE_DIRECT_BOOT";
private static final Queue<String> recentlyReceivedMessageIds;
public void handleIntent(@NonNull Intent intent) {
String var2 = intent.getAction();
// 當(dāng)action不等于"com.google.android.c2dm.intent.RECEIVE" 和 "com.google.firebase.messaging.RECEIVE_DIRECT_BOOT" 時進(jìn)去
// 所以我們跳過
if (!"com.google.android.c2dm.intent.RECEIVE".equals(var2) && !"com.google.firebase.messaging.RECEIVE_DIRECT_BOOT".equals(var2)) {
if ("com.google.firebase.messaging.NEW_TOKEN".equals(var2)) {
this.onNewToken(intent.getStringExtra("token"));
} else {
String var3 = String.valueOf(intent.getAction());
String intent1 = "Unknown intent action: ";
if (var3.length() != 0) {
var3 = intent1.concat(var3);
} else {
var3 = new String(intent1);
}
Log.d("FirebaseMessaging", var3);
}
} else {
// 已知FCM的messaging action是"com.google.android.c2dm.intent.RECEIVE"
this.handleMessageIntent(intent);
}
}
// 處理messaging Intent
private void handleMessageIntent(Intent var1) {
// 之前沒有接收的話,會call this.passMessageIntentToSdk()
if (!this.alreadyReceivedMessage(var1.getStringExtra("google.message_id"))) {
this.passMessageIntentToSdk(var1);
}
}
// 將messaging intent 交給 SDK
private void passMessageIntentToSdk(Intent var1) {
String var2 = var1.getStringExtra("message_type");
if (var2 == null) {
var2 = "gcm";
}
// 我們的message_type 就是 gcm , 所以var3 = 0
byte var3;
label37: {
switch(var2.hashCode()) {
case -2062414158:
if (var2.equals("deleted_messages")) {
var3 = 1;
break label37;
}
break;
case 102161:
if (var2.equals("gcm")) {
var3 = 0;
break label37;
}
break;
case 814694033:
if (var2.equals("send_error")) {
var3 = 3;
break label37;
}
break;
case 814800675:
if (var2.equals("send_event")) {
var3 = 2;
break label37;
}
}
var3 = -1;
}
// 我們的message_type 就是 gcm , 所以var3 = 0
switch(var3) {
case 0:
// MessagingAnalytics FCM數(shù)據(jù)分析,不用管
MessagingAnalytics.logNotificationReceived(var1);
// 分發(fā)消息!!!
this.dispatchMessage(var1);
return;
case 1:
this.onDeletedMessages();
return;
case 2:
this.onMessageSent(var1.getStringExtra("google.message_id"));
return;
case 3:
var2 = this.getMessageId(var1);
SendException var6 = new SendException(var1.getStringExtra("error"));
this.onSendError(var2, var6);
return;
default:
String var4 = "Received message with unknown type: ";
if (var2.length() != 0) {
var4 = var4.concat(var2);
} else {
String var5 = new String(var4);
var4 = var5;
}
Log.w("FirebaseMessaging", var4);
}
}
// 分發(fā)已經(jīng)收到的message
private void dispatchMessage(Intent var1) {
Bundle var2 = var1.getExtras();
if (var2 == null) {
var2 = new Bundle();
}
// 將收到的intent bundle值進(jìn)行加工
var2.remove("androidx.content.wakelockid");
if (NotificationParams.isNotification(var2)) {
NotificationParams var3 = new NotificationParams(var2);
ExecutorService var4 = FcmExecutors.newNetworkIOExecutor();
DisplayNotification var5 = new DisplayNotification(this, var3, var4);
boolean var7 = false;
boolean var10;
try {
var7 = true;
// 判斷APP是否在后臺,
// true,則APP在后臺, 系統(tǒng)自己顯示出來
// false,則APP在前臺,APP自己處理
var10 = var5.handleNotification();
var7 = false;
} finally {
if (var7) {
var4.shutdown();
}
}
if (var10) {
// true, 系統(tǒng)需要自己顯示通知
var4.shutdown();
return;
}
var4.shutdown();
if (MessagingAnalytics.shouldUploadScionMetrics(var1)) {
MessagingAnalytics.logNotificationForeground(var1);
}
}
RemoteMessage var9 = new RemoteMessage(var2);
// APP在前臺, APP自己處理intent
this.onMessageReceived(var9);
}
}
package com.google.firebase.messaging;
class DisplayNotification {
private final Executor networkIoExecutor;
private final Context context;
private final NotificationParams params;
boolean handleNotification() {
if (this.params.getBoolean("gcm.n.noui")) {
return true;
} else if (this.isAppForeground()) {
// 如果APP在前臺,則返回false
return false;
} else {
// APP在后臺,則系統(tǒng)就開始build notification出來顯示
//題外話:細(xì)看可以知道, ImageDownload 會限制大小為1M
ImageDownload var1 = this.startImageDownloadInBackground();
DisplayNotificationInfo var2 = CommonNotificationBuilder.createNotificationInfo(this.context, this.params);
this.waitForAndApplyImageDownload(var2.notificationBuilder, var1);
this.showNotification(var2);
return true;
}
}
// 判斷是否APP在前臺
private boolean isAppForeground() {
if (((KeyguardManager)this.context.getSystemService("keyguard")).inKeyguardRestrictedInputMode()) {
return false;
} else {
if (!PlatformVersion.isAtLeastLollipop()) {
SystemClock.sleep(10L);
}
//獲取當(dāng)前進(jìn)程Pid
int var1 = Process.myPid();
//獲取所有的正在運(yùn)行的進(jìn)程信息
List var3 = ((ActivityManager)this.context.getSystemService("activity")).getRunningAppProcesses();
if (var3 != null) {
Iterator var4 = var3.iterator();
// 遍歷所有進(jìn)程信息
while(var4.hasNext()) {
RunningAppProcessInfo var2 = (RunningAppProcessInfo)var4.next();
// 如果正在運(yùn)行的進(jìn)程Pid 等于 當(dāng)前進(jìn)程Pid
// 而且進(jìn)程的importance等于100
// 則返回true
// 否則返回false
if (var2.pid == var1) {
if (var2.importance == 100) {
return true;
}
return false;
}
}
}
return false;
}
}
}
所以綜上可知,其實(shí)前臺后臺的處理,其實(shí)是FirebaseMessagingService
收到Intent后做的二次處理,當(dāng)APP在前臺的時候,就會回調(diào)onMessageReceived()
方法,否則就會系統(tǒng)自行顯示出來.所以如果有特定需要可以重寫FirebaseMessagingService
或者 直接繼承 EnhancedIntentService
去做一系列操作.
總結(jié)
通過一系列分析,我們了解到了以下幾點(diǎn):
- Firebase 是如何跟FCM SDK通信,而FCM SDK又是如何讓通知欄收到通知的,
- 明白了前后臺接收信息的機(jī)制,
- 為什么國內(nèi)ROM kill APP后收不到message,(國內(nèi)ROM從最近程序中移走APP,會直接kill APP,所以service也被kill了,處理不了IPC過來的intent)