PAYATHON 2026

Native Android 调用 Channel InvokeMethod 未触发 Flutter 方法

支付老李

结论

问题出在 PaymentResponse 中新建的 FlutterNativeView。它不属于当前运行中的 Flutter 页面,而是使用了另一套 Flutter 执行环境。通过它创建的 MethodChannel 无法调用当前 Dart isolate 中由 setMethodCallHandler 注册的方法。

应按以下方式处理:

  1. 在 MainActivity 或 Flutter 插件初始化时,使用当前 FlutterEngine 的 binaryMessenger 创建唯一的 MethodChannel。
  2. 将这个 channel 共享给 PaymentResponse,或者通过事件桥接对象转发支付结果。
  3. 在主线程调用 invokeMethod()。
  4. 不要在支付回调中创建新的 FlutterNativeView 或 FlutterEngine。

为什么 Flutter 没有收到回调

MethodChannel 的连接关系不只取决于 channel 名称。它还绑定了具体的 BinaryMessenger,而每个 BinaryMessenger 又属于特定的 FlutterEngine。

当前代码在回调中执行了:

backgroundFlutterView = FlutterNativeView(p1, true)
val channel = MethodChannel(backgroundFlutterView, MainActivity.CHANNEL)

这段代码临时创建了另一套 Flutter 运行环境。即使 MainActivity.CHANNEL 与 Dart 端使用的字符串完全一致,它们也不在同一个 engine 中,所以 Dart 端注册的 _handlePaymentResponse 不会被调用。

另外,FlutterNativeView 属于旧版 Android embedding API。新代码不应再用它发送 platform channel 消息。

推荐实现:复用当前 FlutterEngine 的 MethodChannel

可以在 MainActivity 中创建 channel,再通过桥接对象把支付结果发送给 Flutter。

Dart 端

调用和回调需要使用同一个 channel 名称:

import 'package:flutter/services.dart';

class PaymentPageState extends State<PaymentPage> {
  static const MethodChannel platform =
      MethodChannel('your.payment.channel');

  @override
  void initState() {
    super.initState();
    platform.setMethodCallHandler(_handlePaymentResponse);
  }

  Future<dynamic> _handlePaymentResponse(MethodCall call) async {
    switch (call.method) {
      case 'message':
        final String? status = call.arguments as String?;
        debugPrint('From Native====$status');
        return '';
      default:
        throw MissingPluginException(
          'Unsupported native method: ${call.method}',
        );
    }
  }

  Future<void> _initializeSDK() async {
    try {
      await platform.invokeMethod<void>('callPaymentSDK');
    } on PlatformException catch (e) {
      debugPrint("Failed to initialize SDK: '${e.message}'.");
    }
  }
}

创建支付结果桥接对象

import android.os.Handler
import android.os.Looper
import io.flutter.plugin.common.MethodChannel

object PaymentResultBridge {

    private var channel: MethodChannel? = null
    private val mainHandler = Handler(Looper.getMainLooper())

    fun attach(methodChannel: MethodChannel) {
        channel = methodChannel
    }

    fun detach() {
        channel = null
    }

    fun sendPaymentStatus(status: String?) {
        mainHandler.post {
            channel?.invokeMethod("message", status)
        }
    }
}

在 MainActivity 中注册同一个 channel

使用 Flutter Android embedding v2 时,可以在 configureFlutterEngine() 中注册:

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {

    companion object {
        const val CHANNEL = "your.payment.channel"
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        val channel = MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            CHANNEL
        )

        PaymentResultBridge.attach(channel)

        channel.setMethodCallHandler { call, result ->
            when (call.method) {
                "callPaymentSDK" -> {
                    // 在这里初始化或调用支付 SDK。
                    // 如果初始化可能失败,应根据实际结果调用:
                    // result.success(...)
                    // result.error(...)
                    startPaymentSdk()
                    result.success(null)
                }

                else -> result.notImplemented()
            }
        }
    }

    override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
        PaymentResultBridge.detach()
        super.cleanUpFlutterEngine(flutterEngine)
    }

    private fun startPaymentSdk() {
        // 调用支付 SDK
    }
}

