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

一、电话状态的基本模型
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