如何正确监听 Android 电话呼叫的结束状态

来源:中国站长站作者:半糖头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何正确监听 Android 电话呼叫的结束状态》,敬请观看详情。想实时感知用户何时挂断电话,却在代码中总是漏掉结束瞬间?这往往是因为只依赖 PhoneStateListener 的 CALL_STATE_OFFHOOK 与 CALL_STATE_IDLE 切换判断。Android 的电话状态广播在不同系统版本和厂商 ROM 上回调时序并不统一,有的机型挂断后不会立即回到 IDLE,而是在数秒后才触发。正确做法应结合 TelephonyManager 监听与 READ_PHONE_STATE 权限申请,在 onCallStateChanged 中区分来电与去电,并利用后台服务保持监听存活。同时要注意 Android 10 之后后台读取通话状态受限,需引导用户授权或改用 AccessibilityService 辅助方案。理清状态机变化规律,才能稳定捕获呼叫结束节点。

在 Android 开发中,准确知道一通电话什么时候结束,是很多通话录音、通话统计、UI 状态恢复功能的基础。系统提供了 TelephonyManager 和 PhoneStateListener 来暴露电话状态,但不少人在实际编码时发现,挂断电话的那一刻并不总是能收到预期回调,或者收到的状态变化有延迟、有遗漏。

如何正确监听 Android 电话呼叫的结束状态

一、电话状态的基本模型

Android 通过 TelephonyManager 提供电话服务相关的信息,其中与呼叫状态有关的是 CALL_STATE_IDLE(空闲)、CALL_STATE_RINGING(响铃)、CALL_STATE_OFFHOOK(摘机,即通话中)。开发者通常继承 PhoneStateListener,重写 onCallStateChanged 方法,就能拿到状态变化。

不过这里有个容易混淆的点:OFFHOOK 并不区分是去电还是来电,只要电话线路被占用就进入该状态;而 IDLE 表示没有任何活动呼叫。所谓监听呼叫结束,本质上是监听从 OFFHOOK 或 RINGING 回到 IDLE 的过程。很多初学者以为挂断会触发一个独立的结束事件,其实系统只是把状态置回 IDLE。

1.1 权限与注册

要接收电话状态,必须在清单文件里声明 READ_PHONE_STATE 权限。从 Android 6.0 开始,该权限属于危险权限,需要动态申请。如果没有权限,listener 不会回调任何状态。

注册监听的代码如下,注意要传入 SUBCRIPTION_ID 适配双卡,单卡可传默认值:

import android.Manifest;
import android.content.pm.PackageManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import androidx.core.app.ActivityCompat;

public class CallStateHelper {
    private TelephonyManager tm;
    private PhoneStateListener listener;

    public void register(android.content.Context context) {
        tm = (TelephonyManager) context.getSystemService(android.content.Context.TELEPHONY_SERVICE);
        listener = new PhoneStateListener() {
            @Override
            public void onCallStateChanged(int state, String phoneNumber) {
                if (state == TelephonyManager.CALL_STATE_IDLE) {
                    // 电话结束或空闲
                    android.util.Log.d("CallState", "呼叫结束");
                } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
                    android.util.Log.d("CallState", "通话中");
                } else if (state == TelephonyManager.CALL_STATE_RINGING) {
                    android.util.Log.d("CallState", "响铃");
                }
            }
        };
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED) {
            tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
        }
    }

    public void unregister() {
        if (tm != null && listener != null) {
            tm.listen(listener, PhoneStateListener.LISTEN_NONE);
        }
    }
}

二、为什么挂断结束状态会监听不到

理论上从 OFFHOOK 到 IDLE 就是结束,但现实里几个因素会导致你漏掉结束瞬间。首先是厂商定制 ROM 的回调延迟,某些手机在用户点击挂断后,底层上报 IDLE 会晚几秒,如果你的页面刚好在挂断后立即销毁了监听,就永远收不到。

其次是动态监听生命周期问题。如果在 Activity 的 onResume 注册、onPause 注销,而通话界面是系统级电话应用,你的 Activity 可能早就被压到后台甚至销毁,自然收不到后续 IDLE。正确方式是用一个长期运行的服务(如前台 Service)持有 TelephonyManager 监听,或者至少用 Application 级别的上下文注册,避免随页面消失。

2.1 区分来电与去电的结束

只靠状态无法区分这通结束的是来电还是去电。可以在 RINGING 时标记 incoming 为 true,在 OFFHOOK 且之前没有 RINGING 时标记为 false(去电)。当回到 IDLE 时,结合标记就能知道结束的是哪类呼叫。

示例逻辑如下:

private boolean wasRinging = false;
private boolean isIncoming = false;

PhoneStateListener listener = new PhoneStateListener() {
    @Override
    public void onCallStateChanged(int state, String phoneNumber) {
        switch (state) {
            case TelephonyManager.CALL_STATE_RINGING:
                wasRinging = true;
                isIncoming = true;
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                if (!wasRinging) {
                    isIncoming = false; // 去电
                }
                break;
            case TelephonyManager.CALL_STATE_IDLE:
                if (wasRinging || isIncoming) {
                    android.util.Log.d("CallEnd", "来电通话结束");
                } else {
                    android.util.Log.d("CallEnd", "去电通话结束");
                }
                wasRinging = false;
                isIncoming = false;
                break;
        }
    }
};

三、Android 版本兼容与限制

从 Android 10(API 29)起,应用在后台无法读取通话状态细节,READ_PHONE_STATE 在后台受限。如果服务在后台,onCallStateChanged 可能不再回调电话号码,甚至部分机型不回调状态。官方推荐需要精细通话感知的场景使用 AccessibilityService 或 CallScreeningService(仅限系统或特定角色)。

另外,Android 9 之前可以通过广播 android.intent.action.PHONE_STATE 接收通话状态,但该隐式广播在后续版本被限制,必须动态注册且受权限管控。因此新应用不建议依赖静态广播,而应以 PhoneStateListener 为主,并处理好前台服务保活。

3.1 使用前台服务保活监听

为避免监听被系统回收,可启动一个前台 Service,展示一个低调的通知,然后在其中注册 TelephonyManager。这样既符合后台限制规范,也能持续收到 IDLE 回调。

public class CallMonitorService extends android.app.Service {
    private TelephonyManager tm;
    private PhoneStateListener listener;

    @Override
    public int onStartCommand(android.content.Intent intent, int flags, int startId) {
        tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        listener = new PhoneStateListener() {
            @Override
            public void onCallStateChanged(int state, String phoneNumber) {
                if (state == TelephonyManager.CALL_STATE_IDLE) {
                    android.util.Log.d("Svc", "服务内捕获呼叫结束");
                }
            }
        };
        if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED) {
            tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
        }
        return START_STICKY;
    }

    @Override
    public android.os.IBinder onBind(android.content.Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        if (tm != null) tm.listen(listener, PhoneStateListener.LISTEN_NONE);
        super.onDestroy();
    }
}

四、总结与最佳实践

监听 Android 电话呼叫结束状态,核心就是稳定接收 CALL_STATE_IDLE 并做好来电去电区分。实践中要把监听放在生命周期更长的组件里,处理好动态权限,针对 Android 10 以上做兼容性降级或采用无障碍方案。

最后提醒,在代码里不要把 <input> 这类标签名直接写出来而未转义,正文描述 HTML 标签时要写成 <input> 形式。函数调用如 tm.listen() 是普通方法调用,不要误写成标签。把握住状态机本质与系统限制,呼叫结束的监听就能做到准确可靠。

AndroidPhoneStateListenerTelephonyManager修改时间:2026-08-02 15:18:37

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。