Flutter 系列(八):Flutter 与 Android 的你来我往

前言

很高兴遇见你~

在本系列的上一篇文章中,我们介绍了 Flutter 中的路由:

1、基本路由

2、命名路由

3、返回上一级

4、替换路由

5、返回到根路由

以及集成 http 库进行 https 请求实战。

还没有看过上一篇文章的朋友,建议先去阅读 Flutter 系列(七):Flutter 路由和 HTTPS 请求实战。接下来我们对 Flutter 与 Android 原生的交互与通信进行介绍

我做 Android 原生开发时,通常会以组件化的方式去进行,根据业务划分不同的组件,每个组件都是一个独立的工程,可以进行独立的运行和调试,当需要发版时,我们会将每个组件打成 aar 包并上传到 Maven 私服仓库,然后整合到 App 壳工程中,最终进行打包上线。在这个开发过程中,组件之间是需要进行通信的,如果需要通信的组件都是 Android 原生开发的,那么可以选择一个路由框架进行通信,例如:Arouter。

但是我们有些业务组件是使用 Flutter 开发的,因此这里就涉及到 Flutter 与 Android 原生的通信,那么它们是如何进行通信的呢?且听我细细道来

一、Android 壳工程集成 Flutter 组件

1、打开 AndroidStudio ,创建一个 Android 工程 AndroidAndFlutterInteractive:

image-20220918215022577

2、接着在创建一个 Flutter 工程 fluttermodule:

image-20220918215214876

main.dart 文件初始代码如下:

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
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

// This widget is the root of your application.
@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;

void _incrementCounter() {
setState(() {
_counter++;
});
}

@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 clicked the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

3、在 Flutter 工程中执行 flutter build aar 命令或者直接使用 AndroidStudio 上的可视化操作:

202209221609588.png

执行完后会有如下提示:

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
Consuming the Module
1. Open <host>/app/build.gradle
2. Ensure you have the repositories configured, otherwise add them:

String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
maven {
url '/Users/zhouying/codeandnotes/Flutter/fluttermodule/build/host/outputs/repo'
}
maven {
url "$storageUrl/download.flutter.io"
}
}

3. Make the host app depend on the Flutter module:

dependencies {
debugImplementation 'com.example.fluttermodule:flutter_debug:1.0'
profileImplementation 'com.example.fluttermodule:flutter_profile:1.0'
releaseImplementation 'com.example.fluttermodule:flutter_release:1.0'
}


4. Add the `profile` build type:

android {
buildTypes {
profile {
initWith debug
}
}
}

大致意思就是在我们创建的 Android 工程中配置生成的 Flutter aar 的仓库地址,然后引用这个 aar,大家按照上述步骤配置即可

注意:上述演示生成的 Flutter aar 只是存在本地,实际开发中,我们会自己编写脚本生成 aar 并上传到 Maven 私服仓库

配置完成后,同步一下项目,如果没啥报错,我们就算是成功集成了 Flutter 组件

二、Android 调起 Flutter 页面(FlutterActivity)

接下来我们继续对 Android 工程进行配置,让 Flutter 页面显示出来

1、在 Android 工程的 AndroidManifest.xml 文件中添加 FlutterActivity

1
2
3
4
5
6
<!--注册FlutterActivity-->
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize" />

2、编写一个 button 跳转到 Flutter 页面

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
//1、activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:textAllCaps="false"
android:onClick="toFlutterActivity"
android:text="跳转 FlutterActivity"
tools:ignore="HardcodedText,UsingOnClickInXml" />

</LinearLayout>

//2、MainActivity
class MainActivity : AppCompatActivity() {

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

//跳转到 FlutterActivity
fun toFlutterActivity(view: View) {
val intent = FlutterActivity.createDefaultIntent(this)
startActivity(intent)
}
}

3、效果展示:

ezgif.com-gif-maker.gif

