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

运行中USSD会话多次发送响应的Android开发问题

问题根源
  • 系统抢了USSD会话控制权:发送USSD请求后,系统默认会弹出自带的USSD界面,直接接管会话,导致APP无法继续发送后续响应请求。
  • 嵌套回调逻辑死板:当前代码用多层嵌套的ResultReceiver处理响应,逻辑耦合度高,且未处理会话持续状态,系统弹窗出现后,后续交互直接失效。
  • 未拦截系统弹窗:缺少阻止系统显示USSD界面的机制,APP无法独占会话控制权,自然无法完成多步交互。
解决方案

1. 用无障碍服务拦截系统USSD弹窗

要让APP持续控制会话,必须先拦截系统的USSD弹窗:

  • 编写继承自AccessibilityService的服务,在onAccessibilityEvent中检测系统USSD窗口(常见包名:com.android.phone或com.android.server.telecom),一旦检测到就调用返回键关闭弹窗。
  • 在Manifest中注册该服务,声明对应权限和过滤规则。

2. 重构为状态化流程控制

放弃多层嵌套回调,改用状态机管理会话流程,比如分为「初始请求」「等待菜单响应」「等待PIN输入」等状态,每个状态对应独立处理逻辑,降低耦合度。

3. 确保会话上下文复用

调用handleUssdRequest时,需保证在同一个会话内执行。部分设备的该方法可能需要额外参数维持会话,且要在收到响应后立即发送下一个请求,避免会话超时或被系统接管。

代码实现示例

无障碍拦截服务

public class UssdInterceptorService extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            String packageName = event.getPackageName().toString();
            // 适配不同厂商的系统USSD窗口包名
            if (packageName.equals("com.android.phone") || packageName.equals("com.android.server.telecom")) {
                // 拦截弹窗,不展示给用户
                performGlobalAction(GLOBAL_ACTION_BACK);
            }
        }
    }

    @Override
    public void onInterrupt() {}

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
        info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        info.packageNames = new String[]{"com.android.phone", "com.android.server.telecom"};
        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
        setServiceInfo(info);
    }
}

Manifest注册服务

<service
    android:name=".UssdInterceptorService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config" />
</service>

无障碍服务配置文件(res/xml/accessibility_service_config.xml)

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_description"
    android:packageNames="com.android.phone,com.android.server.telecom" />

重构后的USSD交互逻辑

private TelephonyManager tm;
private Method handleUssdRequestMethod;
private Object iTelephony;
private String targetOfferAmount;
private String userPin;

// 初始化USSD相关实例
public void initUssd(Context context) {
    tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    try {
        Class<?> telephonyManagerClass = Class.forName(tm.getClass().getName());
        Method getITelephony = telephonyManagerClass.getDeclaredMethod("getITelephony");
        getITelephony.setAccessible(true);
        iTelephony = getITelephony.invoke(tm);
        Method[] methods = iTelephony.getClass().getMethods();
        for (Method method : methods) {
            if (method.getName().equals("handleUssdRequest")) {
                handleUssdRequestMethod = method;
                handleUssdRequestMethod.setAccessible(true);
                break;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 启动USSD流程
public void startUssdFlow(Context context, String initialUssd, String offerAmount, String pin) {
    targetOfferAmount = offerAmount;
    userPin = pin;
    sendUssdRequest(0, initialUssd, response -> processUssdResponse(response));
}

// 发送USSD请求的通用方法
private void sendUssdRequest(int simSlot, String ussd, UssdResponseCallback callback) {
    if (handleUssdRequestMethod == null || iTelephony == null) return;
    try {
        handleUssdRequestMethod.invoke(iTelephony, simSlot, ussd, new ResultReceiver(new Handler(Looper.getMainLooper())) {
            @Override
            protected void onReceiveResult(int resultCode, Bundle ussdResponse) {
                Object p = ussdResponse.getParcelable("USSD_RESPONSE");
                if (p != null) {
                    try {
                        Method getReturnMessage = p.getClass().getMethod("getReturnMessage");
                        CharSequence message = (CharSequence) getReturnMessage.invoke(p);
                        if (callback != null) callback.onResponse(message);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 处理USSD响应的核心逻辑
private void processUssdResponse(CharSequence response) {
    String responseStr = response.toString();
    if (responseStr.contains(targetOfferAmount)) {
        // 提取目标套餐对应的选项
        String offerLine = findLineWithMyOffer(responseStr, targetOfferAmount);
        String option = extractNumberFromLine(offerLine);
        // 发送选项请求,之后发送PIN
        sendUssdRequest(0, option, nextResponse -> {
            sendUssdRequest(0, userPin, null);
            if (textViewUssdResponse != null) {
                textViewUssdResponse.setText(nextResponse);
            }
        });
    } else {
        // 未找到目标套餐,发送99继续查找
        sendUssdRequest(0, "99", this::processUssdResponse);
    }
}

// 响应回调接口
private interface UssdResponseCallback {
    void onResponse(CharSequence response);
}

// 原有工具方法保留
private String findLineWithMyOffer(String response, String offerAmount) {
    String[] lines = response.split("\n");
    for (String line : lines) {
        if (line.contains(offerAmount)) return line;
    }
    return "";
}

private String extractNumberFromLine(String line) {
    for (char c : line.toCharArray()) {
        if (Character.isDigit(c)) return String.valueOf(c);
    }
    return "";
}
注意事项
  • 权限需手动开启:必须申请ACCESSIBILITY_SERVICE权限,用户需前往系统设置为APP手动开启该权限。
  • 适配不同厂商:不同品牌手机的USSD窗口包名可能不同,需测试适配(如部分华为机型包名有差异)。
  • 避免会话超时:USSD会话存在超时限制,需确保每一步响应在超时前发送。
  • 隐藏API兼容性:handleUssdRequest是系统隐藏API,不同Android版本的调用规则可能变化,高版本系统可能限制调用,需做适配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 11:04:51