一、整体架构
第一层:路由层(Flutter Boost )
负责页面跳转,统一管理Flutter和Native的页面导航
第二层:Channel通信层 (MethodChannel,EventChannel,BasicMessageChannel)
负责数据和方法调用,Flutter调Native或者Native推数据
第三层:Plugin实现层
负责Channel的管理和生命周期,统一注册和初始化
我们的Flutter和Native通信方案架构主要分为 Flutter Boost 路由层,主要负责Flutter和Native页面的统一管理和跳转、Channel通信层,负责flutter和native通信,主要包含MethodChannel,EventChannel,BasicMessageChannel三种、plugin实现层,是native层对channel的实现,包含了统一注册管理
二、路由层(最上层)
先说路由层,采用的是Flutter Boost 库进行路由,之所以采用Flutter Boost 路由,是因为第一它可以解决
Flutter和Native页面栈管理
官方的方案中flutter页面和native页面有两个页面栈,native页面和flutter页面交替打开,返回时可能不符合预期,比如native1代开flutter1,再打开native1,返回时,直接返回到native1 了,
FlutterBoost 将 Flutter 页面和native页面都统一到原生页面栈中进行管理。这样返回的时候就是符合预期的,大致的原理就是它打开flutter页面是利用了一个flutteractivity作为容器去承载flutter页面,这个flutteractivity是原生的activity,所以会位于原生的页面栈中
第二它有
引擎复用机制
官方的方案中每次打开一个flutter页面都初始化一个flutter engine,
FlutterBoost 通过复用单个 Flutter 引擎实例,避免了官方多引擎方案中频繁创建引擎的性能损耗(引擎初始化比较耗时、另外多个引擎会导致内存占用高)。
但是 FlutterBoost 利用了 Flutter 底层的一个特性:渲染引擎(Engine)和承载画面的画布(View / Surface)是可以解耦、动态绑定的。
大致的的原理就是在一个flutter页面显示需要一个flutterview 和FlutterEngine,FlutterEngine(引擎):负责在后台跑 Dart 代码、处理逻辑、计算布局。它是不长眼睛的,只负责"画图"。FlutterView(画布):是原生的组件(Android 的 SurfaceView 或 iOS 的 UIView )。它负责把引擎画好的东西"显示"在手机屏幕上,并接收用户的触摸事件,当flutter A页面进入打fluttre b页面时,FlutterBoost 发现页面 A 要被盖住了,它立刻把 FlutterEngine 从 FlutterView A 上"剥离(Detach)" 下来,然后把这个 FlutterEngine 立刻"附着(Attach)" 到新页面的 FlutterView B 上。然后进行flutter b页面的代码运行,布局绘制以及显示,为了不让底下的页面 A 变成一片空白,FlutterBoost 在引擎离开页面 A 的一瞬间,会把页面 A 的最后一帧画面"截个图(Bitmap/Snapshot)"贴在 FlutterView A 上

