背景
在Flutter
项目中,免不了和原生打交道,很多能力最终还是需要原生接口来提供,比如获取App沙盒路径、点击震动反馈、拍摄、访问相册、应用生命周期等。Flutter
使用FlutterChannel
来提供这个能力,至于FlutterChannel
如何实现,如何应用不是本文的重点,本文的重点是探讨如何来全局监听这些FlutterChannel
,并形成一个性能指标。比如传输耗时,传输数据量大小,传输是否报错等,如下图中橙色框内所示。通过这个监听生成的数据为定向优化项目打下基础。
前置知识点
SystemChannels
简述
Flutter
底层也提供了一系列的SystemChannels
,这个系统通道提供一些最基本的能力。下面我简单举例来说明,整个系统通过列表,大家可以去源码(路径src/services/system_channels.dart
)中查看。
- 比如想监听应用生命周期变化,就可以直接用系统通道,没必要自己写。
arduino
// 定义
static const BasicMessageChannel<String?> lifecycle = BasicMessageChannel<String?>(
'flutter/lifecycle',
StringCodec(),
);
// 使用
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
- 比如想监听到系统内存压力,也可以直接用系统通道;Flutter项目监听到这个信号,就可以去释放一些内存以确保App不会因为内存占用太高而被系统杀掉。
dart
// 定义 src/services/system_channels.dart
static const BasicMessageChannel<Object?> system = BasicMessageChannel<Object?>(
'flutter/system',
JSONMessageCodec(),
);
// 使用
SystemChannels.system.setMessageHandler(
(dynamic message) => handleSystemMessage(message as Object));
Future<void> handleSystemMessage(Object systemMessage) async {
final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
final String type = message['type'] as String;
switch (type) {
case 'memoryPressure':
handleMemoryPressure();
}
return;
}
对于一些业务型的的通道,官方没有提供,可以自己写,也可以使用第三方提供的。不再赘述。
ServicesBinding
在阅读源码是时候,先看他注释,使用默认消息信使defaultBinaryMessenger
来监听平台消息。很容易想到,我们只要实现ServicesBinding
混入,并复写这个默认消息信使就可以达到监听的目的了。
简单说说xxxBinging
WidgetsFlutterBinding
通过混入7大xxxBinging
,为Flutter
提供了手势、帧调度、原生平台消息、绘制、语义(比如盲人点击发声)、渲染、组件树七大能力。
scala
class WidgetsFlutterBinding extends BindingBase with
GestureBinding,
SchedulerBinding,
ServicesBinding,
PaintingBinding,
SemanticsBinding,
RendererBinding,
WidgetsBinding
本文主要说下ServicesBinding
,提供和原生交互的能力,并且默认初始化了一些通道,比如前文中提到的应用生命周期事件和内存压力警告其实都在这里实现了。
消息信使 BinaryMessenger
BinaryMessenger
是Flutter
框架给我们提供的唯一一个用于从dart
到原生消息转换的工具,所有的channel
都是基于BinaryMessenger
进行二次包装的。可以理解为一个数据协议,可以用来传递数据。
默认消息信使
查看源码,可以看到Flutter
在初始化ServicesBinding
的时候,创建了一个默认的消息信使用来转换数据。这个默认消息信使名字叫defaultBinaryMessenger
。
defaultBinaryMessenger
是定义在lib/src/services/binding.dart:342
中的_DefaultBinaryMessenger
类一个实例。如下图所示。
那继续来看看_DefaultBinaryMessenger
类的具体实现吧。
我们主要看其中的两个方法,我们需要重写下面的两个方法,然后就可以很简单的全局捕获FlutterChannel
的数据了。
- dart发送消息到native端
scss
Future<ByteData?> send(String channel, ByteData? message)
- dart接收原生的消息
arduino
void setMessageHandler(String channel, MessageHandler? handler)
有了前面的小知识点,话不多说,我们来看看如何全局监听FlutterChannel
。
FlutterChannel全局监听实现
新建一个ChannelObserverServicesBinding
混入。
并且实现系统的BindingBase
,ServicesBinding
。为什么实现ServicesBinding
,是因为其提供和原生交互的能力。BinaryMessengerProxy
是我们重新的消息信使,下文会有详细说明。
typescript
mixin ChannelObserverServicesBinding on BindingBase, ServicesBinding {
@override
BinaryMessenger createBinaryMessenger() {
return BinaryMessengerProxy(super.createBinaryMessenger());
}
}
新建一个ChannelObserverBinding
。
继承自WidgetsFlutterBinding
,并且实现第一个新建的mixin
。这个类主要提供给main
函数调用的,用来初始化我们写的监控方法。
scala
class ChannelObserverBinding extends WidgetsFlutterBinding
with ChannelObserverServicesBinding {
static WidgetsBinding ensureInitialized() {
ChannelObserverBinding();
return WidgetsBinding.instance;
}
}
main
方法调整
新增初始化
csharp
void main() {
ChannelObserverBinding.ensureInitialized();
runApp(const App());
}
以上三步的目的很简单,在Flutter初始化7大混入xxxBinging
的同时,也初始化我们自己用来监听。
显示BinaryMessengerProxy
类
在第1步的时候,大家可能看到了BinaryMessengerProxy
这个类,这个就是我们继承自BinaryMessenger
,并重写其方法,用例捕获我们需要的信息。
4.1 重写send
方法,每当我们在Flutter端调用原生方法,都会进入这个函数,参数说明
channel
就是FlutterChannel
的名字,
message
就是传递的二进制参数,其实这个参数我们可以很简单的解开。因为这个二进制格式基本上是按照以下方法进行编码。当然会存在自定义的编码解码器,需要注意一下就行了。
scala
class BinaryMessengerProxy extends BinaryMessenger {
@override
Future<ByteData?> send(String channel, ByteData? message) async {
final ChannelInfo? info =
_parseChannelInfo(channel, message, InterceptType.SEND);
ByteData? result = await origin.send(channel, message);
_parseResult(info, result);
return result;
}
...
}
4.2 重新setMessageHandler
方法
参数channel
是FlutterChannel
的名字
handler
为注册接收原生消息的回调
大家可以看到,这边新建了一个匿名函数wrapper
, 这个函数主要的目的是给handler
回调加一个钩子,这样在这个handler
需要触发的时候,我们可以全局加上我们需要的监听代码。这个很重要的。
scala
class BinaryMessengerProxy extends BinaryMessenger {
@override
void setMessageHandler(
String channel, Future<ByteData?>? Function(ByteData? message)? handler) {
wrapper(ByteData? message) {
final ChannelInfo? info =
_parseChannelInfo(channel, message, InterceptType.PLATFORM_MESSAGE);
_parseResult(info, message);
return handler?.call(message);
}
origin.setMessageHandler(channel, wrapper)
}
...
}
通过以上的步骤,其实我们已经可以很简单的对FlutterChannel进行了捕获,比如获取数据量的大小,执行需要的时间,FlutterChannel的方向等数据。
如下是一些测试打印日志
yaml
flutter: 通道数据:{channelName: flutter/platform, timeCost: 1, inputSize: 75, outputSize: 6, interceptType: 0, identifyType: 0, arguments: HapticFeedbackType.lightImpact, methodName: HapticFeedback.vibrate}
flutter: 通道数据:{channelName: dev.flutter.pigeon.PathProviderApi.getDirectoryPath, timeCost: 1, inputSize: 7, outputSize: 95, interceptType: 0, identifyType: 1, arguments: null, methodName: null}
flutter: 通道数据:{channelName: leaf.com/testChannel/json, timeCost: 0, inputSize: 63, outputSize: 63, interceptType: 1, identifyType: 1, arguments: {"key1":"value1","key3":"value3","key2":"value2"}, methodName: null}
4.3 源码
其实主要的源码已经说明,剩下的是解析和统计,点击获取。