解决问题: Flutter和原生交互分为2种, 数据传输和view的控制显示
形象的举例:
一次典型的方法调用过程类似网络调用!
由作为客户端的 Flutter,通过方法通道向作为服务端的原生代码宿主发送方法调用请求,原生代码宿主在监听到方法调用的消息后,调用平台相关的 API 来处理 Flutter 发起的请求,最后将处理完毕的结果通过方法通道回发至 Flutter
dart与Native之间多了一层C++写的Engine,至于flutter的C++源码,
架构图:
- 3种方式比较, BasicMessageChannel, MethodChannel, EventChannel
Flutter为开发者提供了一个轻量级的解决方案,即逻辑层的方法通道(Method Channel)机制。基于方法通道,我们可以将原生代码所拥有的能力,以接口形式暴露给Dart,从而实现Dart代码与原生代码的交互,就像调用了一个普通的Dart API一样。
Flutter定义了三种不同类型的Channel
1>. BasicMessageChannel:用于传递字符串和半结构化的信息。BasicMessageChannel支持数据双向传递,有返回值。
2> .MethodChannel:用于传递方法调用(method invocation)。MethodChannel支持数据双向传递,有返回值。通常用来调用 native 中某个方法
3>. EventChannel: 用于数据流(event streams)的通信。 EventChannel仅支持数据单向传递,无返回值。有监听功能,比如电量变化之后直接推送数据给flutter端
对比表格:
3种方式的对比.jpg
实现调用步骤 :
1). Flutter能够通过轻量级的异步方法调用,实现与原生代码的交互。一次典型的调用过程由Flutter发起方法调用请求开始,
2). 请求经由唯一标识符指定的方法通道到达原生代码宿主,
3). 而原生代码宿主则通过注册对应方法实现、响应并处理调用请求,
4). 最后将执行结果通过消息通道,回传至Flutter。
5). 方法调用请求的处理和响应,在Android中是通过FlutterView
1.1 问题:Flutter如何实现一次方法调用请求?
方法调用过程是异步的,所以我们需要使用非阻塞(或者注册回调)来等待原生代码给予响应
非阻塞: 就是单线程中的处理方案
1.1 .1 案例演示: flutter调用原生的方法, MethodChannel
Flutter代码
伪代码: flutter发起原生调用
1.1.2 原生通过MethodChannel调用flutter的案例:
在原生代码中完成方法调用的响应(flutter监听原生调用)
android代码
考虑到打开应用市场的过程可能会出错,我们也需要增加 try-catch 来捕获可能的异常:
核心类:
· ) MethodChannel
· ) DartExecutor
· ) BinaryMessenger:是一个接口,在FlutterView中实现了该接口,在BinaryMessenger的方法中通过JNI来与系统底层沟通
· ) FlutterEngine: 发送数据必然要通过
内部都是通过DartMessenger来调用FlutterJNI的相关API完成通信的
需要注意的是,方法通道是非线程安全的。这意味着原生代码与 Flutter 之间所有接口调用必须发生在主线程
1.2. 1 MethodChannel(flutter----> 原生----(返回结果到)—> flutter)
源码分析:
原理: 从flutter开始调用:
调用栈图:
_DefaultBinaryMessenger
PlatformConfiguration
RuntimeController //flutter/runtime/runtime_controller.cc
Engine
Shell //flutter/shell/common/shell.cc
核心: # PlatformViewAndroid
//flutter/shell/platform/android/platform_view_android.cc
这个view对于Android平台的实例为PlatformViewAndroid。
c调用android ,对应FlutterJNI.java中的handlePlatformMessage()方法
Android 端源码分析:
FlutterJN
把消息添加到队列中去!
所有的Channel都会走上面的逻辑,从这里的handlerInfo.handler.onMessage开始有所不一样了,因为不同的Channel的handler不同。
MethodChannel的执行流程涉及到主线程和UI线程的交互,代码从Dart到C++再到Java层,执行完相应逻辑后原路返回,从Java层到C++层再到Dart层。
1.2.2 EventChannel (原生------>flutter)
不存在: 原生向flutter调用, 然后flutter把结果返回给原生!
EventChannel 可以由 Android 原生主动向 Flutter 发起交互请求
相对于原生为主动式交互,类似于 Android 发送一个广播在 Flutter 端进行接收
android端:
flutter部分:
1.2. 3 BasicMessageChannel
BasicMessageChannel 主要传递字符串和半结构化的数据交互;其编解码有多种类型,在使用时建议 Android 与 Flutter 两端一致;
1).BinaryCodec:基本二进制编码类型;
2).StringCodec:字符串与二进制之间的编码类型;
3).JSONMessageCodec:Json 与二进制之间的编码类型;
4).StandardMessageCodec:默认编码类型,包括基础数据类型、二进制数据、列表、字典等与二进制之间等编码类型;
Flutter -> Android
用途: Flutter 端向 Android 端发送 send 数据请求,Android 端接收到后通过 replay 向 Flutter 端发送消息,从而完成一次消息交互;
Android -> Flutter
根据上述继续由 Android 端主动向 Flutter 端发送数据,Android 通过 send 向 Flutter 发送数据请求,Flutter 通过 setMessageHandler 接收后向 Android 端 return 返回结果,再由 Android 回调接收,从而完成一次数据交互;
分析源码 send 有两个构造函数,有两个参数的构造方法用来接收 Flutter 回调的数据;
3者的通信原理总结:
消息信使:BinaryMessenger
以ByteBuffer为数据载体,然后通过BinaryMessenger来发送与接收数据。整体设计如下。
在Android侧,BinaryMessenger是一个接口,在FlutterView中实现了该接口,在BinaryMessenger的方法中通过JNI来与系统底层沟通。在Flutter侧,BinaryMessenger是一个类,该类的作用就是与类window沟通,而类window才真正与系统底层沟通。
虽然三种Channel各有用途,但是他们与Flutter通信的工具却是相同的,均为BinaryMessager。
BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。
Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。
Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。
当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。
1.3. Platform Channel的代码运行在什么线程
Flutter Engine自己不创建线程,其线程的创建于管理是由enbedder提供的,并且Flutter Engine要求Embedder提供四个Task Runner,分别是Platform Task Runner,UI Task Runner,GPU Task Runner和IO Task Runner。
实际上,在Platform侧执行的代码运行在Platform Task Runner中,而在Flutter app侧的代码则运行在UI Task Runner中。在Android和iOS平台上,Platform Task Runner跑在主线程上。因此,不应该在Platform端的Handler中处理耗时操作。
1.4. Platform Channel是否线程安全
Platform Channel并非是线程安全的,这一点在官方的文档也有提及。Flutter Engine中多个组件是非线程安全的,故跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread。故我们在将Platform端的消息处理结果回传到Flutter端时,需要确保回调函数是在Platform Thread(也就是Android和iOS的主线程)中执行的。
1.5. 是否支持大内存数据块的传递
Platform Channel实际上是支持大内存数据块的传递,当需要传递大内存数据块时,需要使用BasicMessageChannel以及BinaryCodec。而整个数据传递的过程中,唯一可能出现数据拷贝的位置为native二进制数据转化为Dart语言二进制数据。若二进制数据大于阈值时(目前阈值为1000byte)则不会拷贝数据,直接转化,否则拷贝一份再转化。
1.6. 如何将Platform Channel原理应用到开发工作中
实际上Platform Channel的应用场景非常多,我们这里举一个例子:
在平常的业务开发中,我们需要使用到一些本地图片资源,但是Flutter端是无法使用Platform端已存在的图片资源的。当Flutter端需要使用一个Platform端已有的图片资源时,只有将该图片资源拷贝一份到Flutter的Assert目录下才能使用。实际上,让Flutter端使用Platform端的资源并不是一件难事。
我们可以使用BasicMessageChannel来完成这个工作。Flutter端将图片资源名name传递给Platform端,Native端使用Platform端接收到name后,根据name定位到图片资源,并将该图片资源以二进制数据格式,通过BasicMessageChannel,传递回Flutter端。
2. 混合交互的缺点:
1). android 还是得写代码,ios端还是得写代码, 要维护!
2). 运行多个Flutter实例,或在屏幕局部上运行Flutter可能会导致不可以预测的行为;
3). 在后台模式使用Flutter的能力还在开发中(目前不支持);
4).将Flutter库打包到另一个可共享的库或将多个Flutter库打包到同一个应用中,都不支持;
5).添加到应用在Android平台的实现基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能会有不可预知的行为。
混合开发的2种情况
- .flutter项目,里面用android或者ios
2).android项目中,加入flutter进行混合
3. 插件Pigeon
Pigeon工具生成的代码是基于BasicMessageChannel实现通信的。
- 生成的代码是 Java/Objective-C,但是由于 Kotlin 可以调用 Java,Swift 可以调用 Objective-C
4.fluttter 嵌套原生的view视图
Flutter提供了一个平台视图(Platform View)的概念。它提供了一种方法,允许开发者在Flutter里面嵌入原生系统(Android和iOS)的视图,并加入到Flutter的渲染树中,实现与Flutter一致的交互体验。
嵌套原生的view:
由于Flutter与原生渲染方式完全不同,因此转换不同的渲染数据会有较大的性能开销。如果在一个界面上同时实例化多个原生控件,就会对性能造成非常大的影响,所以我们要避免在使用Flutter控件也能实现的情况下去使用内嵌平台视图。
一方面, 需要分别在Android和iOS端写大量的适配桥接代码,违背了跨平台技术的本意,也增加了后续的维护成本;
另一方面, 毕竟除去地图、WebView、相机等涉及底层方案的特殊情况外,大部分原生代码能够实现的UI效果,完全可以用Flutter实现。
平台视图PlatformView
PlatformView跟add to app怎么选
FlutterView的创建:
在Flutter中嵌套原生视图: 通过平台视图,我们就可以将一个原生控件包装成Flutter控件,嵌入到Flutter页面中,
就像使用一个普通的Widget一样。把原生视图组装成一个 Flutter 控件
一次典型的平台视图使用过程与方法通道类似:
首先,由作为客户端的Flutter,通过向原生视图的Flutter封装类(在iOS和Android平台分别是UIKitView和AndroidView)传入视图标识符,用于发起原生视图的创建请求;
然后,原生代码侧将对应原生视图的创建交给平台视图工厂(PlatformViewFactory)实现;
最后,在原生代码侧将视图标识符与平台视图工厂进行关联注册,让Flutter发起的视图创建请求可以直接找到对应的视图创建工厂。
架构原理图:
实现步骤:
1. android 端,把view封装一个.PlatformView !
2. android端创建一个工厂
3. android通过高方法通道, 绑定注册view
flutter端: 直接返回一个绑定方法通道标识的view,AndroidView!
案例demo2:
问题: flutter如何实现原生视图的接口调用?
flutter端:
如何在原生系统实现接口?
android端:
5.如何在程序运行时,动态地调整原生视图的样式?
flutter端我们会用到原生视图的一个初始化属性,即 onPlatformViewCreated
原生端: 会用到MethodChannel, 方法通道
实现原理:
flutter:
1.创建一个controller
- 初始化的时候在controller中创建方法通道
3.在controller中, invoke通道方法(声明方法)
android端实现: 方法通道, 动态调用flutter的方法
案例demo3:
flutter端的代码
Android 端的代码
最后
为了能够方便大家快速学习Flutter, 这里整理了Flutter学习路线图以及《Flutter Dart 语言编程入门到精通》&《Flutter实战:第二版》帮助大家配置相关环境,学习Flutter 的基本语法以及最后的项目实际利用。
学习路线:
《Flutter Dart 语言编程入门到精通》
第一章 Dart语言基础
- 环境准备
- 基础语法
第二章 Dart 异步编程
- Dart的事件循环
- 调度任务
- 延时任务
- Future详解
- async和await
- lsolate
第三章 异步之 Stream 详解
- 什么是Stream
- 单订阅流
- 广播流
- Stream Transformer
- 总结
第四章 Dart标准输入输出流
- 文件操作
第五章 Dart 网络编程
- TCP服务端
- TCP客户端
- UDP服务端
- UDP客户端
- HTTP服务器与请求
- WebSocket
第六章 Flutter 爬虫与服务端
- Dart爬虫开发
- Dart服务端
- 创建Flutter项目演示
- 总结
第七章 Dart 的服务端开发
- 注册登录实现
第八章 Dart 调用C语言混合编程
- 环境准备
- 测试Dart ffi接口
- 总结
第九章 LuaDardo中Dart与Lua的相互调用
- Lua C API
- 创建运行时
- Dart调Lua
- Lua调Dart
《Flutter实战:第二版》
第一章:起步
- 1.1 移动开发技术简介
- 1.2 初始Flutter
- 1.3 搭建Flutter开发环境
- 1.4 Dart语言简介
第二章:第一个Flutter应用
- 2.1 计数器应用实例
- 2.2 Widget简介
- 2.3 状态管理
- 2.4路由管理
- 2.5包管理
- 2.6 资源管理
- 2.7 调试Flutter应用
- 2.8 Flutter异常捕获
第三章:基础组件
- 3.1 文本及样式
- 3.2 按钮
- 3.3 图片及ICON
- 3.4 单选开关和复选框
- 3.5 输入框及表单
- 3.6 进度指示器
第四章:布局类组件
- 4.1 布局类组件简介
- 4.2 布局原理与约束(constraints)
- 4.3 线性布局(Row和Column)
- 4.4 弹性布局(Flex)
- …
第五章:容器类组件
- 5.1 填充(Padding)
- 5.2 装饰容器(DecoratedBox)
- 5.3 变换(Transform)
- 5.4 容器组件(Container)
- 5.5 剪裁(Clip)
- 5.6 空间适配(FittedBox)
- 5.7 页面骨架(Scaffold)
第六章:可滚动组件
- 6.1 可滚动组件简介
- 6.2 SingleChildScrollView
- 6.3 ListView
- 6.4 滚动监听及控制
- …
第七章:功能型组件
- 7.1 导航返回拦截(WillPopScope)
- 7.2 数据共享(InheritedWidget)
- 7.3 跨组件状态共享
- 7.4 颜色和主题
- 7.5 按需rebuild(ValueListenableBuilder)
- 7.6 异步UI更新(FutureBuilder、StreamBuilder)
- 7.7 对话框详解
第八章:事件处理与通知
- 8.1 原始指针事件处理
- 8.2 手势识别
- 8.3 Flutter事件机制
- 8.4 手势原理与手势冲突
- 8.5 事件总线
- 8.6 通知 Notification
第九章:动画
- 9.1 Flutter动画简介
- 9.2 动画基本结构及状态监听
- 9.3 自定义路由切换动画
- 9.4 Hero动画
- 9.5 交织动画
- 9.6 动画切换组件(AnimatedSwitcher)
- 9.7 动画过渡组件
第十章:自定义组件
- 10.1 自定义组件方法简介
- 10.2 组合现有组件
- 10.3 组合实例:TurnBox
- 10.4 CustomPaint 与 Canvas
- 10.5 自绘实例:圆形背景渐变进度条
- 10.6 自绘组件:CustomCheckbox
- 10.7 自绘组件: DoneWidget
- 10.8 水印实例: 文本绘制与离屏渲染
第十一章:文件操作与网络请求
- 11.1 文件操作
- 11.2 通过HttpClient发起HTTP请求
- 11.3 Http请求库-dio
- 11.4 实例:Http分块下载
- …
第十二章:Flutter扩展
- 12.1 包和插件
- 12.2 Flutter Web
第十三章:国际化
- 13.1 让App支持多语言
- 13.2 实现Localizations
- 13.3 使用Intl包
- 13.4 国际化常见问题
第十四章:Flutter核心原理
- 14.1 Flutter UI 框架(Framework)
- 14.2 Element、BuildContext和RenderObject
- 14.3 Flutter启动流程和渲染管线
- 14.4 布局(Layout)过程
- 14.5 绘制(一)绘制原理及Layer
- …
第十五章:一个完整的Flutter应用
- 15.1 Github客户端示例
- 15.2 Flutter APP代码结构
- 15.3 Model类定义
- 15.4 全局变量及共享状态
- 15.5 网络请求封装
- 15.6 APP入口及主页
- 15.7 登录页
- 15.8 多语言和多主题