用过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;
// 接上号码为"name.wgq.yequanzhen"的电话线
let channel = FlutterMethodChannel.init(name: "name.wgq.yequanzhen", binaryMessenger: controller);
doing something....
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

(注意:这里用电话线比喻好像有点不大恰当,因为flutter端创建了通道-电话线后默认接好了,另外一端可以接iOS也可以接Android,这个和电话线有点不大一致,先不管了.不管另外一头接到哪,只要能响应呼叫就行了)

image

  两边都准备好了,我们来试一下从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;
//获取参数
//let params = call.arguments;
//根据方法名称做出不同的处理,方法多的话建议用switch
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 {
// This widget is the root of your application.
@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),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

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;
//获取参数
//let params = call.arguments;
//根据方法名称做出不同的处理,方法多的话建议用switch
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端的方法

原创文章,转载请注明出处,谢谢!