Android 双卡如何同时发送 USSD 请求?
2024-07-12 04:09:31
Android 双卡并发 USSD 请求:挑战与解决方案
在 Android 开发中,我们常常需要利用 USSD 代码与运营商进行交互,例如查询话费余额、流量使用情况等。但当设备使用双卡时,如何同时从两张 SIM 卡发送 USSD 请求并获取响应,成为了一项棘手的任务。
很多开发者尝试使用 TelephonyManager
类中的 sendUssdRequest()
方法来实现这一目标。但实际情况是,当尝试同时从两张不同的 SIM 卡发送 USSD 请求时,其中一个请求往往会失败,并返回 onReceiveUssdResponseFailed: Error -1
的错误信息。
这是因为 Android 系统默认不支持并发 USSD 请求。当你尝试同时发送两个请求时,第二个请求会被阻塞,直到第一个请求完成。
深入分析:为何传统的解决方案失效
在深入探讨解决方案之前,我们先来了解一下为什么传统的 sendUssdRequest()
方法无法满足我们的需求。
Android 系统的 Telephony
框架在处理 USSD 请求时采用了一种同步机制。当 sendUssdRequest()
方法被调用时,它会阻塞调用线程,直到收到来自运营商的响应或超时。这种同步机制在单卡情况下运作良好,但在双卡情况下就会出现问题。
当我们尝试同时发送两个 USSD 请求时,第一个请求会阻塞 Telephony
框架。此时,第二个请求会被系统认为是无效的,因为它无法获得 Telephony
框架的独占访问权限。
解决方案:分步执行,巧妙化解
为了解决这个问题,我们需要改变思路,避免直接进行并发 USSD 请求。我们可采用以下分步执行的策略:
- SIM 卡选择: 首先,我们需要确定要使用的 SIM 卡。Android 提供了
SubscriptionManager
类,可以帮助我们获取设备上所有可用的 SIM 卡信息。 - 异步请求: 为了避免阻塞主线程,我们需要将 USSD 请求封装成异步任务。可以使用
Thread
、AsyncTask
或ExecutorService
等方式实现异步操作。 - 间隔发送: 为了进一步提高成功率,我们可以在发送第一个 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);
}
});
}
}
代码解释:
-
DualSimUssdHelper
类: 封装了发送 USSD 请求的逻辑。 -
sendUssdRequest
方法:- 接受
simSlotIndex
参数,用于指定要使用的 SIM 卡。 - 使用
Thread
创建异步任务,避免阻塞主线程。 - 使用
SubscriptionManager
获取指定 SIM 卡的SubscriptionInfo
。 - 使用
TelephonyManager.createForSubscriptionId
创建对应 SIM 卡的TelephonyManager
实例。 - 使用
simTm.sendUssdRequest
发送 USSD 请求。 - 通过
UssdResponseListener
回调接口返回结果。
- 接受
-
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 卡
}
});
常见问题解答
-
问:为什么需要设置延迟时间?
- 答: 由于 Android 系统对 USSD 请求的处理机制,连续发送两个请求可能会导致第二个请求失败。设置延迟时间可以增加第二个请求成功的概率。
-
问:延迟时间应该设置多长?
- 答: 延迟时间的最佳值取决于设备和运营商。建议从 500 毫秒开始尝试,根据实际情况进行调整。
-
问:这种方法可以保证所有情况下都成功吗?
- 答: 并不能完全保证。由于 USSD 请求的处理机制依赖于运营商,因此在某些情况下仍然可能出现失败的情况。
-
问:还有其他解决方案吗?
- 答: 可以考虑使用第三方库,例如
simcom.jar
等,这些库可能提供了更底层的 API,可以更灵活地控制 USSD 请求。
- 答: 可以考虑使用第三方库,例如
-
问:如何获取设备上可用的 SIM 卡数量?
- 答: 可以使用
SubscriptionManager.getActiveSubscriptionInfoCount()
方法获取设备上可用的 SIM 卡数量。
- 答: 可以使用
总结
尽管 Android 系统本身并不直接支持双卡并发 USSD 请求,但通过巧妙地设计异步请求和间隔发送策略,我们仍然可以实现这一目标。代码示例清晰易懂,方便开发者直接应用到自己的项目中。