前言
在前面的文章中我们讲了许多Flutter中的组件和Flutter中的特定操作,但是单单使用Flutter里的组件和方法是不够的。
就像以前我们讲到文件存储、数据库操作单单靠使用Flutter我们是不能完成的,因为这些数据最终需要存储在特定的终端平台上,我们需要通过特点的代码来实现与特点的平台交互,所以我们引入了第三方库来完成这些操作。
当然,这些第三方库帮我们实现了与不同平台交互的代码,所以我们不需要自己再去自己去编写这些与特定平台交互的代码。
当时我们你不可能一直使用人家的第三方库啊,一些特定的功能是没人能帮你的,所以我们还是很有必要来学习下如何跟特定的平台交互的
原谅我不会Object C ,不会Ios开发,这里仅仅以Android为例来做今天的例子
平台通道
lutter使用了一个灵活的系统,允许您调用特定平台的API,无论在Android上的Java或Kotlin代码中,还是iOS上的ObjectiveC或Swift代码中均可用。
Flutter平台特定的API支持不依赖于代码生成,而是依赖于灵活的消息传递的方式:
应用的Flutter部分通过平台通道(platform channel)将消息发送到其应用程序的所在的宿主(iOS或Android)。
宿主监听的平台通道,并接收该消息。然后它会调用特定于该平台的API(使用原生编程语言) - 并将响应发送回客户端,即应用程序的Flutter部分。
用平台通道在客户端(Flutter UI)和宿主(平台)之间传递消息,如下图所示:
在客户端,MethodChannel 可以发送与方法调用相对应的消息。 在宿主平台上,MethodChannel 在Android((API) 和 FlutterMethodChannel iOS (API) 可以接收方法调用并返回结果。这些类允许您用很少的“脚手架”代码开发平台插件。
接下来我们来个例子看看
调用Android平台Toast
在自前面我们可以很清楚的看到在Android平台我们需要借助于MethodChannel来与Android平台代码交互。我们通过代码来讲解下如何使用
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatelessWidget {
static const platform = const MethodChannel("com.flyou.test/android");
showToast(String msg) async {
try {
await platform.invokeMethod("showToast",{"msg":msg});
} on PlatformException catch (e) {
print(e.toString());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("platformChannels"),),body: Center(
child: RaisedButton(
child: Text("点我提示"),
onPressed: () {
showToast("我是android系统的toast");
},
),
),);
}
}
首先我们需要在我们的Dart代码中定义我们的想要调用的通道
static const platform = const MethodChannel("com.flyou.test/android");
当然这个频道的名字是可以自己定义的的,但是必须要要和一会在Android MainActivity中使用的一致。
然后,我们同个通道来调用在Android平台定义的方法
showToast(String msg) async {
try {
await platform.invokeMethod("showToast",{"msg":msg});
} on PlatformException catch (e) {
print(e.toString());
}
}
我们使用
platform.invokeMethod(“showToast”,{“msg”:msg})
来调用我们在Android平台定义的“showToast”方法
接下来,我们来看下我们在Android Activity做了哪些事情
package com.yourcompany.test1;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.flyou.test/android";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
(call, result) -> {
System.out.println(call.method);
if (call.method.equals("showToast")) {
if (call.hasArgument("msg") && !TextUtils.isEmpty(call.argument("msg").toString())) {
Toast.makeText(MainActivity.this, call.argument("msg").toString(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "toast text must not null", Toast.LENGTH_SHORT).show();
}
}
});
}
}
首先我们也定义了一个跟dart文件中一样的”com.flyou.test/android”
然后我们创建了一个MethodChannel对象并对回调多了处理,当回调中参数的method方法和我们在dart中定义的一样时,我们就调用Android平台的Toast方法弹出吐司,如果msg为空则弹出toast text must not null的吐司。
当我们点击flutter程序中的按钮时,就会触发调用Android系统的Toast回调。
看下效果:
从Android平台获取数据
和上面的类似,我们可以调用系统的方法,我们同样刻印调用我们自己写的方法并且返回调用方法的值,那么我们还是举个例子看下吧。
我们通过flutter调用Android平台的方法获取当前格式化好的时间。
同样的我们还是用用和刚才一样的通道,只不过这一次我们需要更改我们调用的方法即可。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatelessWidget {
static const platform = const MethodChannel("com.flyou.test/android");
Future<String> getAndroidTime() async {
var str;
try {
str = await platform.invokeMethod("getAndroidTime");
} on PlatformException catch (e) {
print(e.toString());
}
return str;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("platformChannels"),
),
body: new Center(
child: new Builder(builder: (BuildContext context) {
return new RaisedButton(
onPressed: () {
getAndroidTime().then((str){
Scaffold.of(context).showSnackBar(new SnackBar(content:Text(str!=null?str:"获取失败")));
});
},
child: Text("点我获取Android平台数据"),
);
}),
),
);
}
}
我们使用一个泛型为String的Future对象来接收Androd平台传来的字符串信息。然后调用then来显示字符串
在MainActivity中我们定义一个getCurrentTime的方法,当触发方法名为getAndroidTime时调用改方法并返回给Flutter调用处
下面就是效果了:
获取系统回调与监听
在前面的Flutter与平台的交互图上我们可以看到,使用MethodChannel可以调用原生平台的方法,在上面的例子中我们呢也给大家演示了如何使用,但是我们如何获取原生平台的监听与回调呢?类似于Android中的广播我们如何在Flutter接收到呢?
下面我们就需要来看下EventChannel了,借助于EventChannel(事件通道)我们可以很轻易的接收平台事件监听的回调。
那么我们还是举个例子,以Android平台网络变化为例,每当网络变化时就会触发Android本地的广播,然后通过EventChannel通知给Flutter组件中,这时候只要我们在Flutter注册相应的EventChannel就可以实现对网络变化事件的监听。
下面我们首先看下ManActivity方法中是怎么做的
package com.yourcompany.test1;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.flyou.test/android";
private static final String NET_CHANGE_CHANNEL = "com.flyou.test/netChanged";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
System.out.println(call.method);
if (call.method.equals("netConnection")) {
boolean networkConnected = NetUtil.isNetworkConnected(getApplicationContext());
result.success(networkConnected);
}else if (call.method.equals("showToast")) {
if (call.hasArgument("msg") && !TextUtils.isEmpty(call.argument("msg").toString())) {
Toast.makeText(MainActivity.this, call.argument("msg").toString(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "toast text must not null", Toast.LENGTH_SHORT).show();
}
}
}
});
new EventChannel(getFlutterView(), NET_CHANGE_CHANNEL).setStreamHandler(
new EventChannel.StreamHandler() {
private BroadcastReceiver netStateChangeReceiver;
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
netStateChangeReceiver = createNetStateChangeReceiver(events);
registerReceiver(netStateChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
@Override
public void onCancel(Object arguments) {
unregisterReceiver(netStateChangeReceiver);
netStateChangeReceiver = null;
}
}
);
}
private BroadcastReceiver createNetStateChangeReceiver(final EventChannel.EventSink events) {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
boolean networkConnected = NetUtil.isNetworkConnected(context);
events.success(networkConnected?"网络可用":"网络不可用");
}
};
}
}
在MainActivity中我们做了三件事件。
第一件事,跟上面的一样,让用户可以通过自己去触发操作判断当前网络是否可用,当然还是通过MethodChannel来实现的,然后把网络连接的状态通过我们刚才实现的Toast来弹出来。
当然这件事我们可以不做,只做监听也是可以的
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
System.out.println(call.method);
if (call.method.equals("netConnection")) {
boolean networkConnected = NetUtil.isNetworkConnected(getApplicationContext());
result.success(networkConnected);
}
}
});
第二件事,我们构造了一个动态广播接受者,来处理网络变化的广播事件,当接收到广播时判断网络状态并返回给Flutter中
private BroadcastReceiver createNetStateChangeReceiver(final EventChannel.EventSink events) {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
boolean networkConnected = NetUtil.isNetworkConnected(context);
events.success(networkConnected?"网络可用":"网络不可用");
}
};
}
}
第三件事,我们新建了EventChannel对象来对广播进行注册于解注册操作,负责广播的管理,并且声明注册广播的过滤器ConnectivityManager.CONNECTIVITY_ACTION,意思就是只接受网络状态变化的广播。
同样的我们定义了一个NET_CHANGE_CHANNEL通道。
new EventChannel(getFlutterView(), NET_CHANGE_CHANNEL).setStreamHandler(
new EventChannel.StreamHandler() {
private BroadcastReceiver netStateChangeReceiver;
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
netStateChangeReceiver = createNetStateChangeReceiver(events);
registerReceiver(netStateChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
@Override
public void onCancel(Object arguments) {
unregisterReceiver(netStateChangeReceiver);
netStateChangeReceiver = null;
}
}
);
}
在Flutter中我们需要注册对广播事件的监听并处理传来的事件即可。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
static const platform = const MethodChannel("com.flyou.test/android");
static const EventChannel eventChannel =
const EventChannel('com.flyou.test/netChanged');
var netChangeStr = "点我获取当前网络状态";
@override
void initState() {
super.initState();
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
void _onEvent(Object event) {
setState(() {
netChangeStr = event;
});
}
void _onError(Object error) {
setState(() {
netChangeStr = "网络状态获取失败";
});
}
Future<bool> isNetConnection() async {
bool isConnection;
try {
isConnection = await platform.invokeMethod("netConnection");
} on PlatformException catch (e) {
print(e.toString());
}
return isConnection;
}
showToast(String msg) async {
try {
await platform.invokeMethod("showToast", {"msg": msg});
} on PlatformException catch (e) {
print(e.toString());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("platformChannels"),
),
body: Center(
child: RaisedButton(
child: Text(netChangeStr),
onPressed: () {
isNetConnection().then((bool) {
showToast(bool ? "网络连接成功" : "网络连接失败");
});
},
),
),
);
}
}
在Flutter中我们使用了StatefulWidget在构建组件,使得获取到系统网络变化时可以及时更新界面UI状态。
具体的流程如下:
首先我们声明了跟MainActivity中一样的方法通道和事件通道来调用系统的方法接收系统的广播回调。
其次,我们完成对方法的注册于事件广播的注册
可以看到
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError)
回调事件我们单独写成方法来处理。
最后,当系统广播触发时,我们通过事件通过或者方法通道来将事件并更新界面上按钮的状态
我们还是来看下效果:
小结
- 我们可以通过MethodChannel来调用系统平台的方法
- 我们可以使用EventChannel来处理系统平台的监听或回调
- 不同平台(Ios、Android)需要单独的代码去实现相应的功能