在之前写过的《Flutter进阶—平台插件》中,笔者简单介绍了如何简单的使用和创建一个Flutter插件,现在可以试试编写一个可以在平台与客户端之间传递数据的Flutter平台插件。在此之前,可以先了解一下平台插件的基本原理。
上面图片是平台通道的结构概述,使用MethodChannel
在客户端(UI)和主机(平台)之间传递消息,消息和响应异步传递,以确保用户界面保持响应。在客户端,Flutter的MethodChannel
类可以发送与方法调用相对应的消息。在平台,Android上的MethodChannel
类和iOS上的FlutterMethodChannel
类可以接收方法调用并发送结果。这些类允许开发者开发一个平台插件,而且如果需要,方法调用也可以朝相反的方向发送,平台作为实现Dart方法的客户端。
标准平台通道使用的是标准消息编解码器,支持简单高效的将JSON格式的值二进制序列化,例如布尔值、数字、字符串、字节缓冲区以及这些数据的列表和映射,具体可以查看StandardMessageCodec类。发送和接收值时,会自动对这些值进行序列化和反序列化。下面的表格展示了在平台端如何接收Dart值,反之亦然。
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
int, if 64 bits not enough | java.math.BigInteger | FlutterStandardBigInteger |
double | java.lang.Double | NSNumber numberWithDouble: |
String | java.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
了解完原理之后,可以通过idea编辑器创建一个新的Flutter项目,名称为share_to_wechat
。默认情况下,默认的项目模板是使用Java编写Android代码、使用Objective-C编写iOS代码。如果需要使用Kotlin或Swift,可以使用-i
与-a
标志,比如执行flutter create -i swift -a kotlin share_to_wechat
命令。
首先,需要创建Flutter平台客户端,构建通道,使用具有基本传递数据功能的单平台方法的MethodChannel
。通道的客户端和主机端通过通道构造函数中传递的通道名来连接。单个应用中使用的所有通道名称必须是唯一的,官方的建议是使用唯一的前缀来命名,例如samples.flutter.test/plugin
。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
//...
class _MyHomePageState extends State {
static const platform = const MethodChannel('samples.flutter.test/plugin');
//...
}
接下来在MethodChannel
上调用一个方法,指定通过String
标识符dataInteraction
调用的具体方法。如果当前平台不支持平台API,那么调用可能会失败,因此需要将invokeMethod
调用包含在try-catch
语句中。使用返回的结果来更新_returnData
,以更新控件状态。
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.test/plugin');
String _returnData = '';
Future<Null> _dataInteraction() async {
String returnData;
try {
final int result = await platform.invokeMethod('dataInteraction');
returnData = '平台返回数据:$result';
} on PlatformException catch (e) {
returnData = '错误信息:${e.message}';
}
setState((){
_returnData = returnData;
});
}
//...
}
最后将模板中的build
方法替换为包含返回信息的文本控件,以及用于刷新返回信息的按钮。
class _MyHomePageState extends State<MyHomePage> {
//...
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Text('${_returnData}'),
),
floatingActionButton: new FloatingActionButton(
onPressed: _dataInteraction,
tooltip: '获取平台数据',
child: new Icon(Icons.autorenew),
),
);
}
}
在编写完Flutter部分的代码之后,使用Java编写Android平台特定的实现代码。为了插件的稳定性,在这里使用Java和Objective-C代码,如果喜欢也可以使用Kotlin和Swift的。首先在Android Studio中打开Flutter项目的Android主机部分,具体操作如下:
android
文件夹java
文件夹中的MainActivity.java
文件接下来,创建一个MethodChannel
并在onCreate
方法中设置一个MethodCallHandler
,这里要确保使用与Flutter客户端上一样的通道名称。
package com.yourcompany.testplugin;
import android.os.Bundle;
import java.util.Random;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.test/plugin";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(new MethodCallHandler(){
@Override
public void onMethodCall(MethodCall call, Result result){
}
});
GeneratedPluginRegistrant.registerWith(this);
}
}
然后编写Java代码,用于调用Android上的随机函数,这跟在Android项目上编写的代码完全一样。在MainActivity
类的onCreate
方法下面中添加以下内容。
public class MainActivity extends FlutterActivity {
//...
@Override
public void onMethodCall(MethodCall call, Result result){
}
private int getData() {
Random ran = new Random();
return ran.nextInt(1000);
}
}
最后,在完成了之前添加的onMethodCall
方法后,还需要处理一个平台方法dataInteraction
,所以需要在call
参数中测试它。这个平台方法的实现只是调用在上一步中编写的Android代码,并使用response
参数传回成功和错误情况的响应。如果调用了一个未知的方法,会报告错误信息,修改下面的代码。
原代码:
public void onMethodCall(MethodCall call, Result result){
}
修改代码:
public void onMethodCall(MethodCall call, Result result){
if(call.method.equals("dataInteraction")) {
int data = getData();
result.success(data);
}else{
result.notImplemented();
}
}
现在就能够在Android上运行应用程序了,点击右下方的按钮就能获取Android主机返回的随机数字。
在确定Android主机与Flutter客户端沟通正常后,在Xcode中打开Flutter项目中的iOS主机部分:
ios
文件夹AppDelegate.m
文件接下来,创建一个FlutterMethodChannel
并在里面添加一个application didFinishLaunchingWithOptions:
方法。这里需要确保与Flutter客户端上使用的是同一个通道名称。
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.test/plugin"
binaryMessenger:controller];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
}];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
接下来,使用ObjectiveC代码添加获取随机数的功能,这段代码与在本机iOS应用程序中编写的代码完全相同。在AppDelegate
类中添加以下作为新方法,添加在@end
之前。
- (int)getData {
int ran = arc4random() % 1000;
return ran;
}
@end
最后,在完成了之前添加的setMethodCallHandler
方法之后。还需要处理一个平台方法getData
,所以要在call
参数中测试。该平台方法的实现只需调用在上一步中编写的iOS代码,并使用result
参数传回成功和错误情况的响应。如果调用了一个未知的方法,会报告错误信息。
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"dataInteraction" isEqualToString:call.method]) {
int data = [self getData];
result (@(data));
} else {
result(FlutterMethodNotImplemented);
}
}];
现在就能够在iOS上运行应用程序了,点击右下方的按钮就能获取iOS主机返回的随机数字。
通过完成前面的例子可以看出,平台插件的实现其实很简单。只要把上面例子的getData方便修改成想要实现的平台特定功能,就能实现对应的平台插件,比如官方插件教程中就实现了通过特定平台API获取电池电量。