Android Firebase Cloud Message(FCM) 推送机制分析(下)

简述

本文主要讲述Android Firebase Cloud Message(FCM) 推送机制.

架构

主要流程:

  1. 业务backend Call FCM backend
  2. FCM backend 推消息到 Android transport layer(ATL)
  3. ATL 推消息到 APP上的FCM SDK
  4. APP处理FCM SDK 回调


    diagram-FCM.png

流程

1. 业务backend Call FCM backend

请参看Android Firebase Cloud Message(FCM) 推送机制分析(上)

2. FCM backend 推消息到 Android transport layer(ATL)

请参看Android Firebase Cloud Message(FCM) 推送机制分析(上)

3. ATL 推消息到 APP上的FCM SDK

3.1 总览

GoogleMobile Service(GMS) -> Firebase Cloud Message (FCM) -> APP FCM SDK
FirebaseInstanceIdReceiver 继承 GMSCloudMessagingReceiver , 之后通过 Binder 机制 IPC 到目前 APP 中的 FCM SDK

3.2 FirebaseInstanceIdReceiver 接收message

3.2.1 FirebaseInstanceIdReceiver 通过 onMessageReceive() 接收 message

可以看到FirebaseInstanceIdReceiver 通过 onMessageReceive() 接收 message , 那又是哪里会去调用到 FirebaseInstanceIdReceiver#onMessageReceive() 呢?我们可以看到这是一个Override方法,我们去父类 CloudMessagingReceiver 找答案

FirebaseInstanceIdReceiver#onMessageReceive.png

3.2.2 CloudMessagingReceiver 调用 onMessageReceive()

可以看到 CloudMessagingReceiver 是继承 BroadcastReceiver, 所以CloudMessagingReceiver 是个广播接收器,所以我们先看 onReceive() 方法.
onReceive() 方法很短,重点在 new zzd(this, var2, var1, var3, var4) 这句代码.

CloudMessagingReceiver#onReceive()

点进去可以知道 zzd 是一个 Runnable , 我们重点查看 run() 方法可知里面只有一行代码, 就是 CloudMessagingReceiver#zza(), 继续追!
zzd#run()

因为这个 CloudMessagingReceiver#zza() 被隐藏了,万幸我们在万能的Github找到了答案. 里面会call 到 zza(@NonNull Context var1, @NonNull Intent var2)zzb(@NonNull Context var1, @NonNull Intent var2) 方法, 我们先看 zzb() 方法.
CloudMessagingReceiver#zza()

zzb() 我们终于如愿见到 onMessageReceive() 方法了!
CloudMessagingReceiver#onMessageReceive()

以上就是继承GMS CloudMessagingReceiverFirebaseInstanceIdReceiver 接收广播后,调用 onMessageReceive() 的过程

3.3 IPC 到目前 APP 中的 FCM SDK

3.3.1 FirebaseInstanceIdReceiver 触发IPC

`FirebaseInstanceIdReceiver` 触发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 process(Intent intent) {
         //准备工作...
        // 启动 FirebaseMessagingService
        return startMessagingService(this.context, intent);
    }

    public Task startMessagingService(Context context2, Intent intent) {
        //准备工作...
       // binder 绑定FirebaseMessagingService
        return bindToMessagingService(context2, intent);
    }

    private static Task bindToMessagingService(Context context2, Intent intent) {
        // 划重点!!!
        // 重点在这里, 实例化 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 intentQueue;
    private final ScheduledExecutorService scheduledExecutorService;

    // IPC发送Intent
    public synchronized Task 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 到 队列
        this.intentQueue.add(bindRequest);
        // 消化队列
        flushQueue();
        return bindRequest.getTask();
    }

    // 处理队列
    private synchronized void flushQueue() {
        if (Log.isLoggable("FirebaseInstanceId", 3)) {
            Log.d("FirebaseInstanceId", "flush queue called");
        }
        // 循环清空队列
        while (!this.intentQueue.isEmpty()) {
            if (Log.isLoggable("FirebaseInstanceId", 3)) {
                Log.d("FirebaseInstanceId", "found intent to be delivered");
            }
            // binder 为空时,再次创建
            if (this.binder == null || !this.binder.isBinderAlive()) {
                startConnectionIfNeeded();
                return;
            }
            if (Log.isLoggable("FirebaseInstanceId", 3)) {
                Log.d("FirebaseInstanceId", "binder is alive, sending the intent.");
            }
            // 划重点!!
            // 关键地方,发送数据到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 回调 [2]

4.1 总览

具体使用请参考 [在Android应用中处理消息](需要翻墙)
FirebaseInstanceIdReceiver 收到广播后,通过IPC start Service , FirebaseMessagingService 处理收到的 Intent 后, 经过判断当前APP是否在前台, 之后调用 FirebaseMessagingService #onMessageReceived() 或 启动 APP的launchActivity.

4.2 FirebaseMessagingService 接收 Intent

已知一般处理前台消息时,继承 FirebaseMessagingService 后,重写 onMessageReceived() 方法.

class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(msg: RemoteMessage) {
        // 前台收到消息,会回调此方法传递RemoteMessage
        super.onMessageReceived(msg)
        Log.d(TAG, Thread.currentThread().name + " " + msg.notification?.body)
    }
}

接下来我们查看是如何接收并传递到这里的.
查看 FirebaseMessagingService 可知继承 EnhancedIntentService , 本质是 Service. 因为是 Service , 我们重点查看 onStartCommand()方法,这个方式是 startService() 的时候会触发的方法. 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不为空就调用processIntent()处理Intent
        Task processIntent = processIntent(startCommandIntent);
        if (processIntent.isComplete()) {
            finishTask(intent);
            return 2;
        }
        processIntent.addOnCompleteListener(EnhancedIntentService$$Lambda$1.$instance, (OnCompleteListener) new EnhancedIntentService$$Lambda$2(this, intent));
        return 3;
    }

    /* access modifiers changed from: protected */
    public Intent getStartCommandIntent(Intent intent) {
        // 看源码可知, FirebaseMessagingService重写了这个方法
        return intent;
    }
}

