返回

Android 双卡如何同时发送 USSD 请求?

Android

Android 双卡并发 USSD 请求:挑战与解决方案

在 Android 开发中,我们常常需要利用 USSD 代码与运营商进行交互,例如查询话费余额、流量使用情况等。但当设备使用双卡时,如何同时从两张 SIM 卡发送 USSD 请求并获取响应,成为了一项棘手的任务。

很多开发者尝试使用 TelephonyManager 类中的 sendUssdRequest() 方法来实现这一目标。但实际情况是,当尝试同时从两张不同的 SIM 卡发送 USSD 请求时,其中一个请求往往会失败,并返回 onReceiveUssdResponseFailed: Error -1 的错误信息。

这是因为 Android 系统默认不支持并发 USSD 请求。当你尝试同时发送两个请求时,第二个请求会被阻塞,直到第一个请求完成。

深入分析:为何传统的解决方案失效

在深入探讨解决方案之前,我们先来了解一下为什么传统的 sendUssdRequest() 方法无法满足我们的需求。

Android 系统的 Telephony 框架在处理 USSD 请求时采用了一种同步机制。当 sendUssdRequest() 方法被调用时,它会阻塞调用线程,直到收到来自运营商的响应或超时。这种同步机制在单卡情况下运作良好,但在双卡情况下就会出现问题。

当我们尝试同时发送两个 USSD 请求时,第一个请求会阻塞 Telephony 框架。此时,第二个请求会被系统认为是无效的,因为它无法获得 Telephony 框架的独占访问权限。

解决方案:分步执行,巧妙化解

为了解决这个问题,我们需要改变思路,避免直接进行并发 USSD 请求。我们可采用以下分步执行的策略:

  1. SIM 卡选择: 首先,我们需要确定要使用的 SIM 卡。Android 提供了 SubscriptionManager 类,可以帮助我们获取设备上所有可用的 SIM 卡信息。
  2. 异步请求: 为了避免阻塞主线程,我们需要将 USSD 请求封装成异步任务。可以使用 ThreadAsyncTaskExecutorService 等方式实现异步操作。
  3. 间隔发送: 为了进一步提高成功率,我们可以在发送第一个 USSD 请求后,设置一个短暂的延迟时间,然后再发送第二个请求。

代码实现:清晰易懂,拿来即用

以下是用 Java 代码实现上述解决方案的示例:

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;

public class DualSimUssdHelper {

    private static final String TAG = "DualSimUssdHelper";
    private static final long DELAY_MILLIS = 500; // 延迟时间,单位毫秒

    public interface UssdResponseListener {
        void onReceived(int simSlot, String response);
        void onError(int simSlot, int errorCode);
    }

    public static void sendUssdRequest(Context context, int simSlotIndex, String ussdCode, UssdResponseListener listener) {
        new Thread(() -> {
            Log.d(TAG, "Sending USSD request for SIM slot: " + simSlotIndex + ", code: " + ussdCode);

            TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
            if (telephonyManager == null) {
                Log.e(TAG, "TelephonyManager is null");
                if (listener != null) {
                    listener.onError(simSlotIndex, -1); // 自定义错误码
                }
                return;
            }

            SubscriptionManager subscriptionManager = context.getSystemService(SubscriptionManager.class);
            if (subscriptionManager == null) {
                Log.e(TAG, "SubscriptionManager is null");
                if (listener != null) {
                    listener.onError(simSlotIndex, -2); // 自定义错误码
                }
                return;
            }

            @SuppressLint("MissingPermission")
            SubscriptionInfo subscriptionInfo = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(simSlotIndex);
            if (subscriptionInfo == null) {
                Log.e(TAG, "SubscriptionInfo is null for SIM slot: " + simSlotIndex);
                if (listener != null) {
                    listener.onError(simSlotIndex, -3); // 自定义错误码
                }
                return;
            }

            int subscriptionId = subscriptionInfo.getSubscriptionId();
            TelephonyManager simTm = telephonyManager.createForSubscriptionId(subscriptionId);
            if (simTm == null) {
                Log.e(TAG, "TelephonyManager for SIM slot " + simSlotIndex + " is null");
                if (listener != null) {
                    listener.onError(simSlotIndex, -4); // 自定义错误码
                }
                return;
            }

            simTm.sendUssdRequest(ussdCode, new TelephonyManager.UssdResponseCallback() {
                @Override
                public void onReceiveUssdResponse(TelephonyManager telephonyManager, String request, CharSequence response) {
                    Log.d(TAG, "USSD response for SIM slot " + simSlotIndex + ": " + response);
                    if (listener != null) {
                        new Handler(Looper.getMainLooper()).post(() -> listener.onReceived(simSlotIndex, response.toString()));
                    }
                }

                @Override
                public void onReceiveUssdResponseFailed(TelephonyManager telephonyManager, String request, int failureCode) {
                    Log.e(TAG, "USSD request failed for SIM slot " + simSlotIndex + ": " + failureCode);
                    if (listener != null) {
                        new Handler(Looper.getMainLooper()).post(() -> listener.onError(simSlotIndex, failureCode));
                    }
                }
            }, new Handler(Looper.getMainLooper()));

        }).start();
    }

