FCM android 客户端随机停止接收数据消息
FCM android client randomly stops receiving data messages
我已经在我的服务器上使用 Android 客户端实现了 FCM,它运行良好了一段时间。突然,客户端一次停止接收通知 5-10 分钟,之后它会立即收到所有待处理的通知。日志显示服务器正在正确发送消息。
当消息队列卡住时,甚至来自 Firebase 控制台的测试消息都无法通过。
我在官方文档中发现的唯一相关内容是关于 the lifetime of a message:
If the device is not connected to FCM, the message is stored until a connection is established (again respecting the collapse key rules). When a connection is established, FCM delivers all pending messages to the device.
但是我的网络工作正常,其他网络和客户端也出现了这个问题。当我刚安装该应用程序时,我的服务器正在生成并成功接收到一个新令牌。只是服务器到客户端的消息没有通过。
如何判断连接是否建立并修复?或者还有什么原因造成的?
这是我的客户端代码
AndroidManifest.xml
<application
android:name=".App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<service
android:name=".service.NotificationService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
NotificationService.java
public class NotificationService extends FirebaseMessagingService {
private static final String TAG = "NotificationService";
private static String token;
public NotificationService() {
token = PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.getString("firebase-token", null);
}
@Override
public void onNewToken(@NonNull String s) {
super.onNewToken(s);
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task.getException());
return;
}
token = task.getResult().getToken();
Log.d(TAG, "onComplete: token = " + token);
PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.edit()
.putString("firebase-token", token)
.apply();
}
});
}
@Override
public void onMessageReceived(final RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.d(TAG, "Received message of size " + remoteMessage.getData().size());
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
Handler h = new Handler(Looper.getMainLooper());
h.post(new Runnable() {
public void run() {
int left = -1, right = -1;
if (remoteMessage.getData().get("left") != null) {
left = Integer.parseInt(Objects.requireNonNull(remoteMessage.getData()
.get("left")));
}
if (remoteMessage.getData().get("right") != null) {
right = Integer.parseInt(Objects.requireNonNull(remoteMessage.getData()
.get("right")));
}
if (remoteMessage.getData().get("REFRESH") != null) {
Util.sendTitleBroadcast(getApplicationContext(),
left, right,
Util.REFRESH_TITLE);
} else {
Util.sendTitleBroadcast(getApplicationContext(),
left, right,
Util.TITLE_DATA_RECEIVED);
}
}
});
}
}
public static String getToken() {
if (token == null) {
token = PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.getString("firebase-token", null);
}
return token;
}
}
服务器端代码
FirebaseMessagingService.java
public class FirebaseMessagingService {
private static final Logger logger = Logger
.getLogger(FirebaseMessagingService.class);
private static FirebaseMessagingService instance;
/**
* Path to resource file that contains the credentials for Admin SDK.
*/
private final String keyPath = "firebase-adminsdk.json";
/**
* Maps a token list to each topic Object.
*/
private static Map<Object, List<String>> topicTokenMap;
public synchronized static FirebaseMessagingService getInstance() {
if (instance == null) {
instance = new FirebaseMessagingService();
}
return instance;
}
private FirebaseMessagingService() {
init();
}
/**
* Initializes the Firebase service account using the credentials from the
* json file found at keyPath.
*/
private void init() {
try {
InputStream serviceAccount
= getClass().getClassLoader().getResourceAsStream(keyPath);
if (serviceAccount != null) {
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials
.fromStream(serviceAccount))
.setDatabaseUrl(databaseUrl)
.build();
FirebaseApp.initializeApp(options);
logger.debug("FirebaseMessagingService: init successful");
} else {
logger.debug("FirebaseMessagingService: Input stream null from"
+ " path: " + keyPath);
}
} catch (IOException ex) {
logger.debug("FirebaseMessagingService: Failed to get credentials "
+ "from inputStream." + ex.getMessage());
}
}
/**
* Sends the messages given as parameters to the list of tokens mapped to
* the given topic Object.
*
* @param topic
* @param messages
*/
public void sendMessage(Object topic,
Map<String, String> messages) {
logger.debug("FirebaseMessagingService: sending Message "
+ messages.toString());
try {
if (topicTokenMap != null) {
List<String> tokenList = topicTokenMap.get(topic);
if (tokenList != null) {
tokenList.removeAll(Collections.singleton(null));
MulticastMessage message = MulticastMessage.builder()
.putAllData(messages)
.addAllTokens(tokenList)
.build();
FirebaseMessaging.getInstance().sendMulticast(message);
logger.debug("FirebaseMessagingService: message sent successfully");
}
}
} catch (FirebaseMessagingException ex) {
logger.debug(ex.getMessage());
}
}
/**
* Registers the token given as a parameter to the topic Object.
*
* @param topic
* @param token
*/
public void registerTokenToTopic(Object topic, String token) {
if (topicTokenMap == null) {
topicTokenMap = new HashMap<Object, List<String>>();
}
List<String> tokens = topicTokenMap.get(topic);
if (tokens == null) {
tokens = new ArrayList<String>();
}
if (!tokens.contains(token)) {
tokens.add(token);
}
topicTokenMap.put(topic, tokens);
}
/**
* Unregisters all instances of the token given as a parameter from the topic
* given.
*
* @param topic
* @param token
*/
public void unregisterTokenFromTopic(Object topic, String token) {
if (topicTokenMap != null) {
List<String> tokens = topicTokenMap.get(topic);
if (tokens != null) {
tokens.removeAll(Collections.singleton(token));
}
}
}
/**
* Looks for a topic that has a field with the name equal to topicFieldName
* and the value equal to topicFieldValue and sends a notification to the
* tokens subscribed to that topic, telling them to ask for an update.
*
* @param topicFieldName
* @param topicFieldValue
*/
public void notifyChangeForTopicField(String topicFieldName,
Object topicFieldValue) {
logger.debug("FirebaseMessagingService: notifyChangeForTopicField: "
+ "topicFieldValue = " + topicFieldValue.toString());
Map<String, String> messageMap = new HashMap<String, String>();
messageMap.put("REFRESH", "true");
if (topicTokenMap != null
&& topicTokenMap.entrySet() != null
&& topicTokenMap.entrySet().size() > 0) {
Iterator it = topicTokenMap.entrySet().iterator();
while (it.hasNext()) {
try {
Map.Entry pair = (Map.Entry) it.next();
Object topic = pair.getKey();
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField topic = " + topic.toString());
Field field = topic.getClass().getDeclaredField(topicFieldName);
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField field = " + field.toString());
field.setAccessible(true);
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField field contains topic: "
+ (field.get(topic) != null ? "true" : "false"));
if (field.get(topic) != null
&& field.get(topic).equals(topicFieldValue)) {
sendMessage(topic, messageMap);
break;
}
it.remove();
} catch (NoSuchFieldException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (SecurityException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalArgumentException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
}
编辑:经过更多测试后,我发现当一个客户端停止工作时,其他客户端可以正常接收消息
我觉得你的 onMessageReceived()
有点矫枉过正。我认为您不必创建自己的处理程序来处理数据。坚持使用 Firebase 示例:FirebaseExample
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
if (/* Check if data needs to be processed by long running job */ true) {
// For long-running tasks (10 seconds or more) use WorkManager.
scheduleJob()
} else {
// Handle message within 10 seconds
handleNow()
}
}
}
我在这里找到了答案:
发生的事情是我在 5 分钟后随机收到路由器超时。我已通过在 4 分钟后而不是默认的 15 分钟后手动向 FCM 发送心跳来解决此问题。
context.sendBroadcast(new Intent("com.google.android.intent.action.GTALK_HEARTBEAT"));
context.sendBroadcast(new Intent("com.google.android.intent.action.MCS_HEARTBEAT"));
几个月前我实施了此修复,此后问题再未出现。
我已经在我的服务器上使用 Android 客户端实现了 FCM,它运行良好了一段时间。突然,客户端一次停止接收通知 5-10 分钟,之后它会立即收到所有待处理的通知。日志显示服务器正在正确发送消息。 当消息队列卡住时,甚至来自 Firebase 控制台的测试消息都无法通过。
我在官方文档中发现的唯一相关内容是关于 the lifetime of a message:
If the device is not connected to FCM, the message is stored until a connection is established (again respecting the collapse key rules). When a connection is established, FCM delivers all pending messages to the device.
但是我的网络工作正常,其他网络和客户端也出现了这个问题。当我刚安装该应用程序时,我的服务器正在生成并成功接收到一个新令牌。只是服务器到客户端的消息没有通过。
如何判断连接是否建立并修复?或者还有什么原因造成的?
这是我的客户端代码
AndroidManifest.xml
<application
android:name=".App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<service
android:name=".service.NotificationService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
NotificationService.java
public class NotificationService extends FirebaseMessagingService {
private static final String TAG = "NotificationService";
private static String token;
public NotificationService() {
token = PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.getString("firebase-token", null);
}
@Override
public void onNewToken(@NonNull String s) {
super.onNewToken(s);
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task.getException());
return;
}
token = task.getResult().getToken();
Log.d(TAG, "onComplete: token = " + token);
PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.edit()
.putString("firebase-token", token)
.apply();
}
});
}
@Override
public void onMessageReceived(final RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.d(TAG, "Received message of size " + remoteMessage.getData().size());
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
Handler h = new Handler(Looper.getMainLooper());
h.post(new Runnable() {
public void run() {
int left = -1, right = -1;
if (remoteMessage.getData().get("left") != null) {
left = Integer.parseInt(Objects.requireNonNull(remoteMessage.getData()
.get("left")));
}
if (remoteMessage.getData().get("right") != null) {
right = Integer.parseInt(Objects.requireNonNull(remoteMessage.getData()
.get("right")));
}
if (remoteMessage.getData().get("REFRESH") != null) {
Util.sendTitleBroadcast(getApplicationContext(),
left, right,
Util.REFRESH_TITLE);
} else {
Util.sendTitleBroadcast(getApplicationContext(),
left, right,
Util.TITLE_DATA_RECEIVED);
}
}
});
}
}
public static String getToken() {
if (token == null) {
token = PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.getString("firebase-token", null);
}
return token;
}
}
服务器端代码 FirebaseMessagingService.java
public class FirebaseMessagingService {
private static final Logger logger = Logger
.getLogger(FirebaseMessagingService.class);
private static FirebaseMessagingService instance;
/**
* Path to resource file that contains the credentials for Admin SDK.
*/
private final String keyPath = "firebase-adminsdk.json";
/**
* Maps a token list to each topic Object.
*/
private static Map<Object, List<String>> topicTokenMap;
public synchronized static FirebaseMessagingService getInstance() {
if (instance == null) {
instance = new FirebaseMessagingService();
}
return instance;
}
private FirebaseMessagingService() {
init();
}
/**
* Initializes the Firebase service account using the credentials from the
* json file found at keyPath.
*/
private void init() {
try {
InputStream serviceAccount
= getClass().getClassLoader().getResourceAsStream(keyPath);
if (serviceAccount != null) {
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials
.fromStream(serviceAccount))
.setDatabaseUrl(databaseUrl)
.build();
FirebaseApp.initializeApp(options);
logger.debug("FirebaseMessagingService: init successful");
} else {
logger.debug("FirebaseMessagingService: Input stream null from"
+ " path: " + keyPath);
}
} catch (IOException ex) {
logger.debug("FirebaseMessagingService: Failed to get credentials "
+ "from inputStream." + ex.getMessage());
}
}
/**
* Sends the messages given as parameters to the list of tokens mapped to
* the given topic Object.
*
* @param topic
* @param messages
*/
public void sendMessage(Object topic,
Map<String, String> messages) {
logger.debug("FirebaseMessagingService: sending Message "
+ messages.toString());
try {
if (topicTokenMap != null) {
List<String> tokenList = topicTokenMap.get(topic);
if (tokenList != null) {
tokenList.removeAll(Collections.singleton(null));
MulticastMessage message = MulticastMessage.builder()
.putAllData(messages)
.addAllTokens(tokenList)
.build();
FirebaseMessaging.getInstance().sendMulticast(message);
logger.debug("FirebaseMessagingService: message sent successfully");
}
}
} catch (FirebaseMessagingException ex) {
logger.debug(ex.getMessage());
}
}
/**
* Registers the token given as a parameter to the topic Object.
*
* @param topic
* @param token
*/
public void registerTokenToTopic(Object topic, String token) {
if (topicTokenMap == null) {
topicTokenMap = new HashMap<Object, List<String>>();
}
List<String> tokens = topicTokenMap.get(topic);
if (tokens == null) {
tokens = new ArrayList<String>();
}
if (!tokens.contains(token)) {
tokens.add(token);
}
topicTokenMap.put(topic, tokens);
}
/**
* Unregisters all instances of the token given as a parameter from the topic
* given.
*
* @param topic
* @param token
*/
public void unregisterTokenFromTopic(Object topic, String token) {
if (topicTokenMap != null) {
List<String> tokens = topicTokenMap.get(topic);
if (tokens != null) {
tokens.removeAll(Collections.singleton(token));
}
}
}
/**
* Looks for a topic that has a field with the name equal to topicFieldName
* and the value equal to topicFieldValue and sends a notification to the
* tokens subscribed to that topic, telling them to ask for an update.
*
* @param topicFieldName
* @param topicFieldValue
*/
public void notifyChangeForTopicField(String topicFieldName,
Object topicFieldValue) {
logger.debug("FirebaseMessagingService: notifyChangeForTopicField: "
+ "topicFieldValue = " + topicFieldValue.toString());
Map<String, String> messageMap = new HashMap<String, String>();
messageMap.put("REFRESH", "true");
if (topicTokenMap != null
&& topicTokenMap.entrySet() != null
&& topicTokenMap.entrySet().size() > 0) {
Iterator it = topicTokenMap.entrySet().iterator();
while (it.hasNext()) {
try {
Map.Entry pair = (Map.Entry) it.next();
Object topic = pair.getKey();
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField topic = " + topic.toString());
Field field = topic.getClass().getDeclaredField(topicFieldName);
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField field = " + field.toString());
field.setAccessible(true);
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField field contains topic: "
+ (field.get(topic) != null ? "true" : "false"));
if (field.get(topic) != null
&& field.get(topic).equals(topicFieldValue)) {
sendMessage(topic, messageMap);
break;
}
it.remove();
} catch (NoSuchFieldException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (SecurityException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalArgumentException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
}
编辑:经过更多测试后,我发现当一个客户端停止工作时,其他客户端可以正常接收消息
我觉得你的 onMessageReceived()
有点矫枉过正。我认为您不必创建自己的处理程序来处理数据。坚持使用 Firebase 示例:FirebaseExample
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
if (/* Check if data needs to be processed by long running job */ true) {
// For long-running tasks (10 seconds or more) use WorkManager.
scheduleJob()
} else {
// Handle message within 10 seconds
handleNow()
}
}
}
我在这里找到了答案: 发生的事情是我在 5 分钟后随机收到路由器超时。我已通过在 4 分钟后而不是默认的 15 分钟后手动向 FCM 发送心跳来解决此问题。
context.sendBroadcast(new Intent("com.google.android.intent.action.GTALK_HEARTBEAT"));
context.sendBroadcast(new Intent("com.google.android.intent.action.MCS_HEARTBEAT"));
几个月前我实施了此修复,此后问题再未出现。