上述效果图虽然跳过去了,但是我们可以看到点击 button 时一个明显的停顿感,用户体验不好,接下来介绍一种预初始化 Flutter 的方式

2.1、Android 预初始化 Flutter 页面跳转

核心思想就是缓存 FlutterEngine,然后从缓存中取出 FlutterEngine 进行跳转

1、修改 MainActivity :

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
class MainActivity : AppCompatActivity() {

companion object{
//缓存 FlutterEngine 的 key
const val FLUTTER_ENGINE_ID = "default"
}
//FlutterEngine
private lateinit var flutterEngine: FlutterEngine

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//初始化 FlutterEngine
flutterEngine = initFlutterEngine(FLUTTER_ENGINE_ID)
}

//跳转到 FlutterActivity
fun toFlutterActivity(view: View) {
val intent = FlutterActivity.withCachedEngine(FLUTTER_ENGINE_ID).build(this)
startActivity(intent)
}

/**
* 初始化 FlutterEngine
* 一般在跳转前调用,从缓存中取出 FlutterEngine,这样可以加快我们页面的一个跳转
*/
private fun initFlutterEngine(engineId: String): FlutterEngine {
//创建 FlutterEngine
val flutterEngine = FlutterEngine(this)
//指定要跳转的 Flutter 页面
flutterEngine.navigationChannel.setInitialRoute("main")
flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
//缓存 FlutterEngine
val flutterEngineCache = FlutterEngineCache.getInstance()
flutterEngineCache.put(engineId,flutterEngine)
return flutterEngine
}

override fun onDestroy() {
super.onDestroy()
/**
* 注意这里一定要销毁,否则会导致内存泄漏
* 因为 FlutterEngine 比显示它的 FlutterActivity 生命周期要长
* 当我们退出 FlutterActivity 时,FlutterEngine 可能还会继续执行代码
* 所以我们应该在 FlutterActivity 退出时调用 flutterEngine.destroy 停止执行并释放资源
*/
flutterEngine.destroy()
}
}

2、Flutter 端也要做相应的修改:

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
void main() => runApp(getRouter(window.defaultRouteName));

///接收 Android 跳转过来的启动路由参数,如果匹配上了走正常流程
///如果没匹配上,则提示 page not found
Widget getRouter(String routeName) {
switch(routeName){
case "main":
return const MyApp();
default:
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("Flutter Demo Home Page"),
),
body: const Center(
child: Text(
"page not found",
style: TextStyle(
fontSize: 24,
color: Colors.red
),
),
),
),
);
}
}

3、当我们修改 Flutter 工程的代码后,重新运行 Android 项目并不会生效,我们需要:

1、在 Flutter 工程重新执行 flutter build aar 命令

2、待 Flutter 命令执行完成,clean Android 工程

此时我们运行 Android 项目,就可以看到效果了:

ezgif.com-gif-maker (1).gif

可以看到,页面跳转变得非常丝滑

现在只是简单的跳转,那么如果我想在跳转时给 Flutter 页面传值要怎么做呢?

2.2、Android 给 Flutter 页面传值

分析 2.1 这个例子,我们在 Android 工程中设置了启动路由:

1
flutterEngine.navigationChannel.setInitialRoute("main")

然后在 Flutter 中通过 window.defaultRouteName 获取了路由

那么我是否可以在启动路由中多添加一些数据,然后 Flutter 获取后进行解析呢?例如:

1
2
3
4
5
6
7
8
9
10
11
//1、我在 Android 中这样设置
flutterEngine.navigationChannel.setInitialRoute("main?{\"name\":\"erdai\",\"age\":18}")

//2、Flutter 中获取路由并进行解析
String url = window.defaultRouteName;
//获取路由名称
String routeName = url.substring(0,url.indexOf("?"));
//获取参数,将参数解析并转换成一个 Map 对象
String paramsString = url.substring(url.indexOf("?") + 1);
Map<String,dynamic> paramsMap = json.decode(paramsString);