生命周期同步
- 双向同步 :原生容器的生命周期事件(如
onCreate、onResume、onDestroy)通过消息通道同步到 Flutter 侧,Flutter 页面的 Widget 生命周期(如initState、dispose)与原生容器保持一致。
统一路由管理:开发者无需关心底层实现,只需通过路由名跳转,简化混合开发复杂度
2.4 Flutter Boost初始化
在Application的onCreate里初始化:
FlutterBoost初始化的时候要传入一个FlutterBoostDelegate,然后实现它的pushNativeRoute和pushFlutterRoute,对应打开本地页面和flutter页面,flutter打开一个页面时,先判断是否是是flutter页面,然后FlutterBoostDelegate的pushNativeRoute或者pushFlutterRoute去打开本地或者flutter页面
native页面打开flutter页面是通过FlutterBoost.instance().open(options),然后也是间接调用到FlutterBoostDelegate的pushFlutterRoute去打开flutter页面
pushNativeRoute就是根据页面名称启动对应的activity
pushFlutterRoute就是启动我们自定义的FlutterBoostActivity,然后根据参数去初始化对应的flutter页面
kotlin
object FlutterFacade {
fun initEngineIfNeed(application: Application) {
FlutterBoost.instance().setup(application, object : FlutterBoostDelegate {
// Flutter调Native页面
override fun pushNativeRoute(options: FlutterBoostRouteOptions) {
openNativePage(options)
}
// Flutter调Flutter页面
override fun pushFlutterRoute(options: FlutterBoostRouteOptions) {
// 创建FlutterActivity并启动
val intent = FlutterBoostActivityNew.CachedEngineIntentBuilder(
FlutterBoostActivityNew::class.java
)
.backgroundMode(backgroundMode)
.url(options.pageName()) // 路由名
.urlParams(options.arguments()) // 参数
.build(context)
context.startActivity(intent)
}
}, { engine: FlutterEngine ->
// 引擎初始化完成后注册Plugin
for (clas in plugins) {
engine.plugins.add(clas.newInstance())
}
}, FlutterBoostSetupOptions.Builder()
.isDebugLoggingEnabled(Api.DEBUG_MODE)
.shouldOverrideBackForegroundEvent(true) // 接管生命周期
.build()
)
}
}
亮点:
- 统一栈管理:Native和Flutter页面统一导航,用户体验流畅
- Engine复用:多个Flutter页面共享一个Engine,首屏秒开
- 生命周期接管:接管前后台事件,保证Flutter页面状态和App状态同步
三、Channel层
3.1 三种Channel对比
| 类型 | 通信方向 | 返回值 | 适用场景 |
|---|---|---|---|
| MethodChannel | Flutter→Native | 必须Result回调 | 获取Native数据或执行Native功能 |
| EventChannel | Native→Flutter | sink推送,无返回值 | 实时数据推送,长时间的数据流 |
| BasicMessageChannel | 双向皆可 | 可选Reply | 耗时复杂操作 |
| Channel 类型 | Native → Flutter | Flutter → Native | |
|---|---|---|---|
| MethodChannel | ✅ 主动调用 | ✅ 主动调用 | |
| BasicMessageChannel | ✅ 发送消息 | ✅ 发送消息 | |
| EventChannel | ✅ 推送数据 | ❌ 不能推送数据 |
选择原则:
- Flutter先发起、需要结果 → MethodChannel
- Native主动推送 → EventChannel
- 耗时操作 → BasicMessageChannel
MethodChannel 主要负责Flutter→Native调用,获取获取Native数据或执行Native能力
EventChannel 主要是Native向Flutter推送数据,比如币种价格变化推送
BasicMessageChannel 用于双向发送消息,比较频繁的通信
3.2 MethodChannel:请求-响应模式
整体流程
Flutter调用 → invokeMethod → MethodChannel → MethodCallHandler处理
↓
业务逻辑
↓
Flutter接收 ← Result.success/error ← MethodChannel ← 结果返回
kotlin
// 获取国际化字符串
static Future<String?> getString(String localizedLabel) => ChannelManager.commonPlatformChannel
.invokeMethod<String>(BridgeMethod.getString, localizedLabel);
class CommonPlugin : BasePlugin() {
private var channel: MethodChannel? = null
private val name = CHANNEL_COMMON
override fun connectChannel(messenger: BinaryMessenger, context: Context) {
Logger.tag(LoganTag.TAG_FLUTTER_BRIDGE).d("connectChannel $name")
handler = CommonBridgeImpl()
channel = MethodChannel(messenger, name).apply {
setMethodCallHandler(handler)
}
}
class CommonBridgeImpl : MethodChannel.MethodCallHandler, IFlutterCommonBridge, IFlutterHandler {
private val scope get(): CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
private val dataAnalysisMarketRepo by lazy { DataAnalysisMarketRepo() }
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Logger.tag("FlutterBridge").i("[${Thread.currentThread()}] onMethodCall ${call.method}")
try {
when (call.method) {
COMMON_GET_STRING -> {
result.success(getString(call.arguments as String))
}
markdown
MethodChannel(请求-响应)
├── CommonChannel(通用能力)
│ ├── getHost - 获取host
│ ├── getVersion - 获取版本号
│ ├── getLocalized - 获取当前语言
│ ├── isUserLogin - 判断登录状态
│ ├── getUserInfo - 获取用户信息
│ └── ...共30+方法
├── NetworkChannel(网络请求)
│ └── request - 统一HTTP请求
├── UCenterChannel(用户中心)
│ ├── changeLanguage - 切换语言
│ └── changeValuation - 切换计价方式
└── TradeChannel(交易记录)
└── ...
EventChannel(订阅-推送)
├── ws_channel:spot - 现货行情流
└── ws_channel:contract - 合约行情流
BasicMessageChannel(双向消息)
├── getDepositQRCodeImage - 生成充值二维码
└── compressImage - 图片压缩
PlatformView(原生组件嵌入)
├── BarChartView - K线图
└── OrderBookView - 订单簿
CommonPlugin负责通用数据或者能力调用,比如获取当前语言,获取用户信息,判断登录状态,复制粘贴
UCenterPlugin负责用户中心设置调用,比如切换语言,修改涨跌色,修改计价方式,等
NetworkPlugin - 网络请求通道
因为native网络请求比较成熟,另外可以进行统一的错误处理,以及添加一些公共逻辑,比如带上cookie,签名超时重试,缓存响应等等,所以构建了一个NetworkPlugin
-
统一错误处理:有错误码解析、错误提示逻辑,证书校验等
-
公共逻辑多:Token注入、签名、超时重试、响应缓存
Flutter将请求方法、URL、参数、Headers等包装成json参数,传递给native,Native解析后调用封装好的网络工具(复用的是之前js的网络请求接口)
Flutter传过来的JSON包含请求方法、URL、参数、Headers等。Native解析后调用封装好的网络工具,获取网络请求后,将结果包装成json传递回调个flutter
Channel名称:com.kcex.flutter.plugin/network_channel
class NetworkBridgeImpl :
3.3 EventChannel:订阅-推送模式
整体流程
Flutter订阅 → EventChannel → StreamHandler.onListen
↓
Native后台监听数据源 → 数据变化 → sink.success(data) → Flutter接收
↓
Flutter取消订阅 → StreamHandler.onCancel → 清理资源
kotlin
Stream<List<TickerEntity>?> marketSpotTickerStream() {
return _spotMarketEventChannel.receiveBroadcastStream().asyncMap((msg) => _mapToTicker(msg, PairType.spot));
}
class WsPlugin : BasePlugin() {
private val wsSpotMarketName = CHANNEL_WS_SPOT
private val wsContractMarketName = CHANNEL_WS_CONTRACT
private var wsSpotMarketChannelHandler: WsBridgeImpl? = null
private var wsContractMarketChannelHandler: WsBridgeImpl? = null
override fun connectChannel(messenger: BinaryMessenger, context: Context) {
wsSpotMarketChannelHandler = WsBridgeImpl(wsSpotMarketName)
wsContractMarketChannelHandler = WsBridgeImpl(wsContractMarketName)
EventChannel(messenger, wsSpotMarketName).setStreamHandler(wsSpotMarketChannelHandler)
EventChannel(messenger, wsContractMarketName).setStreamHandler(wsContractMarketChannelHandler)
}
class WsBridgeImpl(val channel: String) : EventChannel.StreamHandler {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private var sink: EventChannel.EventSink? = null
private var tickerJob: Job? = null
override fun onListen(p0: Any?, p1: EventChannel.EventSink?) {
sink = p1 ?: return
when (channel) {
CHANNEL_WS_SPOT -> tickerJob = handleSpotTickerStream()
CHANNEL_WS_CONTRACT -> tickerJob = handleContractTickerStream()
}
}
WsPlugin - WebSocket行情通道
Channel名称:
- 现货:
com.kcex.flutter.plugin/ws_channel:spot - 合约:
com.kcex.flutter.plugin/ws_channel:contract
为什么不用轮询?
轮询有三个问题:浪费资源、延迟高、服务器压力大。EventChannel是发布-订阅模式,行情变化才推送,延迟毫秒级。
为什么加100ms debounce?
行情数据一秒好几次,如果每次变化都推送,Flutter UI会频繁重建。加了debounce后,只推送最后一次,兼顾实时性和性能。
key为什么用缩写?
全称:"timeZoneRate"、"currentPrice"、"symbolName"、"priceScale" 缩写:"tzr"、"c"、"nm"、"ps"
省30%流量,对高频推送场景效果明显。
3.4 BasicMessageChannel:双向消息模式
整体流程
Flutter发送 → BasicMessageChannel → MessageHandler处理
↓
耗时操作(子线程)
↓
Flutter接收 ← Reply.reply ← 主线程切换 ← 结果返回
BasicMessagePlugin
kotlin
class MessageChannelTool {
MessageChannelTool._();
static const basicMessageChannel =
BasicMessageChannel('com.kcex.flutter.plugin/basic_message', StandardMessageCodec());
static Future<TransferResult> transformData(TransferParam param) async {
return basicMessageChannel
.send(param.toJson())
.then((value) => TransferResult.fromNative(value));
}
}
class BasicMessagePlugin : BasePlugin() {
private val name = CHANNEL_BASIC_MASSAGE
private var channel: BasicMessageChannel<Any>? = null
private var messageHandler: BasicMessageChannel.MessageHandler<Any>? = null
override fun connectChannel(messenger: BinaryMessenger, context: Context) {
Logger.tag(LoganTag.TAG_FLUTTER_BRIDGE).d("connectChannel $name")
messageHandler = BasicMessageHandler()
channel = BasicMessageChannel(messenger, name, StandardMessageCodec()).apply {
setMessageHandler(messageHandler)
}
}
class BasicMessageHandler<T> : BasicMessageChannel.MessageHandler<T> {
override fun onMessage(
message: T?,
reply: BasicMessageChannel.Reply<T>
) {
message ?: return
Logger.tag("FlutterBridge").i("[${Thread.currentThread()}] onMessage: $message")
try {
val mapParams = message as Map<*, *>
val method = mapParams["method"]
when (method) {
SYMBOL_DATA_CONVERSION -> {
val data = mapParams["data"] as String
val gson = Gson()
val convertData = gson.fromJson(data, SymbolDataConvertData::class.java)
val resultJson = gson.toJson(
NumberFmtUtil.symbolDataConversionImpl(convertData)
)
reply.reply(createResult(MESSAGE_RESULT_OK, data = resultJson))
}
Channel名称:com.kcex.flutter.plugin/basic_message
适用场景:生成二维码、图片压缩、数据转换等耗时操作。
为什么不用MethodChannel?
MethodChannel的Result是同步返回的,如果Native处理耗时长,Flutter会一直等待,可能超时。BasicMessageChannel通过Reply异步返回,不阻塞Flutter。
另外适用于频繁交换数据的场景,比如把编辑框的数量,进行国际化展示等
四、Plugin层(承上启下)
4.1 为什么需要Plugin机制
Flutter官方设计了FlutterPlugin接口,让Plugin的生命周期由Engine自动管理,不用手动register/unregister。
定义了一个BasePlugin基类:实现了FlutterPlugin和IConnectChannel接口
实现FlutterPlugin可以将Plugin和Engine绑定,在IConnectChannel中有connectChannel在onAttachedToEngine时候调用,teardownChannel方法在onDetachedFromEngine时候调用,子类实现这两个方法在connectChannel绑定对应的handler实现对应的逻辑,在teardownChannel释放资源,
另外还有一个onActivityResult,在自定义的flutteractivity的onActivityResult中调用,用于从相册选取图片或者扫码时回传数据
kotlin
abstract class BasePlugin : FlutterPlugin, IConnectChannel {
var handler: MethodChannel.MethodCallHandler? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// Engine关联时初始化
connectChannel(binding.binaryMessenger, binding.applicationContext)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// Engine解绑时销毁,防止内存泄漏
teardownChannel()
}
为什么需要Activity结果穿透?Flutter调起Native相机、扫码、分享后,结果会回到Activity。如果不穿透,Flutter就收不到回调。
// 穿透Activity结果回调
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (handler is IFlutterHandler) {
(handler as IFlutterHandler).onActivityResult(requestCode, resultCode, data)
}
}
}
interface IConnectChannel {
fun connectChannel(messenger: BinaryMessenger, context: Context)
fun teardownChannel()
}
每个Plugin实现这个接口,connectChannel里初始化Channel,teardownChannel里释放资源。
4.3 Plugin注册
FlutterFacade统一注册8个Plugin:
csharp
object FlutterFacade {
val plugins: MutableList<Class<out BasePlugin>> = mutableListOf(
NetworkPlugin::class.java, // 网络请求
CommonPlugin::class.java, // 通用业务
UCenterPlugin::class.java, // 用户中心
TradeRecordPlugin::class.java, // 交易记录
BasicMessagePlugin::class.java, // 耗时消息
WsPlugin::class.java, // WebSocket
FlutterNativeViewPlugin::class.java, // PlatformView
FlutterServiceBridge::class.java, // 服务桥接
)
}
Engine初始化时批量注册:
FlutterBoost.instance().setup(..., { engine: FlutterEngine ->
for (clas in plugins) {
engine.plugins.add(clas.newInstance())
}
}, ...)
亮点:
开闭原则:新增Plugin只需加一行代码
生命周期托管:Engine自动管理,不用手动调用
统一风格:所有Plugin遵循同样模式
注册的时候。维护一个Plugin 的class列表,在FlutterBoost初始化时,Engine初始化时(FlutterBoost.instance().setup),通过clas.newInstance()初始化实例然后添加到engine对应的plugins列表中。
六、PlatformView层(最底层)
在
6.1 解决的问题
Flutter绘制能力很强,但有些场景还是需要原生组件:
- 已有成熟库(K线图用MPAndroidChart)
- 硬件能力(相机、传感器)
- 系统级控件(分享面板、支付键盘)
6.2 原理:三层渲染
Flutter Widget
↓
PlatformViewLink.onCreatePlatformView
↓
Engine 分配 viewId
↓
initSurfaceAndroidView
↓
Android 端创建真实 View
sql
┌──────────────────────────────────┐
│ Activity Window │
│ │
│ ┌────────────────────────────┐ │
│ │ Flutter Surface (GPU) │ │
│ │ │ │
│ │ ┌────────────────────┐ │ │
│ │ │ Hole (透明区域) │ │ │
│ │ └────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────┘ │
│ ↑ │
│ Android Native View │
│ (真实 View,非 Texture) │
│ │
└──────────────────────────────────┘
合成显示(最关键)
sql
Flutter Surface(GPU)
↑
透明区域(Hole Punch)
↑
原生 View(真实 View)
↑
系统 WindowManager / CoreAnimation
↓
屏幕
Flutter 负责 布局和遮罩
原生 View 负责 自己绘制
最终由 操作系统合成
AndroidViewSurface是干什么的?
不是 View
是一个 Surface + 挖洞控制器
作用是:
告诉 Flutter:这里要让原生 View 透出来
管理手势命中测试(hitTest)
Flutter 显示原生 View 是通过 PlatformView 实现的。
Flutter 并不会直接绘制原生 View,而是在自己的 Layer 树中预留一个位置,通过 PlatformViewLink 和AndroidViewSurface 创建占区域,再由原生端创建真实的 View 并 attach 到 Activity Window,添加的地方就是flutter预留的透明区域,
最终由系统的 WindowManager 将 Flutter Surface 和原生 View 合成显示。
NativeView不仅首次创建设置数据,还要根据tab切换更新数据
在flutter侧,定义了一个NativeView,在build方法中Android使用PlatformViewLink,iOS使用UiKitView显示本地视图,在onCreatePlatformView属性中,根据传递过来的params.id,将创建的本地视图id记录,用于后续控制,然后调用PlatformViewsService.initSurfaceAndroidView将params.id,viewType,创建参数(creationParams,包括type和data)设置进去,传递给native进行视图创建
didUpdateWidget方法中(当父 Widget 重建,但 State 对象被复用时)使用了MethodChannel调用本地的update方法,根据视图id,以及新的data去重绘视图
在Android侧,自定义一个plugin(FlutterNativeViewPlugin)继承baseplugin,在onAttachedToEngine中将自定义的platformViewFactory注册到platformViewRegistry进行绑定,在自定义的platformViewFactory的oncreate方法中可以根据创建参数的type创建不同类型的视图,然后将对应的id记录到列表中,用于后续控制区分,这里主要是柱状图和折线图,然后根据创建参数中的数据,给视图设置对应的数据,
在connectChannel中会创建对应的MethodChannel,去实现flutter中对应的update方法,根据id去区分要更新哪个视图,然后给视图设置对应新的数据
6.3 Flutter侧使用
kotlin
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
const nativeViewChannelName = 'com.kcex.flutter.plugin/native_view';
const _channel = MethodChannel(nativeViewChannelName);
class _NativeViewPlugin {
static Future<void> update(int id, dynamic data) {
return _channel.invokeMethod<void>('update', {
'id': id,
'data': data
});
}
static Future<void> dispose(int id) {
return _channel.invokeMethod<void>('dispose', {
'id': id,
});
}
}
typedef DataConverter = dynamic Function(dynamic data);
class NativeView<T> extends StatefulWidget {
final T? data;
final DataConverter? dataConverter;
final String viewType;
const NativeView({
super.key,
this.data,
this.dataConverter,
required this.viewType,
});
@override
State<StatefulWidget> createState() {
return _State();
}
}
class _State extends State<NativeView> {
int? _id;
@override
void didUpdateWidget(covariant NativeView oldWidget) {
super.didUpdateWidget(oldWidget);
if (_id != null && widget.data != null && widget.dataConverter != null) {
_NativeViewPlugin.update(_id!, widget.dataConverter!.call(widget.data));
}
}
@override
Widget build(BuildContext context) {
final creationParams = {
'viewType': widget.viewType,
if (widget.data != null && widget.dataConverter != null)
'data': widget.dataConverter!.call(widget.data)
};
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return PlatformViewLink(
viewType: nativeViewChannelName,
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (params) {
_id = params.id;
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: nativeViewChannelName,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () {
params.onFocusChanged(true);
},
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
case TargetPlatform.iOS:
return UiKitView(
viewType: nativeViewChannelName,
onPlatformViewCreated: (int id) {
_id = id;
},
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
gestureRecognizers: {
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
);
default:
return const Text('unsupported');
}
}
@override
void dispose() {
super.dispose();
if (_id != null) {
_NativeViewPlugin.dispose(_id!);
}
}
}
class FlutterNativeViewPlugin : BasePlugin() {
private val platformViewFactory = FlutterNativeViewFactory()
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
binding.platformViewRegistry.registerViewFactory(CHANNEL_NATIVE_VIEW, platformViewFactory)
super.onAttachedToEngine(binding)
}
override fun connectChannel(messenger: BinaryMessenger, context: Context) {
MethodChannel(messenger, CHANNEL_NATIVE_VIEW).setMethodCallHandler { call, result ->
when (call.method) {
"update" -> {
val args = call.arguments as Map<String, Any>
val id = args["id"] as Int
val view = platformViewFactory.getView(id)
if (view != null) {
val data = args["data"]
if (data == null) {
result.success(0)
return@setMethodCallHandler
}
view.update(data)
}
result.success(0)
}
"dispose" -> {
val args = call.arguments as Map<String, Any>
val id = args["id"] as Int
platformViewFactory.removeView(id)
result.success(0)
}
else -> {
result.success(0)
}
}
}
}
override fun teardownChannel() {
platformViewFactory.clearViews()
}
}
class FlutterNativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
private val _views = mutableMapOf<Int, ViewUpdater>()
override fun create(context: Context, id: Int, args: Any?): PlatformView {
val argsMap = args as Map<String, Any>
val viewType = argsMap["viewType"].toString()
val data = argsMap["data"]
val view = when (viewType) {
"BarChartView" -> FlutterBarChartView(context)
"LineChartView" -> FlutterLineChartView(context)
else -> throw UnsupportedOperationException("$args not support")
}
if (data != null) {
view.update(data)
}
_views[id] = view
return view
}
fun getView(id: Int) = _views[id]
fun clearViews() {
_views.clear()
}
fun removeView(id: Int) {
val view = _views.remove(id) as PlatformView
view.dispose()
}
}
6.5 亮点
- 性能60fps:比Flutter自绘的30fps高一倍,直接利用GPU渲染
- 原生能力复用:直接用MPAndroidChart,不用重写
- 双向通信:Flutter可以控制Native View,Native View可以回调Flutter
- 数据隔离:每个PlatformView有独立viewId,数据不会串
五、数据层
5.1 WebSocket数据流
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 服务端 │ ───→ │ Native端 │ ───→ │ Flutter端 │
│ 推送数据 │ │ EventChannel│ │ 更新UI │
└─────────────┘ └─────────────┘ └─────────────┘
Native端维护WebSocket连接,收到数据后通过EventChannel推送给Flutter。
5.2 HTTP请求数据流
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Flutter端 │ ───→ │ Native端 │ ───→ │ 服务端 │
│ MethodCall │ │ HTTP请求 │ │ 返回数据 │
└─────────────┘ └─────────────┘ └─────────────┘
Flutter通过MethodChannel传请求参数,Native发起HTTP请求,结果通过Result回调返回。
七、总结
| 层次 | 核心组件 | 亮点 |
|---|---|---|
| 路由层 | Flutter Boost | 统一页面栈、Engine复用、弹窗支持 |
| Channel层 | 5 Method + 2 Event + 1 BasicMessage | 按业务域分层、职责单一 |
| Plugin层 | BasePlugin | 生命周期托管、Activity穿透 |
| 数据层 | WebSocket + HTTP | debounce防抖、key缩写省流量 |
| 视图层 | PlatformView | 60fps性能、原生组件复用 |
核心亮点:
- 分层解耦:路由、Channel、Plugin各层分离,耦合度低
- 生命周期托管:Engine自动管理Plugin,不用手动register
- 统一页面栈:Flutter Boost解决混合导航问题
- 性能优化:debounce防抖、key缩写、PlatformView 60fps
- 开闭原则:新增Plugin只需加一行代码