Flutter Plugin调用Native APIs
作者:闲鱼技术-储睿
关键词:Flutter, Flutter Plugin, Platform Channel, Method Channel, Flutter Package, Flutter插件
添加微信, 免费测试软件
复制微信号
Flutter是Google使用Dart语言开发的一套移动应用开发框架。它不同于其他开发框架:
(1)因为Flutter使用AOT预编译代码为机器码,所以它的运行效率更高。
(2)Flutter的UI控件并没有使用底层的原生控件,而是使用Skia渲染引擎绘制而成,因为不依赖底层控件,所以多端一致性非常好。
(3)Flutter的扩展性也非常强,开发者可以通过Plugin与Native进行通信。
闲鱼开发Flutter过程中,经常会需要各种Native的能力,如获取设备信息、使用基础网络库等,这时会使用Plugin来做桥接。本文将对Plugin进行详细的介绍,希望能给Flutter开发者一些帮助。
摘要:
本文首先对Flutter Plugin以及原理进行了介绍,然后对Plugin所依赖的Platform Channel进行了讲解,随后对“获取剩余电量Plugin”进行了分解,最后给大家分享一下之前踩过的坑。
1. Flutter Plugin
在介绍Plugin前,我们先简单了解一下Flutter:
Flutter框架包括:Framework和Engine,他们运行在各自的Platform上。
Framework是Dart语言开发的,包括Material Design风格的Widgets和Cupertino(iOS-style)风格的Widgets,以及文本、图片、按钮等基础Widgets;还包括渲染、动画、绘制、手势等基础能力。
Engine是C++实现的,包括Skia(二维图形库);Dart VM(Dart Runtime);Text(文本渲染)等。
实际上,Flutter的上层能力都是Engine提供的。Flutter正是通过Engine将各个Platform的差异化抹平。而我们今天要讲的Plugin,正是通过Engine提供的Platform Channel实现的通信。
2. Platform Channel
2.1 Flutter App调用Native APIs:
通过上图,我们看到Flutter App是通过Plugin创建的Platform Channel调用的Native APIs。
2.2 Platform Channel 架构图:
Platform Channel:
- Flutter App (Client),通过MethodChannel类向Platform发送调用消息;
- Android Platform (Host),通过MethodChannel类接收调用消息;
- iOS Platform (Host),通过FlutterMethodChannel类接收调用消息。
PS:消息编解码器,是JSON格式的二进制序列化,所以调用方法的参数类型必须是可JSON序列化的。
PS:方法调用,也可以反向发送调用消息。
Android Platform
FlutterActivity,是Android的Plugin管理器,它记录了所有的Plugin,并将Plugin绑定到FlutterView。
iOS Platform
FlutterAppDelegate,是iOS的Plugin管理器,它记录了所有的Plugin,并将Plugin绑定到FlutterViewController(默认是rootViewController)。
3. 获取剩余电量Plugin:
3.1 创建Plugin
首先,我们创建一个Plugin(flutter_plugin_batterylevel)项目。Plugin也是项目,只是Project type不同。
(1)IntelliJ欢迎界面点击 Create New Project 或者 点击 File>New>Project…;
(2)在左侧菜单选择 Flutter, 然后点击 Next;
(3)输入 Project name 和 Project location,Project type 选择 “Plugin”;
(4)最后点击 Finish。
Project type:
(1)Application,Flutter应用;
(2)Plugin,暴漏Android和iOS的API给Flutter应用;
(3)Package,封装一个Dart组件,如“浏览大图Widget”。
PS:Plugin有Dart、Android、iOS,3部分代码组成。
3.2 Plugin Flutter部分
3.2.1 MethodChannel:Flutter App调用Native APIs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static const MethodChannel _methodChannel = const MethodChannel('samples.flutter.io/battery'); // Future<String> getBatteryLevel() async { String batteryLevel; try { final int result = await _methodChannel.invokeMethod('getBatteryLevel',{'paramName':'paramVale'}); batteryLevel = 'Battery level: $result%.'; } catch(e) { batteryLevel = 'Failed to get battery level.'; } return batteryLevel; } |
首先,我们实例_methodChannel(Channel名称必须唯一),然后调用invokeMethod()方法。invokeMethod()有2个参数:
(1)方法名,不能为空;
(2)调用方法的参数,该参数必须可JSON序列化,可以为空。
3.2.2 EventChannel:Native调用Flutter App
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static const EventChannel _eventChannel = const EventChannel('samples.flutter.io/charging'); void listenNativeEvent() { _eventChannel.receiveBroadcastStream().listen(_onEvent, onError:_onError); } void _onEvent(Object event) { print("Battery status: ${event=='charging' ? '' : 'dis'}charging."); } void _onError(Object error) { print('Battery status: unknown.'); } |
3.3 Plugin Android部分
3.3.1 Plugin 注册
1 2 3 4 5 6 7 8 9 10 11 |
import android.os.Bundle; import io.flutter.app.FlutterActivity; import io.flutter.plugins.GeneratedPluginRegistrant; public class MainActivity extends FlutterActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); } } |
在FlutterActivity的onCreate()方法中,注册Plugin。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public static void registerWith(Registrar registrar) { private static final String METHOD_CHANNEL = "samples.flutter.io/battery"; private static final String EVENT_CHANNEL = "samples.flutter.io/charging"; // 实例Plugin,并绑定到Channel上 FlutterPluginBatteryLevel plugin = new FlutterPluginBatteryLevel(); final MethodChannel methodChannel = new MethodChannel(registrar.messenger(), METHOD_CHANNEL); methodChannel.setMethodCallHandler(plugin); final EventChannel eventChannel = new EventChannel(registrar.messenger(), EVENT_CHANNEL); eventChannel.setStreamHandler(plugin); } |
(1)Channel名称:必须与Flutter App的Channel名称一致;
(2)MethodChannel和EventChannel初始化的时候都需要传递Registrar,即FlutterActivity;
(3)设置MethodChannel的Handler,即MethodCallHandler;
(4)设置EventChannel的Handler,即EventChannel.StreamHandler;
3.3.2 MethodCallHandler & EventChannel.StreamHandler
MethodCallHandler实现MethodChannel的Flutter App调用Native APIs;
EventChannel.StreamHandler实现EventChannel的Native调用Flutter App。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public class FlutterPluginBatteryLevel implements MethodCallHandler,EventChannel.StreamHandler { @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals("getBatteryLevel")) { Random random = new Random(); result.success(random.nextInt(100)); } else { result.notImplemented(); } } @Override public void onListen(Object obj, EventChannel.EventSink eventSink) { BroadcastReceiver chargingStateChangeReceiver = createChargingStateChangeReceiver(events); } @Override public void onCancel(Object obj) { } private BroadcastReceiver createChargingStateChangeReceiver(final EventSink events) { return new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) { events.error("UNAVAILABLE", "Charging status unavailable", null); } else { boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; events.success(isCharging ? "charging" : "discharging"); } } }; } } |
MethodCallHandler:
(1)public void onMethodCall(MethodCall call, Result result);
EventChannel.StreamHandler:
(1)public void onListen(Object obj, EventChannel.EventSink eventSink);
(2)public void onCancel(Object obj);
3.4 Plugin iOS部分
3.4.1 Plugin 注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#define METHOD_CHANNEL "samples.flutter.io/battery"; #define EVENT_CHANNEL "samples.flutter.io/charging"; @implementation AppDelegate - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { [GeneratedPluginRegistrant registerWithRegistry:self]; FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController; FlutterMethodChannel* batteryChannel = [FlutterMethodChannel methodChannelWithName:METHOD_CHANNEL binaryMessenger:controller]; [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { if ([@"getBatteryLevel" isEqualToString:call.method]) { int batteryLevel = [self getBatteryLevel]; result(@(batteryLevel)); } else { result(FlutterMethodNotImplemented); } }]; FlutterEventChannel* chargingChannel = [FlutterEventChannel eventChannelWithName:EVENT_CHANNEL binaryMessenger:controller]; [chargingChannel setStreamHandler:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end |
iOS的Plugin注册流程跟Android一致。只是需要注册到AppDelegate(FlutterAppDelegate)。
FlutterMethodChannel和FlutterEventChannel被绑定到FlutterViewController。
3.4.2 FlutterStreamHandler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@interface AppDelegate () <FlutterStreamHandler> @property (nonatomic, copy) FlutterEventSink eventSink; @end - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { self.eventSink = eventSink; // 监听电池状态 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onBatteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil]; return nil; } - (FlutterError*)onCancelWithArguments:(id)arguments { [[NSNotificationCenter defaultCenter] removeObserver:self]; self.eventSink = nil; return nil; } - (void)onBatteryStateDidChange:(NSNotification*)notification { if (self.eventSink == nil) return; UIDeviceBatteryState state = [[UIDevice currentDevice] batteryState]; switch (state) { case UIDeviceBatteryStateFull: case UIDeviceBatteryStateCharging: self.eventSink(@"charging"); break; case UIDeviceBatteryStateUnplugged: self.eventSink(@"discharging"); break; default: self.eventSink([FlutterError errorWithCode:@"UNAVAILABLE" message:@"Charging status unavailable" details:nil]); break; } } |
4. 加载Plugin
现在我们已经有了Plugin,但是如何把它加载到Flutter App项目中呢?
It’s Pub. Pub是Dart语言提供的Packages管理工具。
说到Package,它有2种类型:
(1) Dart Packages:只包含Dart代码,如“浏览大图Widget”。
(2) Plugin Packages:包含的Dart代码能够调用Android和iOS实现的Native APIs,如“获取剩余电量Plugin”。
4.1 将一个Package添加到Flutter App中
(1)通过编辑pubspec.yaml(在App根目录下)来管理依赖;
(2)运行flutter packages get,或者在IntelliJ里点击Packages Get;
(3)import package,重新运行App。
管理依赖有3种方式:Hosted packages、Git packages、Path packages。
4.2 Hosted packages(来自pub.dartlang.org)
如果你希望自己的Pulgin给更多的人使用,你可以把它发布到 pub.dartlang.org。
发布Hosted packages:
1 2 |
$flutter packages pub publish --dry-run $flutter packages pub publish |
加载Hosted packages:
编辑pubspec.yaml:
1 2 |
dependencies: url_launcher: ^3.0.0 |
4.3 Git packages(远端)
如果你的代码不经常改动,或者不希望别人修改这部分代码,你可以用Git来管理你的代码。
我们先创建?一个Plugin(flutter_remote_package),并将它传到Git上,然后打个tag。
1 2 3 4 5 6 7 8 |
// cd 到 flutter_remote_package flutter_remote_package $:git init flutter_remote_package $:git remote add origin git@gitlab.alibaba-inc.com:churui/flutter_remote_package.git flutter_remote_package $:git add . flutter_remote_package $:git commit flutter_remote_package $:git commit -m"init" flutter_remote_package $:git push -u origin master flutter_remote_package $:git tag 0.0.1 |
加载Git packages:
编辑pubspec.yaml:
1 2 3 4 5 |
dependencies: flutter_remote_package: ? git: ? ?url: git@gitlab.alibaba-inc.com:churui/flutter_remote_package.git ? ?ref: 0.0.1 |
PS:ref可以指定某个commit、branch、或者tag。
4.4 Path packages(本地)
PS:如果你的代码没有特殊的场景需要, 可以直接把Package放到本地,这样开发和调试都很方便。
我们在Flutter App项目根目录下(flutter_app),创建文件夹(plugins),然后把插件(flutter_plugin_batterylevel)移动到plugins下。
加载Path packages:
编辑pubspec.yaml:
1 2 3 |
dependencies: flutter_plugin_batterylevel: path: plugins/flutter_plugin_batterylevel |
5. 踩过的坑
5.1 用XCode编辑Plugin
我们已经在pubspec.yaml里添加了依赖,但是打开iOS工程,却看不到Plugin?
这时需要执行pod install (或pod update)。
5.2 iOS编译没问题,但是运行时找不到Plugin
1 2 3 4 5 6 7 8 9 |
@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Plugin注册方法 [GeneratedPluginRegistrant registerWithRegistry:self]; // 显示Window self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; |
[self.window setRootViewController:[[FlutterViewController alloc]
initWithNibName:nil bundle:nil]]];
[self.window setBackgroundColor:[UIColor whiteColor]
];
[self.window makeKeyAndVisible]
; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end
[GeneratedPluginRegistrant registerWithRegistry:self]默认注册到self.window.rootViewController的。
所以需要先初始化rootViewController,再注册Plugin。
5.3 Native调用Flutter失败
Flutter App启动后,Native调用Flutter失败?
这是因为Plugin Channel的初始化大概要1.5秒,而且这是一个异步过程。虽然Flutter页面显示出来了,但是Plugin Channel还没初始化完,所以这时Native调用Flutter是没反应的。
5.4 iOS Plugin注册到指定的FlutterViewController
闲鱼首页是Native页面,所以Window的rootViewController不是FlutterViewController,直接注册Plugin会注册失败。我们需要将Plugin注册到指定的FlutterViewController。
FlutterAppDelegate.h
1 2 |
- (NSObject<FlutterBinaryMessenger>*)binaryMessenger; - (NSObject<FlutterTextureRegistry>*)textures; |
我们需要在AppDelegate重写上面两个方法,方法内返回需要指定的FlutterViewController。
延展讨论
Flutter作为应用层的UI框架,底层能力还是依赖Native的,所以Flutter App调用Native APIs的应用场景还是挺多的。
在Plugin方法调用过程中,可能会遇到传递复杂参数的情况(有时需要传递对象),但是Plugin的参数是JSON序列化后的二进制数据,所以传参必须是可JSON序列化的。我觉得,应该有一层对象映射层,来支持传递对象。
说到Plugin传参,Plugin有个很牛逼的能力,就是传递textures(纹理)。闲鱼的Flutter视频播放,实际上是用的Native播放器,然后将textures(纹理)传递给Flutter App。
<