AVD中Kitkat系统测试ITelephony.endCall抛出NoSuchMethodError问题
解决Android KitKat系统中ITelephony.endCall()的NoSuchMethodError异常
这个问题我之前也碰到过,核心原因是不同Android版本里com.android.internal.telephony.ITelephony接口的endCall()方法签名不一致:
- 在Android 6.0(Marshmallow,API23)及以后版本,
endCall()是无返回值的void类型; - 但在KitKat(API19)及更早版本,
endCall()是带boolean返回值的方法。
你定义的ITelephony接口用了void endCall(),在KitKat系统里反射调用时找不到匹配的方法,自然就抛出NoSuchMethodError了。
下面给你两种可行的解决方案:
方案一:适配接口方法签名,按版本调用
先修改你的ITelephony接口,同时兼容两种方法签名:
public interface ITelephony { // 适配KitKat及更早版本(带返回值) boolean endCall(); // 适配Marshmallow及以后版本(无返回值) void endCallVoid(); void silenceRinger(); }
然后在BroadcastReceiver里根据系统版本判断调用对应的方法:
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); try { Class<?> telephonyManagerClass = Class.forName(tm.getClass().getName()); Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony"); getITelephonyMethod.setAccessible(true); Object telephonyService = getITelephonyMethod.invoke(tm); // 先处理静音 Method silenceRingerMethod = telephonyService.getClass().getDeclaredMethod("silenceRinger"); silenceRingerMethod.setAccessible(true); silenceRingerMethod.invoke(telephonyService); // 根据系统版本调用对应endCall方法 int sdkVersion = android.os.Build.VERSION.SDK_INT; Method endCallMethod; if (sdkVersion < android.os.Build.VERSION_CODES.M) { // KitKat及更早,调用带返回值的endCall endCallMethod = telephonyService.getClass().getDeclaredMethod("endCall"); endCallMethod.setAccessible(true); // 忽略返回值即可 endCallMethod.invoke(telephonyService); } else { // Marshmallow及以后,调用无返回值的endCall endCallMethod = telephonyService.getClass().getDeclaredMethod("endCall"); endCallMethod.setAccessible(true); endCallMethod.invoke(telephonyService); } } catch (Exception e) { e.printStackTrace(); // 这里可以添加异常日志记录,方便排查问题 }
方案二:完全通过反射调用,不依赖自定义接口
这种方式更灵活,避免自定义接口和系统实际接口不匹配的问题:
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); try { // 获取TelephonyManager的隐藏方法getITelephony Class<?> tmClass = Class.forName(tm.getClass().getName()); Method getITelephonyMethod = tmClass.getDeclaredMethod("getITelephony"); getITelephonyMethod.setAccessible(true); Object iTelephonyInstance = getITelephonyMethod.invoke(tm); // 调用静音方法 Method silenceRinger = iTelephonyInstance.getClass().getDeclaredMethod("silenceRinger"); silenceRinger.setAccessible(true); silenceRinger.invoke(iTelephonyInstance); // 处理endCall方法,按版本匹配签名 int sdkVersion = android.os.Build.VERSION.SDK_INT; Method endCallMethod = iTelephonyInstance.getClass().getDeclaredMethod("endCall"); endCallMethod.setAccessible(true); if (sdkVersion < android.os.Build.VERSION_CODES.M) { // KitKat及更早版本,方法返回boolean,调用时接收返回值即可 boolean result = (boolean) endCallMethod.invoke(iTelephonyInstance); // 可选:根据result判断是否成功挂断 } else { // Marshmallow及以后,无返回值直接调用 endCallMethod.invoke(iTelephonyInstance); } } catch (Exception e) { e.printStackTrace(); }
额外注意事项
- 权限问题:你已经添加了
READ_PHONE_STATE和CALL_PHONE权限,在KitKat版本里这些权限足够调用隐藏方法,不需要额外的系统权限(MODIFY_PHONE_STATE是系统级权限,普通APP无法获取); - 版本兼容性:从Android 10(API29)开始,系统限制了对
ITelephony这类内部接口的反射调用,如果后续需要适配更高版本,建议改用官方提供的CallScreeningService来实现来电拦截。
内容的提问来源于stack exchange,提问作者Mr. Nacho




