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);
}
}
在 FirebaseInstanceIdReceiver
的 onMessageReceive
通过 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
如何进入
- Android 手机拨号盘输入
*#*#426#*#*
进入FCM Diagnostics
。可以了解一下为啥自己的 Android 手机总是收不到推送消息。 - adb 命令
1
adb shell am start -n com.google.android.gms/.gcm.GcmDiagnostics
- Connected 状态
 |
FCM diagnostic 排查 push 收到情况
 |
调试后台消息
通过字节码插桩到 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
- 在 app module 创建 debug 和 release 文件夹
- 拷贝 google-services. Json 到 2 个文件夹中去
- Firebase 会自动选择
- 配置多个项目
https://firebase.google.com/docs/projects/multiprojects?hl=zh-CN#support_multiple_environments_in_your_android_application - How to setup a different Firebase project for Debug and Release environments
https://medium.com/@brunolemos/how-to-setup-a-different-firebase-project-for-debug-and-release-environments-157b40512164
FCM 放单独的进程
FCM 多进程改造
- 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>
- Firebase 初始化,只在: push 进程初始化;要注意如果是在子进程要操作的库也是需要初始化的
1
2
3
if (!isMainProcess(getApplicationContext())) {
FirebaseApp.initializeApp(this);
}
- 收到 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 也有类似的官方回复: GCM: broadcast intent cancel,can’t receive notification #2917
重启手机
重启手机后,由于应用已没有接收重启系统的广播权限,进程肯定已经不存在了,同时,资源肯定也是被系统回收了的。此时发送推送消息,通知依然能正常展示。由此进一步确认,系统缓存了应用的某种状态,FCM 会依据该状态来决定是否展示通知。
其他
AUTHENTICATION_FAILED
FCM 收不到推送
- 科学上网
- 集成了
GoogleService
框架 - 通知权限是否打开
- 最好使用国外的真机测试(ps: 港版的手机也行),首先测试一下 Facebook 应用在后台被干掉的情形下是否能收到好友消息,如果不能收到即可排除是手机自身的问题;如果某款手机的 Facebook 能在后台被干掉的情形下收到好友的消息,即这款手机是符合 google 三件套系列的原生手机,一般这种情况下,测试自己的应用也能在被干掉的情形下收到 firebase 后台推送的测试消息,如果这时候还收不到说明自己集成的代码有问题,得好好自查一下了。
- 国内手机情况
- VIVO 没自启动权限楞是不给收 GOOGLE 的推送,小米
从最近列表划掉收不到推送
华为 P 30,从最近列表 Kill App 后,收不到推送了
FIS_AUTH_ERROR
java.io.IOException: FIS_AUTH_ERROR
似乎是网络问题
FCM token 270 天失效问题
Firebase 会清除 270 days 未连接的 FCM Token,使其过期
 |
FCM 和翻墙
- 一加手机 fcm/gcm 推送问题 - V2EX
- 国内 Google FCM 推送全挂了 - V2EX
- FCM 与系统 VPN 服务的交互更改导致部分情况下 FCM 绕过 v2rayNG 发起连接 · Issue #2641 · 2dust/v2rayNG · GitHub