实际上就是这么干的,我们修改 Flutter 端的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
void main(){
//获取 Android 传过来的路由
String url = window.defaultRouteName;
//解析并获取路由名称
String routeName = url.substring(0,url.indexOf("?"));
//解析并将参数转换成一个 Map 对象
String paramsString = url.substring(url.indexOf("?") + 1);
Map<String,dynamic> paramsMap = json.decode(paramsString);
//打印参数
print(paramsMap);
runApp(getRouter(routeName));
}
//...

当我们发布 aar,clean Android 工程并重新运行会进行参数的打印:

202209221611327.png

三、Android 嵌入 Flutter 页面(FlutterFragment)

类比 Android 启动 FlutterActivity,主要是通过两种方式构建 intent 对象:

1
2
3
4
5
6
7
//方式一
var intent: Intent = FlutterActivity.createDefaultIntent(this)

//方式二
var intent = FlutterActivity
.withCachedEngine(FLUTTER_ENGINE_ID)
.build(this)

构建 FlutterFragment 类似:

1
2
3
4
5
6
7
//方式一
var flutterFragment: FlutterFragment = FlutterFragment.createDefault()

//方式二
var flutterFragment: FlutterFragment = FlutterFragment
.withCachedEngine(FLUTTER_ENGINE_ID)
.build()

修改 Android 代码 :

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//1、我们新建一个 SecondActivity
//activity_second.xml内容:FrameLayout 用于承载 FlutterFragment
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/flFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity"/>

//2、修改 SecondActivity
class SecondActivity : AppCompatActivity() {
companion object{
//缓存 FlutterEngine 的 key
const val FLUTTER_ENGINE_ID = "default"
}
//FlutterEngine
private lateinit var flutterEngine: FlutterEngine
//FlutterFragment
private lateinit var flutterFragment: FlutterFragment

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
//初始化 FlutterEngine
flutterEngine = initFlutterEngine(FLUTTER_ENGINE_ID)

//初始化 FlutterFragment
flutterFragment = FlutterFragment
.withCachedEngine(FLUTTER_ENGINE_ID)
.build()

//将 FlutterFragment 嵌入到 SecondActivity 中
supportFragmentManager.beginTransaction().replace(R.id.flFragmentContainer,flutterFragment).commit()
}

/**
* 初始化 FlutterEngine
* 上述代码一般在跳转前调用,这样可以加快我们页面的一个跳转
*/
private fun initFlutterEngine(engineId: String): FlutterEngine {
//创建 FlutterEngine
val flutterEngine = FlutterEngine(this)
//指定要跳转的 Flutter 页面并携带参数
flutterEngine.navigationChannel.setInitialRoute("main?{\"name\":\"erdai\",\"age\":18}")
flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
//缓存 FlutterEngine
val flutterEngineCache = FlutterEngineCache.getInstance()
flutterEngineCache.put(engineId,flutterEngine)
return flutterEngine
}

//重写一些方法,然后将其转发到了 FlutterFragment 中
override fun onPostResume() {
super.onPostResume()
flutterFragment.onPostResume()
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
flutterFragment.onNewIntent(intent)
}

override fun onBackPressed() {
super.onBackPressed()
flutterFragment.onBackPressed()
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
flutterFragment.onRequestPermissionsResult(requestCode,permissions,grantResults)
}

override fun onUserLeaveHint() {
super.onUserLeaveHint()
flutterFragment.onUserLeaveHint()
}

override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
flutterFragment.onTrimMemory(level)
}

override fun onDestroy() {
super.onDestroy()
//停止代码执行并释放资源
flutterEngine.destroy()
}
}

//3、在 AndroidManifest 文件中设置 SecondActivity 主题
<activity
android:name=".SecondActivity"
android:exported="false"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"/>

//4、修改 MainActivity 跳转按钮跳转到 SecondActivity
fun toSecondActivity(view: View) {
startActivity(Intent(this,SecondActivity::class.java))
}

