用过flutter的都知道,flutter框架采用的是dart编程语言,并且有自己一整套完整的UI组件,所以基本上90%的业务需求都能满足,那么剩下的10%可能就避免不了要用native端来解决了,这种情况绝大多数发生在需要和硬件设备打交道的场景下.比如需要用到摄像机,麦克风,gps定位等等.那么两个基本上都不搭嘎的语言和框架怎么进行通讯呢?道理很简单,万变不离其宗.现代语言很多都是采用c或者c++作为编译器,再不行找到汇编这个祖宗总够你通讯用的吧.具体底层怎么实现不在讨论范围内.我们主要学习一下怎么进行通讯.总的来说先要凝聚两个语言的共识,也就是两个语言都认可的变量类型,官方的教程中已经给出了flutter与iOS和Android都支持的变量类型,这些类型可以直接被标准消息编码器(StandardMessageCodec)进行编码,与各自平台进行交换.
到这里,通讯标准,相当于通讯用的语言已经有了,还剩下一个问题,就是信道的问题.flutter已经为我们提供了一个创建通讯通道的类MethodChannel,通过这个类我们能够很方便的创建一个通道,这里的我们可以把这个通道先理解为一根电话线,那么要让这根电话线能起作用要有两个条件,1.两端必须接上电话 2.要有个电话号码.我们是在flutter端创建的这根电话线,默认他就已经接在flutter家的电话上了,所以我们创建的时候只要再给个电话号码就行了,注意这个号码必须要唯一
1
| static const channel = const MethodChannel('name.wgq.yequanzhen');
|
现在号码有了,电话线的一端也已经接好了,我们只要到我们需要通讯的平台的家里,把另一端接上去就行了,这里以iOS为例.用xcode打开flutter帮我们创建的对应的项目,修改AppDelegate.swift文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let controller : FlutterViewController = window?.rootViewController as! FlutterViewController; let channel = FlutterMethodChannel.init(name: "name.wgq.yequanzhen", binaryMessenger: controller); doing something.... return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
|
(注意:这里用电话线比喻好像有点不大恰当,因为flutter端创建了通道-电话线后默认接好了,另外一端可以接iOS也可以接Android,这个和电话线有点不大一致,先不管了.不管另外一头接到哪,只要能响应呼叫就行了)
两边都准备好了,我们来试一下从flutter调用iOS端的方法,就用官方的例子吧,获取电池剩余电量,我们先要在iOS端实现一个获取电池电量的方法
1 2 3 4 5 6 7 8 9
| func getBatteryLevel()->Int { let device = UIDevice.current; device.isBatteryMonitoringEnabled = true; if (device.batteryState == .unknown ){ return -1; } else { return Int((device.batteryLevel*100)); } }
|
要注意,我们现在只是建立了一套通信机制,并不是打通底层代码实现互通,所以我们不能直接调用iOS端的函数,只能通过通讯机制告诉native端我们需要知道电池电量,具体native端怎么获取电量的想管也管不着.简单的说就是你发一个命令给native端,具体native那边怎么执行的命令你就没办法了.命令可以由flutter和native端自由约定,只要能看懂就行了.这里约定命令为”iNeedBatteryLevel”.为了让演示更有实用价值一些,我们调用的时候增加了参数传递.这些参数没啥意义,完全为了演示.假如我们要传入name和age这两个参数,查了一下flutter提供的和ios端通讯支持的数据类型中有字典Map,很好,我们只要把这两个参数封装在Map里传过去就行了.建议以后传参数都用Map.简单易懂.调用了之后如果native端会有回传的值的话,我们要定义一个变量来接收它,这里要采用异步的方式来接收相信大家都能理解,不多说了,看代码
1 2 3 4
| var param = {"name":"yequanzhen","age":18}; final String batteryLevel = await platform.invokeMethod('iNeedBatteryLevel', param);
|
(如果没有需要传的参数,param那放null就行了)
好了,消息已经放出去了,我们再回到iOS端去响应这个消息,也就是执行这个命令.只要给通道对象设置一个函数来响应命令就行了,这个函数有两个参数,一个是FlutterMethodCall对象,另一个FlutterResult回调方法,看这两个名字就很清楚他们的作用了,FlutterMethodCall里包含了呼叫所包含的所有信息,包括消息名称和参数,我们处理了之后通过FlutterMethodCall回调将结果返回给flutter就行了,我们就直接把这两个参数取名为call和result吧.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| channel.setMethodCallHandler { (call, result) in let callName = call.method; if (callName == "iNeedBatteryLevel") { let level = getBatteryLevel(); result(String(level)); } }
|
最后贴上完整代码:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: '获取电量', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: '获取电量'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { String batteryLevel = "unknow"; void _getBatteryLevelFromNative() async { const channel = const MethodChannel('name.wgq.yequanzhen'); var param = {"name":"yequanzhen","age":18}; batteryLevel = await channel.invokeMethod('iNeedBatteryLevel', param); setState(() { }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( '电池电量还剩:', ), Text( '$batteryLevel', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _getBatteryLevelFromNative, tooltip: 'Increment', child: Icon(Icons.battery_unknown), ), ); } }
|
iOS端完整代码:
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
| import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let controller : FlutterViewController = window?.rootViewController as! FlutterViewController; let channel = FlutterMethodChannel.init(name: "name.wgq.yequanzhen", binaryMessenger: controller); channel.setMethodCallHandler { (call, result) in let callName = call.method; if (callName == "iNeedBatteryLevel") { let level = self.getBatteryLevel(); result(String(level)); } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func getBatteryLevel()->Int { let device = UIDevice.current; device.isBatteryMonitoringEnabled = true; if (device.batteryState == .unknown ){ return -1; } else { return Int((device.batteryLevel*100)); } } }
|
之后我还会再说一下从iOS端调用flutter端的方法
原创文章,转载请注明出处,谢谢!