文章

02 .FCM进阶

02 .FCM进阶

FCM 原理

FirebaseMessagingService 分发消息

在系统源码中跟踪到FirebaseMessagingService 负责分发通知消息到 SDK。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class FirebaseMessagingService extends EnhancedIntentService {
	// ... 实现EnhancedIntentService
	public void handleIntent(Intent intent) {  
	    String action = intent.getAction();  
	    if (!"com.google.android.c2dm.intent.RECEIVE".equals(action) && !"com.google.firebase.messaging.RECEIVE_DIRECT_BOOT".equals(action)) {  
	        if ("com.google.firebase.messaging.NEW_TOKEN".equals(action)) {  
	            this.onNewToken(intent.getStringExtra("token"));  
	        } else {  
	            Log.d("FirebaseMessaging", "Unknown intent action: " + intent.getAction());  
	        }  
	    } else {  
	        this.handleMessageIntent(intent);  
	    }  
	}
	private void handleMessageIntent(Intent intent) {  
	    String messageId = intent.getStringExtra("google.message_id");  
	    if (!this.alreadyReceivedMessage(messageId)) {  
	        this.passMessageIntentToSdk(intent);  
	    }  
	}
	private boolean alreadyReceivedMessage(String messageId) {  
	    if (TextUtils.isEmpty(messageId)) {  
	        return false;  
	    } else if (recentlyReceivedMessageIds.contains(messageId)) {  
	        if (Log.isLoggable("FirebaseMessaging", 3)) {  
	            Log.d("FirebaseMessaging", "Received duplicate message: " + messageId);  
	        }  
	        return true;  
	    } else {  
	        if (recentlyReceivedMessageIds.size() >= 10) {  
	            recentlyReceivedMessageIds.remove();  
	        } 
	        recentlyReceivedMessageIds.add(messageId);  
	        return false;    }  
	}
	private void passMessageIntentToSdk(Intent intent) {  
		String messageType = intent.getStringExtra("message_type");  
		if (messageType == null) {  
			messageType = "gcm";  
		}  
		switch (messageType) {  
			case "gcm":  
				MessagingAnalytics.logNotificationReceived(intent);  
				this.dispatchMessage(intent);  
				break;        case "deleted_messages":  
				this.onDeletedMessages();  
				break;        case "send_event":  
				this.onMessageSent(intent.getStringExtra("google.message_id"));  
				break;        case "send_error":  
				this.onSendError(this.getMessageId(intent), new SendException(intent.getStringExtra("error")));  
				break;        default:  
				Log.w("FirebaseMessaging", "Received message with unknown type: " + messageType);  
		}  
	}
	private void dispatchMessage(Intent intent) {  
	    Bundle data = intent.getExtras();  
	    if (data == null) {  
	        data = new Bundle();  
	    }  
	    data.remove("androidx.content.wakelockid");  
	    if (NotificationParams.isNotification(data)) {  
	        NotificationParams params = new NotificationParams(data);  
	        ExecutorService executor = FcmExecutors.newNetworkIOExecutor();  
	        DisplayNotification displayNotification = new DisplayNotification(this, params, executor);  
	  
	        label55: {  
	            try {  
	                if (!displayNotification.handleNotification()) {  
	                    break label55;  
	                }  
	            } finally {  
	                executor.shutdown();  
	            }  
	            return;  
	        }  
	        if (MessagingAnalytics.shouldUploadScionMetrics(intent)) {  
	            MessagingAnalytics.logNotificationForeground(intent);  
	        }  
	    }
	    this.onMessageReceived(new RemoteMessage(data));  
	}
	// ....
}

handleIntent() 调用 handleMessageIntent(),调用 passMessageIntentToSdk(),最后调用 dispatchMessage(),在 dispatchMessage() 调用 onMessageReceived(),该方法就是我们自定义的 Service 要复写的方法。