上述 SecondActivity 中我们重写了很多方法,然后将其转发到了 FlutterFragment 中,主要目的是为了实现 Flutter 中所有预期的行为

接下来看下效果:

ezgif.com-gif-maker2.gif

四、Android 与 Flutter 通信

Flutter 提供了一套 PlatformChannel 机制用于 Flutter 和 Android 的通信,主要分为三种类型:

1、MethodChannel:主要用于传递方法调用,Flutter 和 Native(Android)之间进行方法调用时可以使用,是一种双向的通信方式

2、EventChannel:主要用于用户数据流的通信,如:手机电量变化,网络连接变化等。这种方式只能 Native(Android)向 Flutter 发送数据,是一种单向的通信方式

3、BaseicMessageChannel:主要用于传递各种类型数据,它支持的类型有很多,如:String,半结构化信息等,是一种双向的通信方式

4.1、MethodChannel

上面我们介绍了 Android 给 Flutter 页面传值,主要是通过这行代码:

1
flutterEngine.navigationChannel.setInitialRoute("main?{\"name\":\"erdai\",\"age\":18}")

点击查看 navigationChannel 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
//NavigationChannel 源码
public class NavigationChannel {
private static final String TAG = "NavigationChannel";

@NonNull public final MethodChannel channel;

public NavigationChannel(@NonNull DartExecutor dartExecutor) {
this.channel = new MethodChannel(dartExecutor, "flutter/navigation", JSONMethodCodec.INSTANCE);
channel.setMethodCallHandler(defaultHandler);
}
//...
}

发现它实际就是对 MethodChannel 做了一层封装,底层是通过 MethodChannel 来进行通信

这种方式在开发中用的比较多,使用也比较简单,我们直接通过例子说明

下面实现这么一个需求:首先从 MainActivity 跳转到 SecondActivity,然后 SecondActivity 每隔一秒给 Flutter 页面发送一个数字,Flutter 接收到数字并显示到中间的 Text 中,当接收到数字等于 5 ,通知 SecondActivty finish

先看一眼实现的效果:

ezgif.com-gif-maker (3).gif

1、首先来看 Android 端代码实现,SecondActivity 新增的部分:

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
class SecondActivity : AppCompatActivity() {

//...
//MethodChannel
private lateinit var methodChannel: MethodChannel
//发送给 Flutter 的数字
private var count = 0

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
//...
//MethodChannel初始化,注意后面的字符串必须保持 Android 和 Flutter 一致
methodChannel = MethodChannel(flutterEngine.dartExecutor,"com.dream.interactive")
//设置 Flutter 传给我们的方法回调
methodChannel.setMethodCallHandler { call, result ->
if(call.method == "sendFinish"){
finish()
}
}
//开启定时器,每隔一秒给 Flutter 发送一个数字
startTimer()
}

private fun startTimer() {
Timer().schedule(timerTask {
runOnUiThread {
val map = mapOf("count" to count++)
methodChannel.invokeMethod("timer", map)
}
}, 0, 1000)
}

//...
}

2、Flutter 端代码实现,新增部分:

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
class _MyHomePageState extends State<MyHomePage> {
//记录传递过来的数字
int _counter = 0;
//初始化 MethodChannel,字符串必须保持 Android 和 Flutter 一致
final _channel = const MethodChannel("com.dream.interactive");
//...

@override
void initState() {
super.initState();
//设置接收 Android 传递过来的方法回调
_channel.setMethodCallHandler((call) async {
String method = call.method;
switch(method){
//如果匹配到了 timer 方法
case "timer":
//接收传递过来的数字并刷新 UI
setState(() {
_counter = call.arguments["count"];
});
//当数字等于 5,通知 Android finish SecondActivity
if(_counter == 5){
_channel.invokeMethod("sendFinish");
break;
}
break;
default:
break;
}
});
}
//...
}

4.2、EventChannel

我们使用 EventChannel 模拟 Android 发送一个充电信息给 Flutter ,Flutter 接收后在中间的 Text 展示出来