根据 EnhancedIntentService#onStartCommand() 方法可以知道, 当 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 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 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();
    }
}

根据源码可知, ServiceStarter 是一个单例,一个进程只有一个 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 processIntent(Intent intent) {
        // 默认返回false,跳过
        if (handleIntentOnMainThread(intent)) {
            return Tasks.forResult(null);
        }
        TaskCompletionSource taskCompletionSource = new TaskCompletionSource();
        // 2.重点是这里,执行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.匿名函数,绕个圈之后会到这里, handleIntent(intent)
            handleIntent(intent);
        } finally {
            taskCompletionSource.setResult(null);
        }
    }
}

processIntent() 之后会去调用 handleIntent() , FirebaseMessagingService 实现了 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 recentlyReceivedMessageIds;

    public void handleIntent(@NonNull Intent intent) {
        String var2 = intent.getAction();
        // 当action不等于"com.google.android.c2dm.intent.RECEIVE" 和 "com.google.firebase.messaging.RECEIVE_DIRECT_BOOT" 时进去
        // 所以我们跳过
        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数据分析,不用管
            MessagingAnalytics.logNotificationReceived(var1);
            // 分发消息!!!
            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);
        }
    }

    // 分发已经收到的message
    private void dispatchMessage(Intent var1) {
        Bundle var2 = var1.getExtras();
        if (var2 == null) {
            var2 = new Bundle();
        }
        // 将收到的intent bundle值进行加工
        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在后台, 系统自己显示出来
               // false,则APP在前台,APP自己处理
                var10 = var5.handleNotification();
                var7 = false;
            } finally {
                if (var7) {
                    var4.shutdown();
                }
            }

            if (var10) {
              // true, 系统需要自己显示通知
                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在后台,则系统就开始build notification出来显示
            //题外话:细看可以知道, 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);
            }
            //获取当前进程Pid
            int var1 = Process.myPid();
            //获取所有的正在运行的进程信息
            List var3 = ((ActivityManager)this.context.getSystemService("activity")).getRunningAppProcesses();
            if (var3 != null) {
                Iterator var4 = var3.iterator();

                // 遍历所有进程信息
                while(var4.hasNext()) {
                    RunningAppProcessInfo var2 = (RunningAppProcessInfo)var4.next();
                    // 如果正在运行的进程Pid 等于 当前进程Pid
                    // 而且进程的importance等于100
                    // 则返回true
                    // 否则返回false
                    if (var2.pid == var1) {
                        if (var2.importance == 100) {
                            return true;
                        }
                        return false;
                    }
                }
            }

            return false;
        }
    }
}

所以综上可知,其实前台后台的处理,其实是FirebaseMessagingService 收到Intent后做的二次处理,当APP在前台的时候,就会回调onMessageReceived()方法,否则就会系统自行显示出来.所以如果有特定需要可以重写FirebaseMessagingService 或者 直接继承 EnhancedIntentService 去做一系列操作.

总结

通过一系列分析,我们了解到了以下几点:

  1. Firebase 是如何跟FCM SDK通信,而FCM SDK又是如何让通知栏收到通知的,
  2. 明白了前后台接收信息的机制,
  3. 为什么国内ROM kill APP后收不到message,(国内ROM从最近程序中移走APP,会直接kill APP,所以service也被kill了,处理不了IPC过来的intent)

引用

  1. FCM 架构概览
  2. 在Android应用中处理消息
  3. Github Firebase iid 源码

你可能感兴趣的:(Android Firebase Cloud Message(FCM) 推送机制分析(下))