在非 Activity 回调中发送结果

PaymentResponse 不需要创建 Flutter View,也不需要继承 Activity。它只负责将状态交给桥接对象:

class PaymentResponse : LibraryPaymentStatusProtocol, Parcelable {

    override fun paymentStatus(status: String?, activity: Activity?) {
        PaymentResultBridge.sendPaymentStatus(status)
    }

    // Parcelable 所需实现按项目现有代码保留。
}

这里也不需要把 activity != null 作为发送状态的条件。调用 Flutter channel 依赖的是已经注册的 MethodChannel,不是 SDK 回调参数中的 Activity。除非支付 SDK 的其他操作确实需要该 Activity,否则不应因为它是 null 就丢弃支付结果。

更适合插件项目的写法

如果支付功能封装在 Flutter 插件中,可以在 FlutterPlugin.onAttachedToEngine() 中创建 channel:

class PaymentPlugin : FlutterPlugin {

    private lateinit var channel: MethodChannel

    override fun onAttachedToEngine(
        binding: FlutterPlugin.FlutterPluginBinding
    ) {
        channel = MethodChannel(
            binding.binaryMessenger,
            "your.payment.channel"
        )

        PaymentResultBridge.attach(channel)
    }

    override fun onDetachedFromEngine(
        binding: FlutterPlugin.FlutterPluginBinding
    ) {
        PaymentResultBridge.detach()
    }
}

无论采用哪种写法,发送回调的 channel 都必须使用承载当前 Dart isolate 的 FlutterEngine.binaryMessenger。

生命周期与时序注意事项

Dart handler 必须已经注册

如果支付结果在页面执行 initState() 前到达,或者在页面销毁后才返回,Dart 端就没有对应的 handler。

如果支付回调是应用级事件,建议在应用启动阶段或长期存在的 service/controller 中注册 channel,不要只在生命周期较短的页面中注册。

如果结果可能在 Flutter 就绪前到达,可以先在原生端暂存最后一次状态,等 channel attach 后再发送:

object PaymentResultBridge {

    private var channel: MethodChannel? = null
    private var pendingStatus: String? = null
    private val mainHandler = Handler(Looper.getMainLooper())

    fun attach(methodChannel: MethodChannel) {
        channel = methodChannel

        pendingStatus?.let { status ->
            pendingStatus = null
            sendPaymentStatus(status)
        }
    }

    fun detach() {
        channel = null
    }

    fun sendPaymentStatus(status: String?) {
        mainHandler.post {
            val currentChannel = channel

            if (currentChannel == null) {
                pendingStatus = status
            } else {
                currentChannel.invokeMethod("message", status)
            }
        }
    }
}

如果 null 本身也是有效的支付状态,就不能用可空字符串同时表示“没有待发送结果”和“待发送结果为 null”。这种情况应增加一个布尔标记,或者改用数据类。

必须切换到主线程

SDK 的支付回调可能运行在后台线程。调用 MethodChannel.invokeMethod() 前,最好先切换到 Android 主线程:

Handler(Looper.getMainLooper()).post {
    channel.invokeMethod("message", status)
}

channel 名称必须完全一致

Dart 和 Kotlin 两端的名称必须逐字符一致:

const MethodChannel('your.payment.channel');
MethodChannel(binaryMessenger, "your.payment.channel")

不过,名称一致只是必要条件。两端还必须连接到同一个 Flutter engine。

不要长期持有 Activity

支付回调对象的生命周期可能比 MainActivity 更长。把 Activity 保存在全局单例或静态字段中,容易造成内存泄漏。这里共享的只是与当前 engine 绑定的 channel,并在 engine 销毁时调用 detach()。

如果应用明确使用多个 FlutterEngine,还需要按 engine 或业务会话分别管理 channel,不能让一个全局 channel 随意覆盖其他实例。对于只有一个 FlutterActivity 和一个 FlutterEngine 的常规应用,上述桥接方式通常已经足够。

备注:内容仅供参考。