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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 06:28:21