Flutter和Native之间通讯,是日常开发中经常需要用到的功能。
本文介绍了Flutter和Native如何进行通讯,以及在引入FlutterBoost的情况下,如何进行Flutter和Native的通讯。
如果想下载Demo源码,可以直接拉到最后面。
Flutter和Native通信基础
Flutter和Native之间通信的常见场景
- 初始化Flutter时Native向Dart传递数据
- Native发送数据给Dart
- Dart发送数据给Native
- Dart发送数据给Native,然后Native回传数据给Dart
Flutter和Native通信的机制
Flutter和Native通信使用Channel(平台通道)在客户端(UI)和主机(平台)之间传递
Channel支持的数据类型
Channel
Flutter定义了三种不同类型的Channel
- BasicMessageChannel:用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以 Native 端主动调用,也可以Flutter主动调用。
- MethodChannel:Flutter 与 Native 端相互调用,调用后可以返回结果,可以 Native 端主动调用,也可以Flutter主动调用,属于双向通信。此方式为最常用的方式, Native 端调用需要在主线程中执行。
- EventChannel:用于数据流(event streams)的通信, Native 端主动发送数据给 Flutter,通常用于状态的监听,比如网络变化、传感器数据等。
Flutter和Native通信示例
首先,我们要新建一个Android原生项目,和一个Flutter module
,并使其在同一个文件夹下
并将其配置,使其能够进行混合开发,具体详见 Flutter与Android Native进行混合开发
在Flutter Module中,新建BatteryChannel.dart
class BatteryChannel(flutterEngine: BinaryMessenger, context: Context) :
MethodChannel.MethodCallHandler {
private val batteryChannelName = "com.liubike/battery"
private var channel: MethodChannel
private var mContext: Context
companion object {
private const val TAG = "BatteryChannel"
}
init {
Log.d(TAG, "init")
channel = MethodChannel(flutterEngine, batteryChannelName)
channel.setMethodCallHandler(this)
mContext = context;
Log.d(TAG, "init2")
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.d(TAG, "onMethodCall: " + call.method)
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val batteryManager =
mContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(mContext).registerReceiver(
null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) *
100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
在Flutter Module中,进行调用
import 'package:flutter/material.dart';
import 'BatteryChannel.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String _batteryLevel = 'Unknown battery level.';
// 异步获取到电量,然后重新渲染页面
void getBatteryLevel() async{
_batteryLevel = await BatteryChannel.getBatteryLevel();
setState(() {
});
}
@override
void initState() {
super.initState();
BatteryChannel.initChannels(); // 初始化通道
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),Text(
'$_batteryLevel',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: getBatteryLevel,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
在原生项目中,新建BatteryChannel.kt
package com.liubike.fluttercommunicatenative
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import android.util.Log
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class BatteryChannel(flutterEngine: BinaryMessenger, context: Context) :
MethodChannel.MethodCallHandler {
private val batteryChannelName = "com.liubike/battery"
private var channel: MethodChannel
private var mContext: Context
companion object {
private const val TAG = "BatteryChannel"
}
init {
channel = MethodChannel(flutterEngine, batteryChannelName)
channel.setMethodCallHandler(this)
mContext = context;
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.d(TAG, "onMethodCall: " + call.method)
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val batteryManager =
mContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(mContext).registerReceiver(
null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) *
100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
在原生项目中,创建AgentActivity.kt
需要在configureFlutterEngine
方法中,实例化BatteryChannel
package com.liubike.fluttercommunicatenative
import android.os.Bundle
import android.widget.Toast
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class AgentActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Toast.makeText(this, "agrent", Toast.LENGTH_SHORT).show()
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
BatteryChannel(flutterEngine.dartExecutor.binaryMessenger,this)
}
}
不要忘了在AndroidManifest.xml
中注册
<activity
android:name=".AgentActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
最后,在MainActivity.kt中跳转到Flutter页面
跳转到Flutter页面后,再去获取电量
package com.liubike.fluttercommunicatenative
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var btn01 = findViewById<Button>(R.id.btn_01)
btn01.setOnClickListener {
/*
//跳转到Flutter默认页面
startActivity(
FlutterActivity.createDefaultIntent(this)
)*/
/*
//跳转到Flutter指定页面
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/home")
.build(this)
)*/
startActivity(Intent(this,AgentActivity::class.java))
}
}
}
效果如下
引入FlutterBoost,进行Flutter和Native的通讯
其他地方,和Flutter官方是一样的,主要就是Channel实例化改到了FlutterBoost初始化的地方。
关于FlutterBoost的接入,可以看我的另一篇博客Flutter接入FlutterBoost进行跳转
在Flutter Module中,新建BatteryChannel.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class BatteryChannel {
static const _batteryChannelName = "com.liubike/battery"; // 1.方法通道名称
static MethodChannel _batteryChannel;
static void initChannels(){
_batteryChannel = MethodChannel(_batteryChannelName); // 2. 实例化一个方法通道
}
// 3. 异步任务,通过平台通道与特定平台进行通信,获取电量,这里的宿主平台是 Android
static getBatteryLevel() async {
initChannels();
String batteryLevel;
try {
final int result = await _batteryChannel.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
return batteryLevel;
}
}
在Flutter Module中,进行调用
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_module/BatteryChannel.dart';
class SecondPage extends StatelessWidget {
const SecondPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _batteryLevel = 'Unknown battery level.';
// 异步获取到电量,然后重新渲染页面
getBatteryLevel() async{
_batteryLevel = await BatteryChannel.getBatteryLevel();
setState(() {
});
}
@override
void initState() {
super.initState();
BatteryChannel.initChannels(); // 初始化通道
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MaterialButton(
color: Colors.green,
onPressed: () {
getBatteryLevel(); // 2.调用通道方法
},
child: const Text("获取电量")),
new Text(_batteryLevel),
],
),
),
);
}
}
在Android端新建BatteryChannel.kt
package com.liubike.boosttestandroid
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import android.util.Log
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class BatteryChannel(flutterEngine: BinaryMessenger, context: Context) :
MethodChannel.MethodCallHandler {
private val batteryChannelName = "com.liubike/battery"
private var channel: MethodChannel
private var mContext: Context
companion object {
private const val TAG = "BatteryChannel"
}
init {
channel = MethodChannel(flutterEngine, batteryChannelName)
channel.setMethodCallHandler(this)
mContext = context;
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.d(TAG, "onMethodCall: " + call.method)
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val batteryManager =
mContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(mContext).registerReceiver(
null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) *
100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
在Application中初始化FlutterBoost,并实例化Channel
FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {
@Override
public void pushNativeRoute(FlutterBoostRouteOptions options) {
String pageName = options.pageName();
Log.i("NativeApp", "pageName:" + pageName);
//这里根据options.pageName来判断你想跳转哪个页面
Intent intent = null;
if ("native_main".equals(pageName)){
intent = new Intent(FlutterBoost.instance().currentActivity(), MainActivity.class);
}else if("native_second".equals(pageName)){
intent = new Intent(FlutterBoost.instance().currentActivity(), SecondActivity.class);
}
FlutterBoost.instance().currentActivity().startActivityForResult(intent, options.requestCode());
}
@Override
public void pushFlutterRoute(FlutterBoostRouteOptions options) {
Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
.destroyEngineWithActivity(false)
.uniqueId(options.uniqueId())
.url(options.pageName())
.urlParams(options.arguments())
.build(FlutterBoost.instance().currentActivity());
FlutterBoost.instance().currentActivity().startActivity(intent);
}
}, engine -> {
//Channel在这里进行实例化
new BatteryChannel(engine.getDartExecutor(), this);
});
最后,在MainActivity.kt中跳转到Flutter页面
跳转到Flutter页面后,再去获取电量
Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
.destroyEngineWithActivity(false)
.url("flutterPage") //在main.dart的routerMap中有注册 flutterPage
.urlParams(params)
.build(this);
startActivity(intent);
效果如下
相关源码下载
Flutter与Android Native进行混合开发,相互跳转,进行通信_示例Demo