Native Android 调用 Channel InvokeMethod 未触发 Flutter 方法
结论
问题出在 PaymentResponse 中新建的 FlutterNativeView。它不属于当前运行中的 Flutter 页面,而是使用了另一套 Flutter 执行环境。通过它创建的 MethodChannel 无法调用当前 Dart isolate 中由 setMethodCallHandler 注册的方法。
应按以下方式处理:
- 在
MainActivity或 Flutter 插件初始化时,使用当前FlutterEngine的binaryMessenger创建唯一的MethodChannel。 - 将这个 channel 共享给
PaymentResponse,或者通过事件桥接对象转发支付结果。 - 在主线程调用
invokeMethod()。 - 不要在支付回调中创建新的
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 的常规应用,上述桥接方式通常已经足够。
备注:内容仅供参考。
