Flutter Android端USSD服务拨号失败,求正确实现方案
解决Flutter中调用USSD返回-1(USSD_RETURN_FAILURE)的问题
以下是针对你的场景的分步修复方案,覆盖权限配置、编码处理、API调用及Flutter端实现:
1. 完善Android权限配置
在AndroidManifest.xml中添加必要权限,同时注意动态申请(危险权限无法仅通过Manifest声明生效):
<uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- Android 12+ 额外需要 --> <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
2. 修正USSD字符串编码
USSD中的#是URI片段标识符,直接传递会被截断,需替换为%23(保留*不变,因为它是USSD指令的一部分):
val ussdRaw = "*150*1*$secret*1*$amount*$gsmNumber*$gsmNumber#" val encodedUssd = ussdRaw.replace("#", "%23")
3. 正确实现TelephonyManager调用(MainActivity.kt)
适配Android版本(API26+用sendUssdRequest,低版本用Intent fallback),并处理权限检查:
import android.Manifest import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.telephony.TelephonyManager import android.telephony.UssdResponse import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { private val CHANNEL = "ussd_channel" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "sendUssd") { val secret = call.argument<String>("secret") ?: "" val amount = call.argument<String>("amount") ?: "" val gsmNumber = call.argument<String>("gsmNumber") ?: "" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { sendUssdWithApi26(secret, amount, gsmNumber, result) } else { sendUssdWithIntent(secret, amount, gsmNumber, result) } } else { result.notImplemented() } } } @RequiresApi(Build.VERSION_CODES.O) private fun sendUssdWithApi26(secret: String, amount: String, gsmNumber: String, result: MethodChannel.Result) { val telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager // 检查权限 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { result.error("PERMISSION_DENIED", "CALL_PHONE/READ_PHONE_STATE permissions missing", null) return } val ussdRaw = "*150*1*$secret*1*$amount*$gsmNumber*$gsmNumber#" val encodedUssd = ussdRaw.replace("#", "%23") telephonyManager.sendUssdRequest(encodedUssd, object : TelephonyManager.UssdResponseCallback() { override fun onReceiveUssdResponse(manager: TelephonyManager, req: String, resp: UssdResponse) { result.success(resp.message) } override fun onReceiveUssdResponseFailed(manager: TelephonyManager, req: String, failureCode: Int) { Log.e("USSD", "Failure code: $failureCode") when(failureCode) { TelephonyManager.USSD_RETURN_FAILURE -> result.error("USSD_FAIL", "USSD request rejected", null) TelephonyManager.USSD_RETURN_ERROR -> result.error("USSD_ERR", "Carrier returned error", null) else -> result.error("UNKNOWN", "Code: $failureCode", null) } } }, null) } private fun sendUssdWithIntent(secret: String, amount: String, gsmNumber: String, result: MethodChannel.Result) { val ussdRaw = "*150*1*$secret*1*$amount*$gsmNumber*$gsmNumber#" val encodedUssd = ussdRaw.replace("#", "%23") val intent = android.content.Intent(android.content.Intent.ACTION_CALL, Uri.parse("tel:$encodedUssd")) if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { result.error("PERMISSION_DENIED", "CALL_PHONE permission missing", null) return } startActivity(intent) result.success("USSD initiated via dialer") } }
4. Flutter端权限申请与调用
使用permission_handler包申请权限,再通过MethodChannel调用Android端逻辑:
import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; class UssdManager { static const _channel = MethodChannel('ussd_channel'); static Future<String?> sendUssd({ required String secret, required String amount, required String gsmNumber, }) async { // 申请必要权限 final callPerm = await Permission.phone.request(); final readPerm = await Permission.phone.status; if (callPerm.isDenied || readPerm.isDenied) { throw Exception("Required permissions are not granted"); } try { return await _channel.invokeMethod('sendUssd', { 'secret': secret, 'amount': amount, 'gsmNumber': gsmNumber, }); } on PlatformException catch (e) { throw Exception(e.message); } } }
关键注意事项
- 双卡设备适配:如果是双卡手机,需通过
telephonyManager.createForSubscriptionId(subscriptionId)指定SIM卡,避免默认SIM不支持该USSD服务。 - 运营商限制:部分运营商会禁止第三方应用调用USSD,先手动拨打该USSD码确认是否能正常使用。
- 低版本兼容:API26以下设备无法获取USSD响应结果,只能唤起拨号界面让用户手动确认。
内容的提问来源于stack exchange,提问作者Majd Maihoub
相关产品推荐
相关产品推荐