EnhancedIntentService 负责解析处理收到的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class EnhancedIntentService extends Service {
	public final synchronized IBinder onBind(Intent intent) {  
	    if (Log.isLoggable("EnhancedIntentService", 3)) {  
	        Log.d("EnhancedIntentService", "Service received bind request");  
	    }  
	    if (this.binder == null) {  
	        this.binder = new WithinAppServiceBinder(new WithinAppServiceBinder.IntentHandler() {  
	            @KeepForSdk  
	            public Task<Void> handle(Intent intent) {  
	                return EnhancedIntentService.this.processIntent(intent);  
	            }  
	        });  
	    }  
	    return this.binder;  
	}
	@MainThread  
	private Task<Void> processIntent(Intent intent) {  
	    if (this.handleIntentOnMainThread(intent)) {  
	        return Tasks.forResult((Object)null);  
	    } else {  
	        TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource();  
	        this.executor.execute(() -> {  
	            try {  
	                this.handleIntent(intent);  
	            } finally {  
	                taskCompletionSource.setResult((Object)null);  
	            }  
	  
	        });  
	        return taskCompletionSource.getTask();  
	    }  
	}
	// 实现类就是FirebaseMessagingService
	public abstract void handleIntent(Intent var1);
}

FirebaseInstanceIdReceiver

消息是哪里接收来的,跟踪到是通过 AIDL 形式把广播中的消息传到 Service 中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class FcmBroadcastProcessor {
	public Task<Integer> process(Intent intent) {  
		String binaryData64 = intent.getStringExtra("gcm.rawData64");  
		if (binaryData64 != null) {  
			intent.putExtra("rawData", Base64.decode(binaryData64, 0));  
			intent.removeExtra("gcm.rawData64");  
		}
		return this.startMessagingService(this.context, intent);  
	}
	@SuppressLint({"InlinedApi"})  
	public Task<Integer> startMessagingService(Context context, Intent intent) {  
	    boolean subjectToBackgroundCheck = PlatformVersion.isAtLeastO() && context.getApplicationInfo().targetSdkVersion >= 26;  
	    boolean isHighPriority = (intent.getFlags() & 268435456) != 0;  
	    if (subjectToBackgroundCheck && !isHighPriority) {  
	        return bindToMessagingService(context, intent);  
	    } else {  
	        Task<Integer> startServiceResult = Tasks.call(this.executor, () -> {  
	            return ServiceStarter.getInstance().startMessagingService(context, intent);  
	        });  
	        return startServiceResult.continueWithTask(this.executor, (r) -> {  
	            return PlatformVersion.isAtLeastO() && (Integer)r.getResult() == 402 ? bindToMessagingService(context, intent).continueWith(Runnable::run, (t) -> {  
	                return 403;  
	            }) : r;  
	        });  
	    }  
	}  
	private static Task<Integer> bindToMessagingService(Context context, Intent intent) {  
	    if (Log.isLoggable("FirebaseMessaging", 3)) {  
	        Log.d("FirebaseMessaging", "Binding to service");  
	    }  
	  
	    if (ServiceStarter.getInstance().hasWakeLockPermission(context)) {  
	        WakeLockHolder.sendWakefulServiceIntent(context, getServiceConnection(context, "com.google.firebase.MESSAGING_EVENT"), intent);  
	    } else {  
	        getServiceConnection(context, "com.google.firebase.MESSAGING_EVENT").sendIntent(intent);  
	    }  
	  
	    return Tasks.forResult(-1);  
	}
}

FirebaseInstanceIdReceiveronMessageReceive 通过 FcmBroadcastProcessor 将数据发给 FirebaseMessagingService 分发消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final class FirebaseInstanceIdReceiver extends CloudMessagingReceiver {  
    private static final String TAG = "FirebaseMessaging";  
    public FirebaseInstanceIdReceiver() {  
    }  
    private static Intent createServiceIntent(@NonNull Context context, @NonNull String action, @NonNull Bundle data) {  
        return (new Intent(action)).putExtras(data);  
    }  
    @WorkerThread  
    protected int onMessageReceive(@NonNull Context context, @NonNull CloudMessage message) {  
        try {  
            return (Integer)Tasks.await((new FcmBroadcastProcessor(context)).process(message.getIntent()));  
        } catch (InterruptedException | ExecutionException var4) {  
            Log.e("FirebaseMessaging", "Failed to send message to service.", var4);  
            return 500;  
        }  
    }  
    @WorkerThread  
    protected void onNotificationDismissed(@NonNull Context context, @NonNull Bundle data) {  
        Intent notificationDismissedIntent = createServiceIntent(context, "com.google.firebase.messaging.NOTIFICATION_DISMISS", data);  
        if (MessagingAnalytics.shouldUploadScionMetrics(notificationDismissedIntent)) {  
            MessagingAnalytics.logNotificationDismiss(notificationDismissedIntent);  
        }  
  
    }  
}

