You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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持续运行,同时为每个聊天对象单独记录是否已经发送过通知,避免全局变量的混乱。下面是修正后的代码思路和示例:

  1. 用线程安全的集合+SharedPreferences来存储已经发送过通知的聊天对象ID,这样即使Service被系统重启,也能恢复记录。
  2. 移除不必要的Thread,直接在Service中初始化Firebase监听。
  3. 监听PChat节点的实时变化,当有新的聊天对象加入时,自动给对应的Chat节点添加监听。
  4. 不要主动调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:23:06