HarmonyOS鸿蒙Next中flutter与原生通信出现了问题
HarmonyOS鸿蒙Next中flutter与原生通信出现了问题
原生端
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved.
*/
import { util } from '@kit.ArkTS';
import { Logger } from './Logger';
// 日志标签
const TAG: string = 'JWSUtil';
// Base64编码填充计算常量
const BASE64_PADDING_MOD: number = 4;
// 无效的Base64填充长度
const BASE64_PADDING_INVALID: number = 1;
/**
* JWS工具类,用于解码JWS格式的订单信息
*/
export class JWSUtil {
/**
* 解码JWS格式的字符串
* @param data JWS格式的字符串
* @returns 解码后的订单信息字符串
*/
public static decodeJwsObj(data: string): string {
// 将JWS字符串按"."分割成三部分(头部、载荷、签名)
const jws: string[] = data.split('.');
let result: string = '';
// 校验JWS格式是否正确(至少包含三部分)
if (jws.length < 3) {
return result;
}
try {
// 创建UTF-8解码器(忽略BOM头)
const textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
// 创建Base64工具实例
const base64 = new util.Base64Helper();
// 获取载荷部分(JWS的第二部分)
let payload = jws[1];
// 计算需要补充的填充字符
const pad = payload.length % BASE64_PADDING_MOD;
if (pad) {
// 无效的填充长度(Base64编码不允许余1的情况)
if (pad === BASE64_PADDING_INVALID) {
throw new Error('InvalidLengthError: 输入的Base64字符串长度无法正确填充');
}
// 补充"="字符使长度满足Base64要求
payload += new Array(BASE64_PADDING_MOD - pad + 1).join('=');
}
// 先Base64解码,再UTF-8解码得到原始字符串
result = textDecoder.decodeToString(base64.decodeSync(payload));
} catch (err) {
Logger.error(TAG, `解码JWS失败: ${JSON.stringify(err)}`);
}
return result;
}
}
import { iap } from '@kit.IAPKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { MethodChannel, MethodResult, MethodCall } from '@ohos/flutter_ohos';
import { FlutterPlugin, FlutterPluginBinding } from '@ohos/flutter_ohos';
import { JWSUtil } from '../common/JWSUtil';
// 类型定义
interface PurchaseData {
type: number;
jwsPurchaseOrder?: string;
}
interface PurchaseOrderPayload {
purchaseOrderId: string;
purchaseToken: string;
productType: string | number;
productId: string;
purchaseTime: number;
finishStatus?: string;
}
interface ProductInfo {
productId: string;
productName: string;
price: string;
description: string;
productType: number;
}
interface PurchasedProductInfo {
orderId: string;
productId: string;
productType: number;
purchaseTime: string;
status: string;
}
interface PurchaseParams {
productId: string;
productType: number;
}
interface EventData {
success?: boolean;
message?: string;
products?: ProductInfo[];
purchases?: PurchasedProductInfo[];
orderId?: string;
productId?: string;
}
export default class HuaweiIapPlugin implements FlutterPlugin {
private methodChannel?: MethodChannel;
private context: common.UIAbilityContext = {} as common.UIAbilityContext;
private api: IapApi;
constructor() {
this.api = new IapApi(this);
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
// 获取应用上下文(关键修复)
this.context = binding.getApplicationContext() as common.UIAbilityContext;
this.methodChannel = new MethodChannel(
binding.getBinaryMessenger(),
'com.nobook.payment/huawei_iap'
);
// 使用that保存当前实例引用
let that = this;
this.methodChannel.setMethodCallHandler({
onMethodCall: (call: MethodCall, result: MethodResult) => {
// 检查上下文有效性
if (!that.context) {
result.error('100', '上下文无效,IAP初始化失败', null);
return;
}
switch (call.method) {
case 'init':
that.api.initializeIap(result);
break;
case 'queryProducts':
that.api.queryProducts(result);
break;
case 'purchaseProduct':
that.api.purchaseProduct(call.args, result);
break;
case 'queryPurchases':
that.api.queryPurchases(result);
break;
default:
result.notImplemented();
break;
}
}
});
}
// 提供给IapApi访问上下文的方法
getContext(): common.UIAbilityContext {
return this.context;
}
// 提供给IapApi发送事件的方法
sendEvent(eventName: string, data: EventData): void {
this.methodChannel?.invokeMethod(eventName, data);
}
onDetachedFromEngine(binding: FlutterPluginBinding): void {
this.methodChannel?.setMethodCallHandler(null);
}
getUniqueClassName(): string {
return "HuaweiIapPlugin";
}
}
// 核心业务逻辑类(分离实现,类似ScreenSaveApi)
class IapApi {
private plugin: HuaweiIapPlugin;
constructor(plugin: HuaweiIapPlugin) {
this.plugin = plugin;
}
// 初始化IAP环境
async initializeIap(result: MethodResult): Promise<void> {
try {
const context = this.plugin.getContext();
// 1. 检查IAP环境
await iap.queryEnvironmentStatus(context);
this.plugin.sendEvent('onEnvironmentChecked', { success: true });
// 2. 查询并处理未完成订单
await this.queryUnfinishedPurchases();
this.plugin.sendEvent('onUnfinishedOrdersQueried', { success: true });
result.success(null);
} catch (err) {
const e = err as BusinessError;
this.plugin.sendEvent('onEnvironmentChecked', {
success: false,
message: `环境检查失败: ${e.code}, ${e.message}`
});
result.error(e.code.toString(), e.message, null);
}
}
// 查询并处理未完成订单
private async queryUnfinishedPurchases(): Promise<void> {
const context = this.plugin.getContext();
const productTypes = [
iap.ProductType.CONSUMABLE,
iap.ProductType.NONCONSUMABLE,
iap.ProductType.AUTORENEWABLE,
iap.ProductType.NONRENEWABLE
];
for (const type of productTypes) {
try {
const param: iap.QueryPurchasesParameter = {
productType: type,
queryType: iap.PurchaseQueryType.UNFINISHED
};
const res = await iap.queryPurchases(context, param);
const purchaseDataList: string[] = res.purchaseDataList || [];
if (purchaseDataList.length > 0) {
purchaseDataList.forEach(data => this.processPurchaseData(data));
}
} catch (err) {
console.error(`查询未完成订单失败: ${JSON.stringify(err)}`);
}
}
}
// 处理购买数据
private processPurchaseData(purchaseData: string): void {
try {
const parsedData = JSON.parse(purchaseData) as PurchaseData;
const jwsOrder = parsedData.jwsPurchaseOrder;
if (!jwsOrder) {
console.error('JWS订单信息无效');
return;
}
const orderStr = JWSUtil.decodeJwsObj(jwsOrder);
if (!orderStr) {
console.error('JWS解码失败');
return;
}
const orderPayload = JSON.parse(orderStr) as PurchaseOrderPayload;
console.warn(`处理未完成订单: ${orderPayload.purchaseOrderId}`);
if (orderPayload.finishStatus !== '1') {
this.deliverOrder(orderPayload);
}
} catch (e) {
console.error(`处理购买数据异常: ${JSON.stringify(e)}`);
}
}
// 确认发货
private async deliverOrder(order: PurchaseOrderPayload): Promise<void> {
try {
const context = this.plugin.getContext();
const param: iap.FinishPurchaseParameter = {
productType: Number(order.productType),
purchaseToken: order.purchaseToken,
purchaseOrderId: order.purchaseOrderId
};
await iap.finishPurchase(context, param);
this.plugin.sendEvent('onDeliveryCompleted', {
orderId: order.purchaseOrderId,
success: true
});
} catch (err) {
const e = err as BusinessError;
this.plugin.sendEvent('onDeliveryCompleted', {
orderId: order.purchaseOrderId,
success: false,
message: `发货失败: ${e.code}, ${e.message}`
});
}
}
// 查询商品
async queryProducts(result: MethodResult): Promise<void> {
try {
const context = this.plugin.getContext();
const productIds = ['prod001', 'prod002', 'prod003'];
const productTypes = [
iap.ProductType.CONSUMABLE,
iap.ProductType.NONCONSUMABLE,
iap.ProductType.AUTORENEWABLE,
iap.ProductType.NONRENEWABLE
];
const products: ProductInfo[] = [];
for (const type of productTypes) {
try {
const param: iap.QueryProductsParameter = {
productType: type,
productIds: productIds
};
const res = await iap.queryProducts(context, param);
products.push(...res.map((p: iap.Product): ProductInfo => ({
productId: p.id,
productName: p.name,
price: p.price,
description: p.description || '无描述',
productType: p.type
})));
} catch (err) {
console.error(`查询商品失败: ${JSON.stringify(err)}`);
}
}
this.plugin.sendEvent('onProductsQueried', { products, success: true });
result.success(null);
} catch (err) {
const e = err as BusinessError;
result.error(e.code.toString(), e.message, null);
}
}
// 购买商品
async purchaseProduct(params: PurchaseParams, result: MethodResult): Promise<void> {
try {
const context = this.plugin.getContext();
const productId = params.productId;
const productType = params.productType;
const param: iap.PurchaseParameter = {
productId: productId,
productType: productType,
quantity: 1
};
const purchaseResult = await iap.createPurchase(context, param);
if (purchaseResult.purchaseData) {
this.processPurchaseData(purchaseResult.purchaseData);
this.plugin.sendEvent('onPurchaseResult', {
success: true,
productId: productId
});
}
result.success(null);
} catch (err) {
const e = err as BusinessError;
this.plugin.sendEvent('onPurchaseResult', {
success: false,
productId: params.productId,
message: `购买失败: ${e.code}, ${e.message}`
});
result.error(e.code.toString(), e.message, null);
}
}
// 查询已购商品
async queryPurchases(result: MethodResult): Promise<void> {
try {
const context = this.plugin.getContext();
const productTypes = [
iap.ProductType.CONSUMABLE,
iap.ProductType.NONCONSUMABLE,
iap.ProductType.AUTORENEWABLE,
iap.ProductType.NONRENEWABLE
];
const purchases: PurchasedProductInfo[] = [];
for (const type of productTypes) {
try {
const param: iap.QueryPurchasesParameter = {
productType: type,
queryType: iap.PurchaseQueryType.ALL
};
const res = await iap.queryPurchases(context, param);
const purchaseDataList = res.purchaseDataList || [];
for (const data of purchaseDataList) {
const parsedData = JSON.parse(data) as PurchaseData;
const jwsOrder = parsedData.jwsPurchaseOrder;
if (!jwsOrder) {
continue;
}
const orderStr = JWSUtil.decodeJwsObj(jwsOrder);
const orderPayload = JSON.parse(orderStr) as PurchaseOrderPayload;
purchases.push({
orderId: orderPayload.purchaseOrderId,
productId: orderPayload.productId,
productType: Number(orderPayload.productType),
purchaseTime: new Date(orderPayload.purchaseTime).toLocaleString(),
status: orderPayload.finishStatus === '1' ? '已完成' : '未完成'
});
}
} catch (err) {
console.error(`查询已购商品失败: ${JSON.stringify(err)}`);
}
}
this.plugin.sendEvent('onPurchasesQueried', { purchases, success: true });
result.success(null);
} catch (err) {
const e = err as BusinessError;
result.error(e.code.toString(), e.message, null);
}
}
}
flutter端
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HuaweiIapPage extends StatefulWidget {
const HuaweiIapPage({Key? key}) : super(key: key);
@override
State<HuaweiIapPage> createState() => _HuaweiIapPageState();
}
class _HuaweiIapPageState extends State<HuaweiIapPage> {
// 统一MethodChannel实例,与原生端保持一致
final MethodChannel _iapChannel =
const MethodChannel('com.nobook.payment/huawei_iap');
// 状态管理
bool _isProcessing = false;
String _statusMessage = '准备就绪';
List<Map<String, dynamic>> _products = [];
List<Map<String, dynamic>> _purchases = [];
bool _showPurchases = false;
@override
void initState() {
super.initState();
// 初始化监听器
_initListener();
// 初始化IAP
_initialize();
}
// 初始化事件监听器
void _initListener() {
_iapChannel.setMethodCallHandler(_handleIapEvent);
}
// 初始化IAP环境
Future<void> _initialize() async {
_setProcessingState(true, '初始化IAP环境...');
try {
// 调用原生端初始化方法
await _iapChannel.invokeMethod('init');
} on PlatformException catch (e) {
_updateStatus('初始化失败: ${e.message}', isError: true);
}
}
// 处理原生端发送的事件
Future<dynamic> _handleIapEvent(MethodCall call) async {
switch (call.method) {
case 'onEnvironmentChecked':
final bool success = call.arguments['success'] as bool;
if (success) {
_updateStatus('环境检查通过');
_queryProducts();
} else {
_updateStatus('环境不支持IAP: ${call.arguments['message']}',
isError: true);
}
break;
case 'onUnfinishedOrdersQueried':
_updateStatus('未完成订单已处理');
break;
case 'onProductsQueried':
setState(() {
_products = List<Map<String, dynamic>>.from(call.arguments['products']);
_isProcessing = false;
});
_updateStatus('获取到${_products.length}个商品');
break;
case 'onPurchaseResult':
final bool success = call.arguments['success'] as bool;
final String productId = call.arguments['productId'] as String;
if (success) {
_updateStatus('购买成功: $productId');
_queryPurchases();
} else {
_updateStatus('购买失败: ${call.arguments['message']}', isError: true);
}
break;
case 'onPurchasesQueried':
setState(() {
_purchases = List<Map<String, dynamic>>.from(call.arguments['purchases']);
_isProcessing = false;
});
_updateStatus('查询到${_purchases.length}条购买记录');
break;
case 'onDeliveryCompleted':
final String orderId = call.arguments['orderId'] as String;
_updateStatus('订单已发货: $orderId');
break;
}
return null;
}
// 查询商品列表
Future<void> _queryProducts() async {
_updateStatus('正在查询商品...');
try {
await _iapChannel.invokeMethod('queryProducts');
} on PlatformException catch (e) {
_updateStatus('查询商品失败: ${e.message}', isError: true);
}
}
// 购买商品
Future<void> _purchaseProduct(String productId, int productType) async {
_updateStatus('正在购买: $productId...');
try {
await _iapChannel.invokeMethod(
'purchaseProduct',
{
'productId': productId,
'productType': productType,
},
);
} on PlatformException catch (e) {
_updateStatus('购买请求失败: ${e.message}', isError: true);
}
}
// 查询已购商品
Future<void> _queryPurchases() async {
_updateStatus('正在查询购买记录...');
try {
await _iapChannel.invokeMethod('queryPurchases');
} on PlatformException catch (e) {
_updateStatus('查询失败: ${e.message}', isError: true);
}
}
// 更新状态信息(封装状态更新逻辑)
void _updateStatus(String message, {bool isError = false}) {
setState(() {
_statusMessage = message;
if (isError) {
_isProcessing = false;
}
});
}
// 设置处理中状态
void _setProcessingState(bool isProcessing, String message) {
setState(() {
_isProcessing = isProcessing;
_statusMessage = message;
});
}
// 获取商品类型名称
String _getProductTypeName(int type) {
switch (type) {
case 0:
return '消耗型';
case 1:
return '非消耗型';
case 2:
return '自动续期订阅';
case 3:
return '非续期订阅';
default:
return '未知类型';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('华为应用内支付'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
body: Column(
children: [
// 状态信息展示区
Container(
padding: const EdgeInsets.all(16),
color: Colors.grey[200],
child: Row(
children: [
if (_isProcessing)
const Padding(
padding: EdgeInsets.only(right: 8),
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
Expanded(
child: Text(
_statusMessage,
style: TextStyle(
color: _statusMessage.contains('失败')
? Colors.red
: Colors.black,
),
),
),
],
),
),
// 操作按钮区
Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: _isProcessing ? null : _initialize,
child: const Text('重新初始化'),
),
ElevatedButton(
onPressed: _isProcessing ? null : _queryProducts,
child: const Text('刷新商品'),
),
ElevatedButton(
onPressed: _isProcessing ? null : _queryPurchases,
child: const Text('查询购买记录'),
),
ElevatedButton(
onPressed: () =>
setState(() => _showPurchases = !_showPurchases),
child: Text(_showPurchases ? '显示商品' : '显示购买记录'),
),
],
),
),
// 内容展示区
Expanded(
child: _showPurchases ? _buildPurchasesList() : _buildProductsList(),
),
],
),
);
}
// 构建商品列表
Widget _buildProductsList() {
if (_products.isEmpty) {
return const Center(child: Text('暂无商品信息'));
}
return ListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
return ListTile(
leading: const Icon(Icons.shopping_bag, size: 36),
title: Text(product['productName']),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product['description']),
Text('价格: ${product['price']}'),
Text('类型: ${_getProductTypeName(product['productType'])}'),
],
),
trailing: ElevatedButton(
onPressed: _isProcessing
? null
: () => _purchaseProduct(
product['productId'], product['productType']),
child: const Text('购买'),
),
);
},
);
}
// 构建购买记录列表
Widget _buildPurchasesList() {
if (_purchases.isEmpty) {
return const Center(child: Text('暂无购买记录'));
}
return ListView.builder(
itemCount: _purchases.length,
itemBuilder: (context, index) {
final purchase = _purchases[index];
return ListTile(
leading: const Icon(Icons.receipt, size: 36),
title: Text('订单: ${purchase['orderId']}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('商品: ${purchase['productId']}'),
Text('时间: ${purchase['purchaseTime']}'),
Text('状态: ${purchase['status']}'),
Text('类型: ${_getProductTypeName(purchase['productType'])}'),
],
),
);
},
);
}
}
他一直是初始化失败:“初始化失败:BusinessError 1001860001:System iinternal error”
更多关于HarmonyOS鸿蒙Next中flutter与原生通信出现了问题的实战教程也可以访问 https://www.itying.com/category-92-b0.html
2 回复
在HarmonyOS Next中,Flutter与原生通信问题通常涉及Platform Channels的实现。确保Flutter端MethodChannel名称与原生端完全一致。鸿蒙侧需使用ohos.ability上下文正确注册MethodHandler,并检查数据序列化格式(JSON)。常见问题包括线程冲突(需使用鸿蒙的TaskDispatcher切换主线程)、权限未声明或ArkCompiler对JSI调用的限制。
更多关于HarmonyOS鸿蒙Next中flutter与原生通信出现了问题的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html