最终的广播来源 CloudMessagingReceiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class CloudMessagingReceiver extends BroadcastReceiver {
	@WorkerThread  
	private final int zzb(@NonNull Context var1, @NonNull Intent var2) {  
	    if (var2.getExtras() == null) {  
	        return 500;  
	    } else {  
	        String var3 = var2.getStringExtra("google.message_id");  
	        Task var12;  
	        if (TextUtils.isEmpty(var3)) {  
	            var12 = Tasks.forResult((Object)null);  
	        } else {  
	            Bundle var4 = new Bundle();  
	            var4.putString("google.message_id", var3);  
	            var12 = zzs.zzb(var1).zzc(2, var4);  
	        }  
	        CloudMessage var14 = new CloudMessage(var2);  
	        int var8 = this.onMessageReceive(var1, var14);  
	        try {  
	            long var5 = TimeUnit.SECONDS.toMillis(1L);  
	            Tasks.await(var12, var5, TimeUnit.MILLISECONDS);  
	        } catch (InterruptedException | TimeoutException | ExecutionException var7) {  
	            String var9 = String.valueOf(var7);  
	            String var10 = String.valueOf(var9);  
	            int var11 = var10.length();  
	            StringBuilder var13 = new StringBuilder(var11 + 20);  
	            var13.append("Message ack failed: ");  
	            var13.append(var9);  
	            Log.w("CloudMessagingReceiver", var13.toString());  
	        }  
	        return var8;  
	    }  
	}
}

结论:FCM 消息是通过广播发出来的

FCM 进阶

FCM diagnostic

如何进入

Firebase Support

  • Android 手机拨号盘输入 *#*#426#*#* 进入 FCM Diagnostics。可以了解一下为啥自己的 Android 手机总是收不到推送消息。
  • adb 命令
1
adb shell am start -n com.google.android.gms/.gcm.GcmDiagnostics
  • Connected 状态
![300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240905194106.png)

FCM diagnostic 排查 push 收到情况

![300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20241219205345.png)

调试后台消息

通过字节码插桩到 FCM 收到消息处,添加自己的逻辑

1
2
3
4
5
6
7
8
9
10
@TargetClass("com.google.firebase.messaging.FirebaseMessagingService")  
@Insert("dispatchMessage")  
private void dispatchMessageFirebase(Intent intent) {  
    try {  
        Origin.callVoid();  
    } catch (Throwable e) { 
        Logger.e("push", "error", e);  
    }  
    // xxx
}

FCM 区分 debug 和 release

  1. 在 app module 创建 debug 和 release 文件夹
  2. 拷贝 google-services. Json 到 2 个文件夹中去
  3. Firebase 会自动选择

FCM 放单独的进程

FCM 多进程改造

  1. FCM 所有相关的 Service 和 BroadcastReceiver 放到 :push 进程中去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<service
    android:name="me.hacket.assistant.samples.google.firebase.fcm.MyFirebaseMessagingService"
    android:exported="false"
    android:process=":push">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
<receiver
    android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
    android:permission="com.google.android.c2dm.permission.SEND"
    android:exported="true"
    android:process=":push"
    tools:node="replace">

    <intent-filter>

        <action
            android:name="com.google.android.c2dm.intent.RECEIVE" />
    </intent-filter>
</receiver>
<service
    android:name="com.google.firebase.messaging.FirebaseMessagingService"
    android:exported="false"
    android:directBootAware="true"
    android:process=":push"
    tools:node="replace">

    <intent-filter
        android:priority="-500">

        <action
            android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
  1. Firebase 初始化,只在: push 进程初始化;要注意如果是在子进程要操作的库也是需要初始化的
1
2
3
if (!isMainProcess(getApplicationContext())) {
	FirebaseApp.initializeApp(this);
}
  1. 收到 FCM 的消息后,通过 IPC 机制回到主进程进行操作,如果确定不需要回到主进程也可以不用回;可以通过广播的方式回到主进程中来

FCM 多进程遇到的问题

Default FirebaseApp is not initialized in this process