1、 Android 端代码实现,SecondActivity 新增的部分:

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
class SecondActivity : AppCompatActivity() {

/**
* EventChannel 事件接收器,它是一个接口,我们主要通过它给 Flutter 传递 event 事件
*/
private lateinit var eventSink: EventChannel.EventSink
//电量信息
private var electricity = 0

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)

//初始化 EventChannel,注意后面的字符串必须保持 Android 和 Flutter 一致
val eventChannel = EventChannel(flutterEngine.dartExecutor,"com.dream.eventchannel")
//设置接收 Flutter 传递过来的数据流回调
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
//当 Flutter 与 Android 建立连接后会回调此方法
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
//打印 Flutter 传过来的参数,建立连接时返回的值,仅此一次
Log.d("erdai", "onListen: $arguments")
//对 eventSink 赋值
eventSink = events
//开启定时器,每隔一秒电量增加 20%
startTimer()
}

//当 Flutter 与 Android 断开连接后会回调此方法
override fun onCancel(arguments: Any?) {
Log.d("erdai", "onCancel: 断开连接")
}
})
}

//开启定时器,每隔一秒电量增加 20%
private fun startTimer() {
Timer().schedule(timerTask {
runOnUiThread {
//每隔一秒电量 +20
electricity += 20
//发送事件给 Flutter
eventSink.success("电量:$electricity%")
if(electricity == 100){
//当电量为 100 ,发送完成事件给 Flutter
eventSink.endOfStream()
}
}
}, 0, 1000)
}
}

2、Flutter 端代码实现,新增部分:

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
class _MyHomePageState extends State<MyHomePage> {
//电量信息
dynamic electricity;
//EventChannel 注意后面的字符串必须保持 Android 和 Flutter 一致
final _eventChannel = const EventChannel("com.dream.eventchannel");
//订阅流信息
StreamSubscription? _streamSubscription;


@override
void initState() {
super.initState();
//初始化 StreamSubscription
_streamSubscription = _eventChannel
.receiveBroadcastStream(["Hello,建立连接吧"])
.listen(_onData,onError: _onError,onDone: _onDone);
}

//接收 Andorid 发送过来的正常事件
void _onData(event){
//打印
print(event);
//对 electricity 赋值,刷新 UI
setState(() {
electricity = event;
});
}

//接收 Andorid 发送过来的 error 事件
void _onError(error){
//打印
print(error);
}

//接收 Android 发送过来的完成事件
void _onDone(){
print('_onDone');
}

//释放资源
@override
void dispose() {
if(_streamSubscription != null){
_streamSubscription?.cancel();
_streamSubscription = null;
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
//...
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//...
Text(
'$electricity',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
//..
);
}
}

接下来我们看下效果和 Log 日志:

ezgif.com-gif-maker (4).gif 202209221613607.png

4.3、BaseicMessageChannel

我们使用 BaseicMessageChannel 实现一段 Andorid 和 Flutter 的对话,Flutter 收到 Android 的消息,在中间的 Text 展示出来,Android 收到 Flutter 的消息,使用 Toast 展示出来

1、 Android 端代码实现,SecondActivity 新增的部分:

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
class SecondActivity : AppCompatActivity() {
//...
//BasicMessageChannel
private lateinit var messageChannel: BasicMessageChannel<String>

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
//...

//初始化 BasicMessageChannel,注意后面的字符串必须保持 Android 和 Flutter 一致
messageChannel =
BasicMessageChannel(flutterEngine.dartExecutor,"com.dream.messagechannel",StringCodec.INSTANCE)
//设置接收 Flutter 传递过来的消息回调
messageChannel.setMessageHandler { replay: String?, reply: BasicMessageChannel.Reply<String> ->
//打印 Flutter 发过来的消息
Log.d("erdai", "onCreate: $replay")
//使用 Toast 展示出来
Toast.makeText(this,replay,Toast.LENGTH_SHORT).show()
//回传消息给 Flutter
reply.reply("梧桐山")
}

//发送消息给 Flutter
messageChannel.send("周末去爬山吗?") { replay: String? ->
//接收 Flutter 回传的消息
//打印 Flutter 回传的消息
Log.d("erdai", "onCreate: $replay")
//使用 Toast 展示出来
Toast.makeText(this,replay,Toast.LENGTH_SHORT).show()
}
}

//...
}

