Flutter混合开发概述
Flutter可以和原生混合开发,其中有几种主要的形式
- 作为单独页面嵌入原生App中,Flutter页面和原生页面可以互相打开.
- 作为页面的一部分嵌入,Flutter页面可以嵌入到原生页面的一部分,同样原生页面也可以嵌入Flutter页面的一部分
混合开发之Native工程接入Flutter步骤
- 创建Flutter module
- 添加Flutter module依赖
- 在java或ObjectC中调用Flutter module
- 编写Dart代码
- 运行项目
- 热重启/重新加载
- 调试Dart代码
- 发布应用
1 创建Flutter Module
1.1 手动创建module方式
创建 flutter_hybird 文件夹并切换到该目录下,运行命令
flutter create -t module flutter_module
复制代码
使用AndroidStudio 打开该module并运行
需要注意的是如果想将Flutter的module集成到现有原生工程中,必须将原生工程和Flutter的module放在同一目录下
将Flutter的module添加到现有的Android项目中
需要注意的是Flutter目前最低支持api16+,所以在集成之前需要将项目的最低支持版本改为16或者之上版本
- 在Android项目的根目录的setting.gradle文件中添加
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'flutter_module/.android/include_flutter.groovy' // new
))
复制代码
- 在Android项目的app目录下build.gradle添加
dependencies {
implementation project(':flutter')
复制代码
1.2 AndroidStudio 自动创建
Android Studio会直接帮我们做完上述工作,开箱即用即可.
Native调用flutter
Android中以Activity形式调用Flutter
1. 在AndroidManifest.xml文件中配置FlutterActivity
#application节点下
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:theme="@style/Theme.AndroidAppProject"
android:windowSoftInputMode="adjustResize" />
复制代码
增加一个按钮点击事件,打开Flutter页面
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.bt_start).setOnClickListener {
startActivity(FlutterActivity.createDefaultIntent(this))
}
}
}
复制代码
效果
打开Flutter指定页面
Flutter目前可以打开指定页面,但是无法携带参数,因此我们可以把main.dart作为中转跳转并携带参数
- 首先定义json格式
{"route":"/page1","param":{"id":5}}
复制代码
- 其中route表示跳转的路径
- param表示携带的参数
- 给flutter设置跳转路径
findViewById<Button>(R.id.bt_start).setOnClickListener {
startActivity(FlutterActivity.withNewEngine().initialRoute("{\"route\":\"/page1\",\"param\":{\"id\":5}}").build(this))
}
复制代码
- 在flutter的module中获取设置的路径值
import 'dart:ui';
String defaultRouteName=window.defaultRouteName;
复制代码
- defaultRouteName 就是我们传过来的值 定义对象解析相关属性
class NativeToFlutter{
String route;
Map<String,dynamic> param;
NativeToFlutter({this.route, this.param});
factory NativeToFlutter.fromJson(Map<String,dynamic> map){
return NativeToFlutter(route:map['route'],param:map['param']);
}
}
复制代码
开始解析相关路径的参数
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
String defaultRouteName=window.defaultRouteName;
print("defaultRouteName=$defaultRouteName");
Widget widget;
if("/"==defaultRouteName){//initialRoute 配置的路径
widget=MyHomePage();
}else{
//使用json解析json字符串
var result=json.decode(defaultRouteName);
print("result=${result.runtimeType}");
NativeToFlutter nativeToFlutter= NativeToFlutter.fromJson(result);
//判断路径进行跳转
if(nativeToFlutter.route=="/page1"){
//解析出对应的参数
var id=nativeToFlutter.param['id'];
widget=Page1();
}else{
widget=MyHomePage();
}
}
return MaterialApp(
initialRoute: "/",
routes: {'/page1': (context) => Page1()},
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: widget,
);
}
}
复制代码
MaterialApp中
- initialRoute 初始化页面的路径
- routes 配置flutter页面路径
当然这只是简单的交互,目前Native打开Flutter页面比较缓慢,因为每次启动Flutter页面时,都会创建FlutterEngine
关于FlutterEngine的相关内容建议直接阅读flutter中文网站
Android中以Fragment形式调用Flutter
新建一个Activity 并在其布局文件中添加个framelayout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/design_default_color_secondary"
android:gravity="center"
android:text="我是头部title"
android:textSize="60sp"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/fl_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
复制代码
Activity
class FirstActivity : AppCompatActivity() {
companion object {
private const val TAG = "flutter_fragment"
}
private var fragment: FlutterFragment?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first_actvity)
fragment = supportFragmentManager.findFragmentByTag(TAG) as FlutterFragment?
if (fragment == null) {
fragment = FlutterFragment.withNewEngine().build()
supportFragmentManager.beginTransaction().add(R.id.fl_container, fragment!!, TAG).commit()
}
}
}
复制代码
和Activity一样,Fragment也可以指定页面路径,但是无法携带参数
FlutterFragment.withNewEngine().initialRoute("/page1").build()
复制代码
同样也可以通过Activity的那种方式,指定页面并传递参数
FlutterFragment.withNewEngine().initialRoute("{\"route\":\"/page1\",\"param\":{\"id\":5}}").build()
复制代码
这样既可达到同样的效果
和Activity一样,让Flutter页面以fragment形式也需要FlutterEngine,关于预热FlutterEngine相关的知识,可以查看Flutter中文官网
Flutter部分代码热更新
-
关闭app
-
Flutter Attach
-
加载出Flutter部分代码
-
更改Flutter部分代码并启用热加载
Flutter与Native通讯机制
在做Flutter开发的时候通常离不开Flutter和Native的通讯,比如Dart调用native相册获取图片,Native将本地信息(GPS,陀螺仪等)主动传给Dart. Dart和Native的通讯通常有这几种场景
- 初始化Flutter时Native给Flutter传递数据
- Native发送数据给Dart
- Dart给Native发送数据
- Dart发送数据给Native,然后Native回传数据给Dart
Flutter和Native之前的通讯是通过Channel完成的
Flutter定义了三种类型的Channel
- BasicMessageChannel: 用于传递字符串和半结构化的信息,持续通讯,收到消息后可以回复此次消息;如:Native遍历文件信息传递给Dart
- MethodChannel: 用于传递方法调用的一次性通讯,如调用native的拍照和相册,读写日历,选择联系人等
- EventChannel: 用于数据流通讯,持续通讯,收到消息后无法此次消息,通常用于Native向Dart发送数据,如:电量的变化,网络连接变化,陀螺仪,传感器等;
这三种类型的Channel都是全双工通讯,既通讯是双向的,
BasicMessageChannel 使用
首先看一下它的构造函数
BasicMessageChannel(name, codec, { BinaryMessenger? binaryMessenger })
- name:是channel的名称,dart和native建立通讯的标示
- codec:编码集,可以是如下几种
示例
class ChannelPage extends StatefulWidget {
@override
ChannelPageState createState() {
return ChannelPageState();
}
}
class ChannelPageState extends State<ChannelPage> {
String nativeSentString = '';
String nativeReturnString = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("测试flutter的channel通讯"),
),
body: Column(
children: [
Text('测试BasicMethodChannel'),
Text("Native主动回传消息:$nativeSentString"),
Text("Native主动返回消息:$nativeReturnString"),
RaisedButton(
onPressed: () {
sentMessage();
},
child: Text("点击给Native发送消息"),
)
],
),
);
}
static const String BASE_CHANNEL = 'BasicMessageChannel';
BasicMessageChannel _basicMessageChannel = BasicMessageChannel(BASE_CHANNEL, StringCodec());
_initChannel() {
//当native发送消息后在这里处理
_basicMessageChannel.setMessageHandler((message) {
setState(() {
nativeSentString = message;
});
print(message?.toString());
//这里收到native消息后主动给native回消息
return null;
});
}
@override
void initState() {
_initChannel();
super.initState();
}
sentMessage() async {
try {
//主动给native发送消息
String result = await _basicMessageChannel.send("hello native");
setState(() {
nativeReturnString=result;
});
print(result);
} catch (e) {
print(e?.toString());
}
}
}
复制代码
native(Android端配置) 新建类
class FlutterBasicMessageChannel(context: Context) : FlutterPlugin, BasicMessageChannel.MessageHandler<String> {
private val channelName:String="BasicMessageChannel"
private var context:Context?=null
companion object{
private var basicMessageChannel:BasicMessageChannel<String>?=null
fun sentContent(content:String){
basicMessageChannel?.send(content)
}
}
override fun onMessage(message: String?, reply: BasicMessageChannel.Reply<String>) {
Toast.makeText(context, "收到了哈哈哈哈$message",Toast.LENGTH_LONG).show()
reply.reply("给你一条消息哈哈哈哈")
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//注册消息通道
context= binding.applicationContext
basicMessageChannel= BasicMessageChannel<String>(binding.binaryMessenger,channelName,StringCodec.INSTANCE)
basicMessageChannel?.setMessageHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//销毁消息通道
basicMessageChannel?.setMessageHandler(null)
basicMessageChannel=null
}
}
复制代码
在MainActivity中配置自定义Channel
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
flutterEngine.plugins.add(FlutterBasicMessageChannel(this))
super.configureFlutterEngine(flutterEngine)
}
}
复制代码
效果:
MethodChannel使用
构造函数 MethodChannel(name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
- name:标识该channel,其实只传这一个参数即可
static const String methodChannelName = "MethodChannel";
MethodChannel _methodChannel = MethodChannel(methodChannelName);
@override
void initState() {
_initChannel();
super.initState();
}
_initChannel() {
//当native通过该channel调用flutter的代码时在这里处理并返回结果
_methodChannel.setMethodCallHandler((call){
if ("hiFlutter"==call.method) {
}else{
call.noSuchMethod(null);
}
return null;
});
}
//点击按钮触发该方法调用native的方法并传递参数
static const String methodName = "giveMeFive";
callMethodChannel() async {
Map<String, String> params = {};
params["name"] = "张三1";
params["age"] = "12";
try{
String result = await _methodChannel.invokeMethod(methodName, params);
setState(() {
nativeReturnString = result;
});
}catch(e){
print(e?.toString());
}
}
复制代码
native端(Android) 新建类FlutterMethodChannel
- 实现FlutterPlugin是为了注册该channel
- 实现MethodChannel.MethodCallHandler 是为了处理dart端传来的方法调用
class FlutterMethodChannel(context: Context) : FlutterPlugin, MethodChannel.MethodCallHandler {
private val channelName:String="BasicMessageChannel"
private var context:Context?=null
companion object{
private var methodChannel:MethodChannel?=null
fun sentContent(content:String){
}
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
context= binding.applicationContext
//初始化并设置methodhandler
methodChannel= MethodChannel(binding.binaryMessenger,"MethodChannel");
methodChannel?.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel=null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Toast.makeText(context, "收到了哈哈哈哈${call.method}",Toast.LENGTH_LONG).show()
if (call.method=="giveMeFive") {
var params: Map<*, *>? = call.arguments as? Map<*, *>;
var name= params?.get("name")
if ("张三"==name){
result.success("5555555555555555")
}else{
result.error("100","不好意思在下只要张三","")
}
}else{
result.notImplemented()
}
}
}
复制代码
- result.notImplemented() flutter端调用的方法native未实现
- result.success() native处理完成并返回结果
- result.error() native处理异常并通知flutter该异常
这里需要注意的是一定要通过result对native处理的各个情况进行反馈,要不然flutter端就会等待并没有结果,尤其是native端出现了异常也一定要反馈给flutter端,
MainActivity中加上该配置
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
flutterEngine.plugins.add(FlutterMethodChannel(this))
super.configureFlutterEngine(flutterEngine)
}
}
复制代码
EventChannel 使用
构造方法 EventChannel(name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger])
- name 和其他两个channel一样,
@override
void initState() {
enableStream();
super.initState();
}
static const String eventChannelName = "EventChannel";
EventChannel _eventChannel = EventChannel(eventChannelName);
StreamSubscription _streamSub;
enableStream() {
//监听native发送过来的信息
_streamSub = _eventChannel.receiveBroadcastStream().listen((event) {
print(event.toString());
}, onError: (e) {
print("onError");
}, onDone: () {
print("onDone");
});
}
@override
void dispose() {
//注销并释放资源
if (_streamSub != null) {
_streamSub.cancel();
_streamSub = null;
}
super.dispose();
}
复制代码
native(Android端)
定义 FlutterEventChannel类
class FlutterEventChannel : FlutterPlugin, EventChannel.StreamHandler {
private var context:Context?=null
companion object{
private var eventChannel:EventChannel?=null
private var chargingStateChangeReceiver: BroadcastReceiver? = null
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
context= binding.applicationContext
//初始化EventChannel并设置Handler
eventChannel= EventChannel(binding.binaryMessenger, "EventChannel");
eventChannel?.setStreamHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//重置EventChannel并设置Handler为null
eventChannel?.setStreamHandler(null)
eventChannel=null
}
//flutter端开始监听回调该方法
override fun onListen(arguments: Any?, events: EventSink?) {
//注册监听手机电量变化 chargingStateChangeReceiver=createChargingStateChangeReceiver(events)
context?.registerReceiver(chargingStateChangeReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
}
override fun onCancel(arguments: Any?) {
//释放监听
context?.unregisterReceiver(chargingStateChangeReceiver)
chargingStateChangeReceiver=null
}
private fun createChargingStateChangeReceiver(events: EventSink?): BroadcastReceiver {
return object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
events?.error("UNAVAILABLE", "Charging status unavailable", null)
} else {
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
events?.success(if (isCharging) "charging" else "discharging")
}
}
}
}
}
复制代码
可以看到日志
I/flutter (15313): charging
I/flutter (15313): charging
I/flutter (15313): charging
I/flutter (15313): charging
I/flutter (15313): charging
复制代码