赞
踩
前言:Flutter系列的文章我应该会持续更新至少一个月左右,从User Interface(UI)到数据相关(文件、数据库、网络)再到Flutter进阶(平台特定代码编写、测试、插件开发等),欢迎感兴趣的读者持续关注(可以扫描左边栏二维码或者搜索”IT工匠“关注微信公众号/头条号(微信公众号和头条号同名),会同步推送)。
本文主要介绍如何编写平台特定的代码,Flutter
使用了一套灵活的系统以保证我们可以调用特定平台的API
,这里的**"特定平台的API”**可以是由Android
上的Java
或Kotlin
代码构建的,也可以是iOS
上由ObjectiveC
或Swift
代码构建的。
Flutter
对特定平台API
的调用是基于这样一套流程的:
Flutter
代码通过平台通道(platform channel
)将消息发送到原生代码层即我们的特定平台的代码层(iOS
或Android
)。iOS
或者Android
层的原生代码通过对平台通道的监听,在合适的时机接收Flutter
代码发送的消息。iOS
或者Android
层的原生代码根据接收到的消息调用不同的本平台的原生API
iOS
或者Android
层的原生代码将原生API
的执行结果返回给Flutter
层的代码整个过程的示意图是这样的:
注意:如果你需要在
Java/Kotlin/Objective-C/Swift
中使用特定平台独有的API
或库,可以通过本文的内容进行实现,但是,如果你只是想根据不同的平台执行不同的代码,则不需要使用本文的办法编写平台特定的代码,只需要在Flutter
应用程序通过检查defaultTargetPlatform
属性的值来确定当前程序运行的平台,然后根据当前平台调用不同的Dart
代码即可。
这里我们首先规定两个术语,我们将Flutter
层的代码称为客户端代码,将Android
或iOS
层的平台特定代码称为宿主端代码。
使用平台通道在客户端(Flutter UI
)和宿主(Android
或iOS
平台)之间传递消息的原理示意图如下:
发送消息和等待响应都是异步的过程,这样可以保证不会造成我们用户界面的阻塞。
在客户端(Flutter
层),MethodChannel
API可以发送与方法调用相对应的消息。
在宿主平台上(Android
或iOS
层),Android
上的MethodChannel
API和iOS
上的FlutterMethodChannel
API 可以通过接收从平台通道传递过来的方法调用请求从而进行对应的方法的调用,最后将调用结果通过平台通道返回给客户端。
注意: 如果需要,特定平台也可以反过来调用Flutter
层的API
。
标准平台通道使用标准消息编解码器,以支持简单的类似JSON
值的高效二进制序列化,例如 booleans,numbers, Strings, byte buffers, List, Maps
等, 当你通过平台通道进行消息的发送和接收时,被发送的消息会自动进行序列化(发送时)和反序列化(接收时)。
下表列出了Dart
语言中的数据类型在Android
和iOS
的对应类型:
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int , 如果32位int 不够用 | java.lang.Long | NSNumber numberWithLong: |
int , 如果64位int 不够用 | 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 |
下面我们来通过一个实例展示如何在Flutter
中实现特定平台API
的调用,我们要实现的功能是通过调用平台特定的API
来获取和显示当前设备的电池电量。实现的方法是通过调用Android
端的BatteryManager API
和iOS
端的 device.batteryLevel API
进行当前设备电量的获取。
首先创建一个新的应用程序:
flutter create batterylevel
默认情况下,模板支持使用Java
编写Android
代码,或使用Objective-C
编写iOS
代码。要使用Kotlin
或Swift
,请使用-i
和/或-a
标志:
flutter create -i swift -a kotlin batterylevel
应用的State
类拥有当前的应用状态,我们需要继承这个类以托管当前设备的电量。
首先,我们构造一个通道(MethodChannel
),在构造通道的时候需要传入一个通道名称,这个通道名称是客户端和宿主端进行连接的纽带(或者理解为密匙),我们所指定的通道名称必须在本app内是全局唯一的,Flutter官方的建议是在通道名称前加一个唯一的“域名前缀”
以避免通道名称的冲突,例如flutter_demo_code.channel_demo.chennel/battery
:
class _MyAppState extends State<MyApp> { static const platform = const MethodChannel('flutter_demo_code.channel_demo.chennel/battery'); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('IT工匠'), ), body: Center( child: Text('demo'), ), ), ); } }
构件好通道(MethodChannel
)之后,我们就可以通过构建好的通道调用特定平台的方法了,这里使用的是MethodChannel.invokeMethod<T>(String method, [ dynamic arguments ])
方法,该方法接收两个参数:
method
:String
类型的参数,表示特定平台上被调用的方法名称[ dynamic arguments ]
:一个数组,表示的是调用特定平台上方法时传入的参数以我们这个例子来说,我们稍后会在Android
端和iOS
端分别写一个int getBatteryLevel()
方法,这个方法的作用是获取当前设备的电量并返回,那么这里的method
就应该传入getatteryLevel
这个字符串,注意没有()
,而如果还需要传递参数,直接构造一个[ dynamic arguments]
类型的参数并传递进去即可,我们来看我们的代码实现(注意看注释):
String _batteryLevel = '电池电量未知'; Future<Null> _getBatteryLevel() async { String batteryLevel; /** 这里使用了一个try…catch将 platform.invokeMethod()方法包裹了起来,原因是该方法的调用是有可能失败的,或者说是有可能抛出异常的,比如当平台不支持平台API(例如在模拟器中运行)时。 */ try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = '当前电量:$result % .'; } on PlatformException catch (e) { batteryLevel = "获取电池电量失败,失败原因: '${e.message}'."; } //使用返回的结果,在setState中来更新用户界面状态batteryLevel。 setState(() { _batteryLevel = batteryLevel; });
最后,我们在build()
中分别添加一个用于显示当前电池电量的Text
和用于更新当前电池电量的RaisedButton
:
@override Widget build(BuildContext context) { return new Material( child: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ new RaisedButton( child: new Text('获取当前设备电量'), onPressed: _getBatteryLevel, ), new Text(_batteryLevel), ], ), ), ); }
首先在Android Studio
中打开您的Flutter
应用的Android
部分(当然,如果你习惯直接在Flutter
项目中修改iOS
部分也是没问题的):
Android Studio
‘File > Open…’
Flutter app
目录, 然后选择里面的 android
文件夹,点击 OK
java
目录下打开 MainActivity.java
接下来,在onCreate()
里做两件事:
MethodChannel
)的实例,在构造管道的时候传入我们刚才在Flutter
平台上指定的管道名称flutter_demo_code.channel_demo.chennel/battery
MethodChannel
)类设置MethodCallHandler
,相当于对管道进行监听,当Flutter
调用本管道的方法时,会在MethodCallHandler
的onMethodCall()
方法收到回调具体实现代码如下:
import io.flutter.app.FlutterActivity; 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 = "flutter_demo_code.channel_demo.chennel/battery"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( new MethodCallHandler() { @Override public void onMethodCall(MethodCall call, Result result) { // TODO } }); } }
然后我们使用Java
代码,基于Android
电池API
来获取当前设备的电量:
private int getBatteryLevel() {
int batteryLevel = -1;
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
} else {
Intent intent = new ContextWrapper(getApplicationContext()).
registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
}
return batteryLevel;
}
最后,我们完成之前添加的onMethodCall()
方法。我们需要处理管道中方法名为getBatteryLevel
的调用,所以我们对call
参数进行检查,看当前call
参数中的方法名是否为getBatteryLevel
,如果是,则调用上一步编写的getBatteryLevel()
方法并将结果通过result
返回给Flutter
层:
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getBatteryLevel")) {
int batteryLevel = getBatteryLevel();
if (batteryLevel != -1) {
result.success(batteryLevel);
} else {
//如果出现错误调用这个方法,Flutter层会抛出PlatformException异常
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
//Flutter层会抛出MissingPluginException异常
result.notImplemented();
}
}
现我们就可以在Android
设备上运行该Flutter
程序,运行效果是这样的:
需要说明的是,本小节在Android
端使用的是Java
语言,如果你想使用Kotlin
,也是完全可以的,逻辑完全一样,只是代码语法不同而已。
使用Xcode
打开Flutter
应用程序中的iOS
部分(当然,如果你习惯直接在Flutter
项目中修改iOS
部分也是没问题的):
Xcode
‘File > Open…’
Flutter app
目录, 然后选择里面的 iOS
文件夹,点击 OK
Xcode
项目的构建没有错误。Runner > Runner
,打开AppDelegate.m
接下来,进行两步操作:
application didFinishLaunchingWithOptions:
方法内部创建一个管道(FlutterMethodChannel
),确保管道名称为flutter_demo_code.channel_demo.chennel/battery
实现代码如下:
- (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:@"flutter_demo_code.channel_demo.chennel/battery"
binaryMessenger:controller];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
//TODO
}];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
然后,我们基于ObjectiveC
代码,通过iOS
电池API
来获取电池电量:
- (int)getBatteryLevel {
UIDevice* device = UIDevice.currentDevice;
device.batteryMonitoringEnabled = YES;
if (device.batteryState == UIDeviceBatteryStateUnknown) {
return -1;
} else {
return (int)(device.batteryLevel * 100);
}
}
最后,我们完成之前添加的setMethodCallHandler
方法,对平台方法名为getBatteryLevel
的管道调用进行捕捉和处理,将处理的返回结果返回给Flutter
层:
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [self getBatteryLevel];
if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];
现我们就可以在Android
设备上运行该Flutter
程序了,由于贫穷限制了笔者,所以此处放不出真机演示图,大家自行脑补。
需要说明的是,本小节在iOS
端使用的是Objective-C
语言,如果你想使用Swift
,也是完全可以的,逻辑完全一样,只是代码语法不同而已。
如果你希望在你的平台特定代码可以用在多个Flutter
程序中,那么将这部分抽取出来做成一个packages
会十分有效,关于packages
部分我会在明天进行介绍,欢迎关注。
如果您希望与Flutter
生态系统中的其他开发人员分享你的特定平台代码,可以将需要共享的部分抽取出来做成一个插件进行发布,关于这部分我也会在明天进行介绍,欢迎关注。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。