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

Android中BroadcastReceiver实现来电注册时事件重复的问题排查

Android来电监听中BroadcastReceiver重复触发CALL_STATE_IDLE的问题

我在Android应用里实现来电注册功能,用了BroadcastReceiver来监听,代码如下:

public class IncomingCall extends BroadcastReceiver { 
    //private AppPhoneStateListener phoneStateListener; 
    private TelephonyManager telephonyManager; 
    private HashMap<String, AppPhoneStateListener> appPhoneStateListenerHashMap = new HashMap<>(); 

    @Override 
    public void onReceive(Context context, Intent intent) { 
        try { 
            // Create manager for listener registration 
            AppPhoneStateListener phoneStateListener = new AppPhoneStateListener(); 
            telephonyManager = (TelephonyManager) context 
                    .getSystemService(Context.TELEPHONY_SERVICE); 
            // Register listener for LISTEN_CALL_STATE 
            telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 
        } catch (Exception e) { 
            Crashlytics.logException(e); 
        } 
    } 

    private class AppPhoneStateListener extends PhoneStateListener { 
        private CallMonitoring callMonitoring = new CallMonitoring(); 

        @Override 
        public void onCallStateChanged(int state, String incomingNumber) { 
            // Service only for child device 
            Role role = User.getRole(); 
            if (role == Role.parent) return; 

            Log.d("MyPhoneListener","state [" + state + "]"); 

            if (state == TelephonyManager.CALL_STATE_RINGING) { 
                callMonitoring.registerChangeState(state); 
                if (!appPhoneStateListenerHashMap.containsKey(incomingNumber)) { 
                    appPhoneStateListenerHashMap.put(incomingNumber, this); 
                } else { 
                    telephonyManager.listen(this, PhoneStateListener.LISTEN_NONE); 
                } 
                //Log.d("MyPhoneListener","saved call states [" + callMonitoring.showCallStates() + "]"); 
            } 

            if (appPhoneStateListenerHashMap.containsKey(incomingNumber)) { 
                if (state == TelephonyManager.CALL_STATE_OFFHOOK) { 
                    callMonitoring.registerChangeState(state); 
                    //Log.d("MyPhoneListener","saved call states [" + callMonitoring.showCallStates() + "]"); 
                } 
                if (state == TelephonyManager.CALL_STATE_IDLE) { 
                    //Log.d("MyPhoneListener","saved call states [" + callMonitoring.showCallStates() + "]"); 
                    // There was incoming call then we have call attachCall api method 
                    if (callMonitoring.wasIncomingCall()) { 
                        long duration = callMonitoring.getLastActionDuration(); 
                        CallType callType = callMonitoring.getCallType(); 
                        CallStatus callStatus = callMonitoring.getCallStatus(); 

                        // Logging to debug 
                        Log.d("MyPhoneListener","api params: phone [" + incomingNumber + "]; duration [" + duration + "]; callType [" + callType.getCallType() + "]; callStatus [" + callStatus.getCallStatus() + "]"); 

                        // Call api 

                        // Unregister listener for LISTEN_CALL_STATE 
                        unsubscribeCallListener(incomingNumber); 
                    } 
                    // Return call monitoring call to initial state 
                    callMonitoring.clearState(); 
                } 
            } 
        } 

        private void unsubscribeCallListener(String incomingNumber) { 
            appPhoneStateListenerHashMap.remove(incomingNumber); 
            telephonyManager.listen(this, PhoneStateListener.LISTEN_NONE); 
        } 
    } 
}

现在遇到的问题是:应用重启后的第一次来电能正常触发TelephonyManager.CALL_STATE_RINGING,未接听的话会触发TelephonyManager.CALL_STATE_IDLE。但第二次及之后的来电会出现事件重复,TelephonyManager.CALL_STATE_IDLE会被触发多次,而且像是在独立线程里处理,callMonitoring.clearState()根本阻止不了重复输出日志。想问问该怎么重置BroadcastReceiver的状态,或者是不是漏了什么关键步骤?


问题分析与解决方案

兄弟,你的问题根源出在每次收到广播都注册一个新的PhoneStateListener,但旧的Listener并没有被正确清理掉,导致多个Listener同时监听通话状态,所以后续来电会触发多次回调。

核心问题拆解

  1. BroadcastReceiver的实例是临时的:系统每次触发广播时,可能会创建新的Receiver实例,你把appPhoneStateListenerHashMap作为Receiver的成员变量,每次新广播过来这个HashMap都是空的——之前注册的Listener根本没被记录,也就无法注销,导致Listener越积越多。
  2. 重复注册未清理:每次onReceive都新建Listener并注册,旧的Listener没有被注销,导致同一个通话状态变化会被多个Listener捕获,自然出现重复日志。