2、Flutter 端代码实现,新增部分:

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
class _MyHomePageState extends State<MyHomePage> {

//记录 Android 传过来的值
dynamic _content;
//BasicMessageChannel,注意后面的字符串必须保持 Android 和 Flutter 一致
final _messageChannel = const BasicMessageChannel("com.dream.messagechannel", StringCodec());

@override
void initState() {
super.initState();
//设置接收 Android 传递过来的消息回调
_messageChannel.setMessageHandler((message) =>Future<String>((){
//打印 Android 发送过来的消息
print(message);
//给 _content 赋值,刷新 UI
setState(() {
_content = message;
});
//回传值给 Android
return "好啊";
}));

//...
}

//点击 FloatingActionButton 的响应方法
void _incrementCounter() async{
//给 Android 发送消息,并接收 Android 回传的消息
var result = await _messageChannel.send("去爬哪座山?");
//打印 Android 回传的消息
print("$result");
//给 _content 赋值,刷新 UI
setState(() {
_content = result;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//...
Text(
'$_content',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

看下效果和 Log 日志:

ezgif.com-gif-maker (5).gif

202209221614000.png

4.4、通信原理

202209221615654.png

从图中我们可以看出:

1、Android 和 Flutter 都是以 ByteBuffer 为载体,然后通过 BinaryMessenger 来发送和接收数据

2、Android 和 Flutter 都是基于 PlatformChannel 机制来进行通信的

之所以我们能够如此简单的进行通信,实则是系统给我们做了大量的封装:线程的切换,数据拷贝等复杂操作

另外需要注意的是:在 Android 侧,BinaryMessenger 是一个接口,在 FlutterView 中实现了该接口,在 BinaryMessenger 的方法中通过 JNI 来与系统底层沟通。在 Flutter 侧,BinaryMessenger 是一个类,该类的作用就是与类 window 沟通,而类 window 才真正与系统底层沟通

五、总结

本篇文章我们介绍了:

1、Android 集成 Flutter

主要就是将 Flutter 端的代码打成 aar ,然后 Android 引用这个 aar

2、Android 调起 Flutter 页面(FlutterActivity,FlutterFragment),并给 Flutter 页面传值

传值底层使用的 MethodChannel

3、Android 与 Flutter 通信,主要使用到了 Flutter 的 PlatformChannel 机制,其实现主要有三种类型:

1、MethodChannel:用于 Flutter 和 Android 之间的方法通信,双向的

2、EventChannel:用于 Flutter 和 Android 之间的数据流通信,单向的:Android -> Flutter

3、BaseicMessageChannel:用于 Flutter 和 Android 之间的数据通信,双向的

4、简单的介绍了 Android 与 Flutter 通信的原理

好了,本篇文章到这里就结束了,希望能给你带来帮助 🤝

感谢你阅读这篇文章

下篇预告

下篇文章我会讲开发 Flutter 项目的一个技术选型,尽请期待吧🍺

参考和推荐

Android 集成 Flutter | 与交互

一篇看懂Android与Flutter之间的通信

你的点赞,评论,是对我巨大的鼓励!

欢迎关注我的公众号: sweetying ,文章更新可第一时间收到

如果有问题,公众号内有加我微信的入口,在技术学习、个人成长的道路上,我们一起前进!


Flutter 系列(八):Flutter 与 Android 的你来我往
https://sweetying520.github.io/2022/10/10/Flutter 系列(八):Flutter 与 Android 的你来我往/
作者
sweetying
发布于
2022年10月10日
许可协议