现象:java. Lang. RuntimeException: Unable to create application me. Hacket. Assistant. Debug. DebugApp: java. Lang. IllegalStateException: Default FirebaseApp is not initialized in this process me. Hacket. Assistant. Samples: push. Make sure to call FirebaseApp. InitializeApp (Context) first.
原因:多进程初始化的问题
解决:MyFirebaseMessagingService 放到指定的进程,就在需要在这个进程中对 Firebase 初始化

Fcm 多进程,会导致 App 在前台,但还是判定为后台 App

FCM 遇到的问题

进程存活与被杀死情况下,FCM 消息的展示情况的现象

退入后台,无操作停留 2 小时

通过 usb 连接手机,进入 ADB 模式,执行 ps 命令后,进程仍然存在,说明并未被系统回收资源。此时发送推送消息,如预期,通知消息正常展示。

退入后台,通过最近任务,滑动杀死 APP

同样,进入 ADB 后,通过 ps 命令查看进程状态,进程已不存在,但不确定资源是否被系统及时回收。此时发送推送消息,通知消息能正常展示

进入应用详情,强行停止 APP

不出意料的,进程肯定已经不存在了,ps 命令查看也是如此。同样的,不能确定资源是否被系统及时回收。此时发送推送消息,通知消息却不能正常展示。

由此推测,FCM 通道不依赖 APP 的进程是否存活。这个特点,是优于 APP 自己实现推送通知的。

应用被用户主动 kill 后,系统直接把死亡进程所属的广播,都直接过滤掉了,从而不对其发送广播。 在 Android 系统中,应用被用户主动 kill 后,在 AMS 中会调用 finishForceStopPackageLocked() 中发送内部广播:ACTION_PACKAGE_RESTARTED,它会限制包的自启或者通知移除等等。而广播是通过 sendBroadcast 来发送的,在 AMS broadcastIntentLocked 中,明确添加了 intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); 该 Flag 后续会从系统中查找缓存,过滤广播的代码:

1
registeredReceivers =mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, userId);

IntentResolver.queryIntent 方法内部有调用:

1
2
3
4
5
6
final boolean excludingStopped = intent.isExcludingStopped();
if (excludingStopped && isFilterStopped(filter, userId)) {  // -------这里会过滤Stopped 和进程Stop的
    if (debug) {
         Slog.v(TAG, " Filter's target is stopped; skipping");
    }
}

isExcludingStopped() 的定义代码:

1
2
3
public boolean isExcludingStopped() {
  return (mFlags&(FLAG_EXCLUDE_STOPPED_PACKAGES|FLAG_INCLUDE_STOPPED_PACKAGES))== FLAG_EXCLUDE_STOPPED_PACKAGES;
} 

官方的 issues 也有类似的官方回复: image.png|900 GCM: broadcast intent cancel,can’t receive notification #2917

重启手机

重启手机后,由于应用已没有接收重启系统的广播权限,进程肯定已经不存在了,同时,资源肯定也是被系统回收了的。此时发送推送消息,通知依然能正常展示。由此进一步确认,系统缓存了应用的某种状态,FCM 会依据该状态来决定是否展示通知。

其他

AUTHENTICATION_FAILED

FCM 收不到推送

  1. 科学上网
  2. 集成了 GoogleService 框架
  3. 通知权限是否打开
  4. 最好使用国外的真机测试(ps: 港版的手机也行),首先测试一下 Facebook 应用在后台被干掉的情形下是否能收到好友消息,如果不能收到即可排除是手机自身的问题;如果某款手机的 Facebook 能在后台被干掉的情形下收到好友的消息,即这款手机是符合 google 三件套系列的原生手机,一般这种情况下,测试自己的应用也能在被干掉的情形下收到 firebase 后台推送的测试消息,如果这时候还收不到说明自己集成的代码有问题,得好好自查一下了。
  5. 国内手机情况
    • VIVO 没自启动权限楞是不给收 GOOGLE 的推送,小米

从最近列表划掉收不到推送

华为 P 30,从最近列表 Kill App 后,收不到推送了

https://www.v2ex.com/t/604800

FIS_AUTH_ERROR

java.io.IOException: FIS_AUTH_ERROR

似乎是网络问题

FCM token 270 天失效问题

Firebase 会清除 270 days 未连接的 FCM Token,使其过期

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240625204139.png)

FCM 和翻墙

Ref

本文由作者按照 CC BY 4.0 进行授权