基于Firebase的Android消息通知Service异常:运行一次即停止
嘿,我来帮你分析下问题出在哪,以及怎么修复它!
首先看你的代码,导致Service运行一次就停止的核心原因有这几个:
问题1:主动停止Service的逻辑错误
你在stop == 0时调用了thread.interrupt()和stopSelf(),这里的stop初始值是PChat的子节点数量,每处理一个聊天对象的消息就减1,减到0就直接停了Service。这就意味着只要初始的所有聊天对象都触发过一次监听,Service就会立刻停止,自然没法接收后续的新消息或者新聊天对象的通知。
问题2:全局first变量的逻辑混乱
你在遍历每个聊天对象时,反复重置first = true,而且这个变量是多个ValueEventListener共享的——多个监听同时触发时,会互相覆盖first的值,导致判断逻辑完全混乱,要么永远发不出通知,要么重复发。
问题3:不必要的Thread使用
Firebase的ValueEventListener本身就是在后台线程中触发回调的,你额外开一个Thread完全是画蛇添足,反而容易引发线程安全问题。
问题4:监听的添加与移除逻辑不合理
你给每个Chat节点添加监听后,收到一次消息就移除了监听,但如果后续有新的聊天对象加入PChat,你的代码不会自动给新对象添加监听(除非PChat节点再次触发onDataChange,但此时原来的stop变量已经是0,Service早就停了)。
修复方案
我们需要调整逻辑,让Service持续运行,同时为每个聊天对象单独记录是否已经发送过通知,避免全局变量的混乱。下面是修正后的代码思路和示例:
- 用线程安全的集合+SharedPreferences来存储已经发送过通知的聊天对象ID,这样即使Service被系统重启,也能恢复记录。
- 移除不必要的Thread,直接在Service中初始化Firebase监听。
- 监听
PChat节点的实时变化,当有新的聊天对象加入时,自动给对应的Chat节点添加监听。 - 不要主动调用
stopSelf(),让Service保持运行(如果需要停止,可以在退出应用时手动停止)。
修正后的代码示例:
public class Pmessginfnotifiysvc extends Service { FirebaseAuth auth; // 存储已经通知过的聊天对象ID,线程安全 private Set<String> notifiedChatIds = ConcurrentHashMap.newKeySet(); DatabaseReference pChatRef; // 保存每个Chat节点的监听,方便后续移除 private Map<String, ValueEventListener> chatListeners = new ConcurrentHashMap<>(); @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); auth = FirebaseAuth.getInstance(); // 初始化PChat节点的监听 initPChatListener(); // 从SharedPreferences恢复已通知的记录(避免Service重启后丢失) loadNotifiedChatIds(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 返回START_STICKY,让系统在Service被回收后尝试重启 return START_STICKY; } private void initPChatListener() { String userId = auth.getCurrentUser().getUid(); pChatRef = FirebaseDatabase.getInstance().getReference().child("Users").child(userId).child("PChat"); // 监听PChat节点的变化,包括新增、移除聊天对象 pChatRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // 遍历所有聊天对象 for (DataSnapshot kid : dataSnapshot.getChildren()) { String chatId = kid.getValue().toString(); String chatPartnerId = kid.getKey(); // 如果这个聊天对象还没发过通知,且还没添加监听 if (!notifiedChatIds.contains(chatPartnerId) && !chatListeners.containsKey(chatId)) { addChatListener(chatId, chatPartnerId); } } // 移除已经不存在的聊天对象的监听 Iterator<Map.Entry<String, ValueEventListener>> iterator = chatListeners.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, ValueEventListener> entry = iterator.next(); String chatId = entry.getKey(); if (!dataSnapshot.hasChild(chatId)) { FirebaseDatabase.getInstance().getReference().child("Chat").child(chatId) .removeEventListener(entry.getValue()); iterator.remove(); } } } @Override public void onCancelled(DatabaseError databaseError) { // 处理取消监听的错误 } }); } private void addChatListener(String chatId, String chatPartnerId) { DatabaseReference chatRef = FirebaseDatabase.getInstance().getReference().child("Chat").child(chatId); ValueEventListener listener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // 只要有新消息(这里假设dataSnapshot有数据就代表有新消息) if (dataSnapshot.exists() && !notifiedChatIds.contains(chatPartnerId)) { // 发送通知 MyNotificationManager.getInstance(getApplicationContext()) .displayNotification("TvTime", chatPartnerId + " Has Sent You A Message!"); // 标记为已通知 notifiedChatIds.add(chatPartnerId); saveNotifiedChatIds(); // 移除该聊天对象的监听,后续消息不再通知 chatRef.removeEventListener(this); chatListeners.remove(chatId); } } @Override public void onCancelled(DatabaseError databaseError) { // 处理取消监听的错误 chatListeners.remove(chatId); } }; chatRef.addValueEventListener(listener); chatListeners.put(chatId, listener); } // 保存已通知的聊天对象ID到SharedPreferences private void saveNotifiedChatIds() { SharedPreferences prefs = getSharedPreferences("NotificationPrefs", MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putStringSet("notifiedChatIds", notifiedChatIds); editor.apply(); } // 从SharedPreferences加载已通知的记录 private void loadNotifiedChatIds() { SharedPreferences prefs = getSharedPreferences("NotificationPrefs", MODE_PRIVATE); Set<String> savedIds = prefs.getStringSet("notifiedChatIds", null); if (savedIds != null) { notifiedChatIds.addAll(savedIds); } } @Override public void onDestroy() { super.onDestroy(); // 移除所有监听,避免内存泄漏 if (pChatRef != null && !pChatRef.getListeners().isEmpty()) { pChatRef.removeEventListener((ValueEventListener) pChatRef.getListeners().get(0)); } for (Map.Entry<String, ValueEventListener> entry : chatListeners.entrySet()) { FirebaseDatabase.getInstance().getReference().child("Chat").child(entry.getKey()) .removeEventListener(entry.getValue()); } chatListeners.clear(); } }
额外说明
- 我用
ConcurrentHashMap的newKeySet()来存储已通知的聊天对象ID,保证线程安全,因为Firebase的回调是在后台线程触发的。 - 添加了
SharedPreferences来持久化已通知的记录,这样即使Service被系统重启,也不会重复给同一个聊天对象发通知。 - 监听
PChat节点的所有变化,包括新增和移除聊天对象,自动管理Chat节点的监听添加和移除。 - 每个聊天对象的监听在发送一次通知后就移除,符合你“针对每个聊天对象仅通知一次”的需求,同时Service会持续运行,监听新的聊天对象。
内容的提问来源于stack exchange,提问作者Niv Shalom
相关产品推荐
相关产品推荐