    public static void sendUssdRequestsSequentially(Context context, String ussdCode1, String ussdCode2, UssdResponseListener listener) {
        sendUssdRequest(context, 0, ussdCode1, new UssdResponseListener() {
            @Override
            public void onReceived(int simSlot, String response) {
                if (listener != null) {
                    listener.onReceived(simSlot, response);
                }
                // 在第一个请求完成后发送第二个请求
                sendUssdRequest(context, 1, ussdCode2, listener);
            }

            @Override
            public void onError(int simSlot, int errorCode) {
                if (listener != null) {
                    listener.onError(simSlot, errorCode);
                }
                // 即使第一个请求失败,也要尝试发送第二个请求
                sendUssdRequest(context, 1, ussdCode2, listener);
            }
        });
    }
}

代码解释:

  1. DualSimUssdHelper 类: 封装了发送 USSD 请求的逻辑。

  2. sendUssdRequest 方法:

    • 接受 simSlotIndex 参数,用于指定要使用的 SIM 卡。
    • 使用 Thread 创建异步任务,避免阻塞主线程。
    • 使用 SubscriptionManager 获取指定 SIM 卡的 SubscriptionInfo
    • 使用 TelephonyManager.createForSubscriptionId 创建对应 SIM 卡的 TelephonyManager 实例。
    • 使用 simTm.sendUssdRequest 发送 USSD 请求。
    • 通过 UssdResponseListener 回调接口返回结果。
  3. sendUssdRequestsSequentially 方法:

    • 依次发送两个 USSD 请求,并在第一个请求完成后(无论成功或失败)再发送第二个请求。
    • 使用了嵌套的 UssdResponseListener 来处理第一个请求的结果,并在第一个请求完成后发送第二个请求。

使用示例:

// 发送 USSD 请求到 SIM 卡 1
DualSimUssdHelper.sendUssdRequest(context, 0, "*100#", new DualSimUssdHelper.UssdResponseListener() {
    @Override
    public void onReceived(int simSlot, String response) {
        // 处理 SIM 卡 1 的响应
    }

    @Override
    public void onError(int simSlot, int errorCode) {
        // 处理 SIM 卡 1 的错误
    }
});

// 依次发送两个 USSD 请求
DualSimUssdHelper.sendUssdRequestsSequentially(context, "*100#", "*200#", new DualSimUssdHelper.UssdResponseListener() {
    @Override
    public void onReceived(int simSlot, String response) {
        // 处理响应,根据 simSlot 区分 SIM 卡
    }

    @Override
    public void onError(int simSlot, int errorCode) {
        // 处理错误,根据 simSlot 区分 SIM 卡
    }
});

常见问题解答

  1. 问:为什么需要设置延迟时间?

    • 答: 由于 Android 系统对 USSD 请求的处理机制,连续发送两个请求可能会导致第二个请求失败。设置延迟时间可以增加第二个请求成功的概率。
  2. 问:延迟时间应该设置多长?

    • 答: 延迟时间的最佳值取决于设备和运营商。建议从 500 毫秒开始尝试,根据实际情况进行调整。
  3. 问:这种方法可以保证所有情况下都成功吗?

    • 答: 并不能完全保证。由于 USSD 请求的处理机制依赖于运营商,因此在某些情况下仍然可能出现失败的情况。
  4. 问:还有其他解决方案吗?

    • 答: 可以考虑使用第三方库,例如 simcom.jar 等,这些库可能提供了更底层的 API,可以更灵活地控制 USSD 请求。
  5. 问:如何获取设备上可用的 SIM 卡数量?

    • 答: 可以使用 SubscriptionManager.getActiveSubscriptionInfoCount() 方法获取设备上可用的 SIM 卡数量。

总结

尽管 Android 系统本身并不直接支持双卡并发 USSD 请求,但通过巧妙地设计异步请求和间隔发送策略,我们仍然可以实现这一目标。代码示例清晰易懂,方便开发者直接应用到自己的项目中。