具体修复步骤

1. 用全局单例管理PhoneStateListener

因为Receiver实例是临时的,需要一个全局单例来统一管理所有注册的Listener,避免遗漏注销:

public class CallListenerManager {
    private static CallListenerManager instance;
    private TelephonyManager telephonyManager;
    private HashMap<String, PhoneStateListener> listenerMap = new HashMap<>();

    private CallListenerManager(Context context) {
        telephonyManager = (TelephonyManager) context.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
    }

    public static synchronized CallListenerManager getInstance(Context context) {
        if (instance == null) {
            instance = new CallListenerManager(context.getApplicationContext());
        }
        return instance;
    }

    // 确保同一个号码只注册一次监听
    public void registerListener(String phoneNumber, PhoneStateListener listener) {
        if (!listenerMap.containsKey(phoneNumber)) {
            listenerMap.put(phoneNumber, listener);
            telephonyManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
        }
    }

    // 注销监听并移除记录
    public void unregisterListener(String phoneNumber) {
        PhoneStateListener listener = listenerMap.remove(phoneNumber);
        if (listener != null) {
            telephonyManager.listen(listener, PhoneStateListener.LISTEN_NONE);
        }
    }

    // 检查是否已注册该号码的监听
    public boolean hasListener(String phoneNumber) {
        return listenerMap.containsKey(phoneNumber);
    }
}

2. 修改BroadcastReceiver的逻辑

不再每次广播都新建Listener,而是通过单例检查并管理:

public class IncomingCall extends BroadcastReceiver { 
    @Override 
    public void onReceive(Context context, Intent intent) { 
        try { 
            // 获取来电号码(Android10+需要READ_CALL_LOG权限,需动态申请)
            String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
            if (TextUtils.isEmpty(incomingNumber)) {
                return;
            }

            CallListenerManager manager = CallListenerManager.getInstance(context);
            // 避免重复注册
            if (!manager.hasListener(incomingNumber)) {
                AppPhoneStateListener phoneStateListener = new AppPhoneStateListener(context, incomingNumber);
                manager.registerListener(incomingNumber, phoneStateListener);
            }
        } catch (Exception e) {
            Crashlytics.logException(e);
        }
    } 

    private class AppPhoneStateListener extends PhoneStateListener { 
        private Context context;
        private String incomingNumber;
        private CallMonitoring callMonitoring = new CallMonitoring(); 

        public AppPhoneStateListener(Context context, String incomingNumber) {
            this.context = context.getApplicationContext(); // 用全局上下文避免内存泄漏
            this.incomingNumber = incomingNumber;
        }

        @Override 
        public void onCallStateChanged(int state, String number) { 
            // 父设备直接注销监听
            Role role = User.getRole(); 
            if (role == Role.parent) {
                CallListenerManager.getInstance(context).unregisterListener(incomingNumber);
                return;
            } 

            Log.d("MyPhoneListener","state [" + state + "] for number: " + incomingNumber); 

            switch (state) {
                case TelephonyManager.CALL_STATE_RINGING:
                    callMonitoring.registerChangeState(state);
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    callMonitoring.registerChangeState(state);
                    break;
                case TelephonyManager.CALL_STATE_IDLE:
                    if (callMonitoring.wasIncomingCall()) {
                        long duration = callMonitoring.getLastActionDuration();
                        CallType callType = callMonitoring.getCallType();
                        CallStatus callStatus = callMonitoring.getCallStatus();

                        Log.d("MyPhoneListener","api params: phone [" + incomingNumber + "]; duration [" + duration + "]; callType [" + callType.getCallType() + "]; callStatus [" + callStatus.getCallStatus() + "]");

                        // 调用你的API逻辑
                    }
                    callMonitoring.clearState();
                    // 通话结束后必须注销监听
                    CallListenerManager.getInstance(context).unregisterListener(incomingNumber);
                    break;
            }
        } 
    } 
}

3. 额外注意事项

  • 权限适配:Android10及以上版本获取来电号码需要READ_CALL_LOG权限,必须动态申请,否则EXTRA_INCOMING_NUMBER会返回null。
  • 内存泄漏:Listener要持有ApplicationContext,不要用BroadcastReceiver的临时Context。
  • 异常场景处理:如果来电号码为空,直接跳过逻辑,避免空指针。

这样修改后,每个来电号码只会对应一个Listener,通话结束后及时注销,就不会出现多次触发CALL_STATE_IDLE事件的问题了。


内容的提问来源于stack exchange,提问作者Alex Zhulin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:07:55