You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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();
}

额外注意事项

  1. 权限问题:你已经添加了READ_PHONE_STATECALL_PHONE权限,在KitKat版本里这些权限足够调用隐藏方法,不需要额外的系统权限(MODIFY_PHONE_STATE是系统级权限,普通APP无法获取);
  2. 版本兼容性:从Android 10(API29)开始,系统限制了对ITelephony这类内部接口的反射调用,如果后续需要适配更高版本,建议改用官方提供的CallScreeningService来实现来电拦截。

内容的提问来源于stack exchange,提问作者Mr. Nacho

火山引擎 最新活动