Android端调用Stripe.createPaymentMethod致应用卡顿并终止执行
Flutter Stripe卡片保存:卡顿与Isolate使用问题
问题描述
使用flutter_stripe实现应用内卡片保存功能,流程如下:
- 调用
Stripe.instance.createPaymentMethod - 调用
Stripe.instance.createToken - 将Token提交至RestAPI
Android端出现两个核心问题:
- 步骤1执行耗时久,引发主线程卡顿(日志显示跳过73帧)
- 步骤1完成后,后续步骤因类型转换错误终止执行;尝试用Isolate优化性能时,又遇到配置相关的异常
代码片段
步骤1:创建PaymentMethod
await Stripe.instance.createPaymentMethod( params: PaymentMethodParams.card( paymentMethodData: PaymentMethodData( billingDetails: BillingDetails(name: cardHolderName), // 备注:此参数未生效 ), ), options: const PaymentMethodOptions( setupFutureUsage: PaymentIntentsFutureUsage.OnSession));
步骤2-3:创建Token并提交API
await Stripe.instance .createToken(const CreateTokenParams.card( params: CardTokenParams( type: TokenType.Card, ))).then((value) async { print('Token Received'); // 调用API提交Token await createCard(value.id); }).onError((error, stackTrace) { getErrorDialog(error); });
报错日志
执行日志(卡顿+类型转换错误)
I/flutter (18568): Started Execution I/ViewRootImpl(18568): jank_removeInvalidNode jank list is null ...(重复的jank日志) I/flutter (18568): Pay Method Created I/Choreographer(18568): Skipped 73 frames! The application may be doing too much work on its main thread. ... E/flutter ( 1627): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'int' is not a subtype of type 'String' in type cast E/flutter ( 1627): #0 _$$_TokenDataFromJson (package:stripe_platform_interface/src/models/create_token_data.g.dart:146:40) E/flutter ( 1627): #1 new _$_TokenData.fromJson (package:stripe_platform_interface/src/models/create_token_data.freezed.dart:1777:7) ...(调用栈省略)
使用compute时的报错
Unhandled Exception: Instance of 'StripeConfigException'
重新设置publishKey后的报错
Unhandled Exception: Bad state: The BackgroundIsolateBinaryMessenger.instance value is invalid until BackgroundIsolateBinaryMessenger.ensureInitialized is executed. E/flutter (29872): #0 BackgroundIsolateBinaryMessenger.instance (package:flutter/src/services/_background_isolate_binary_messenger_io.dart:27:7) ...
解决方案
1. 修复类型转换错误(优先解决)
日志中的类型转换错误是因为createToken的参数不完整,导致Stripe返回的Token数据解析失败。若使用CardField组件获取卡片信息,需从组件状态中提取完整卡片详情后再调用createToken:
// 假设cardField是CardField组件的GlobalKey final cardFieldState = cardField.currentState; final cardDetails = await cardFieldState?.completeCardDetails(); if (cardDetails != null) { final tokenResult = await Stripe.instance.createToken( CreateTokenParams.card( params: CardTokenParams( number: cardDetails.number, expMonth: cardDetails.expMonth, expYear: cardDetails.expYear, cvc: cardDetails.cvc, ), ), ); // 提交Token到API await createCard(tokenResult.id); }
2. 解决主线程卡顿问题
flutter_stripe的异步方法理论上不会阻塞主线程,但Android端底层可能存在同步操作。可以通过以下方式优化:
- 显示加载动画:在执行步骤1时展示加载状态,避免用户感知卡顿
- 避免不必要的操作:检查是否有其他同步计算或UI操作与步骤1并行执行
- 正确使用Isolate(仅当必须时):若必须将Stripe操作移到后台Isolate,需先初始化
BackgroundIsolateBinaryMessenger,并重新配置Stripe:
import 'dart:isolate'; import 'package:flutter/services.dart'; import 'package:flutter_stripe/flutter_stripe.dart'; Future<void> runStripeInBackground(String publishKey, String cardHolderName, CardDetails cardDetails) async { final result = await Isolate.run(() async { // 初始化后台Isolate的Messenger BackgroundIsolateBinaryMessenger.ensureInitialized(); // 重新配置Stripe Stripe.publishableKey = publishKey; await Stripe.instance.applySettings(); // 执行PaymentMethod创建 final paymentMethod = await Stripe.instance.createPaymentMethod( params: PaymentMethodParams.card( paymentMethodData: PaymentMethodData( billingDetails: BillingDetails(name: cardHolderName), ), ), options: const PaymentMethodOptions( setupFutureUsage: PaymentIntentsFutureUsage.OnSession, ), ); // 创建Token final tokenResult = await Stripe.instance.createToken( CreateTokenParams.card( params: CardTokenParams( number: cardDetails.number, expMonth: cardDetails.expMonth, expYear: cardDetails.expYear, cvc: cardDetails.cvc, ), ), ); return {'paymentMethodId': paymentMethod.id, 'tokenId': tokenResult.id}; }); // 在主线程处理结果(如提交API、更新UI) await createCard(result['tokenId']); }
注意:Isolate无法直接访问UI组件状态,需将卡片详情等数据作为参数传递。
3. 优化流程(推荐)
Stripe官方推荐使用SetupIntent保存卡片,无需手动创建PaymentMethod和Token,流程更高效且减少前端操作:
- 后端创建SetupIntent并返回
clientSecret - 前端调用
confirmSetupIntent完成卡片保存:
await Stripe.instance.confirmSetupIntent( clientSecret, params: SetupIntentConfirmParams.card( billingDetails: BillingDetails(name: cardHolderName), ), );
- 后端通过SetupIntent事件获取保存的PaymentMethod,无需前端传递Token
内容的提问来源于stack exchange,提问作者ASAD HAMEED
相关产品推荐
相关产品推荐

