Flutter——平台通信记录器 : channel_observer

1,296 阅读3分钟

我正在参加跨端技术专题征文活动,详情查看:juejin.cn/post/710123…

前言

Flutter自身的定位,决定了基于其开发的项目在不断迭代的过程中,会有越来越多的平台通信。这些通信多来自各种平台端的sdk,而这些sdk一般是由不同人、团队甚至公司负责的,所以在sdk变动过程中,可能由于沟通不够及时、或者疏忽大意而未能及时通知到客户端。

例如,某个字段类型由int变为string,如果这个字段涉及到核心业务线那么可能会在测试中及时发现,而如果是在非核心业务线则不一定能及时发现。 这种错误,在抵达flutter侧时多为TypeCast Error。 初期, 我们的APM 会将此类错误进行上报,但是由于platform channel众多,很难确定是由哪个channel引起的,为此我们增加了channel observer用于记录最近n条的平台通信记录。 当APM再次上报类似错误后,会导出channel记录一同上报,藉此便可排查出bug点。

下面我简单的介绍一下具体原理与实现。

原理与实现

Flutter-平台通信简介

Flutter与平台端的通信连接层位于ServicesBinding中,其主要负责监听平台信息(系统/自定义)并将其转到defaultBinaryMessenger中处理,其内部初始化方法:

mixin ServicesBinding on BindingBase, SchedulerBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    _defaultBinaryMessenger = createBinaryMessenger();
    
    //...无关代码
  }
  
    @protected
    BinaryMessenger createBinaryMessenger() {
      return const _DefaultBinaryMessenger._();
    }
}

通过createBinaryMessenger 方法,创建了一个_DefaultBinaryMessenger对象,Flutter平台端通信都由此类来负责,其内部实现如下:

class _DefaultBinaryMessenger extends BinaryMessenger {
  const _DefaultBinaryMessenger._();

  ///当我们在调用 xxxChannel.invokeMethod()方法时,最终会调用到send()方法,
  @override
  Future<ByteData?> send(String channel, ByteData? message) {
        final Completer<ByteData?> completer = Completer<ByteData?>();
        
        ///channel : 通道名
        ///message :  你的参数
        ///通过engine中转到平台端
        ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply){
          try {
            ///reply : 平台端返回的结果
            completer.complete(reply);
          } catch (exception, stack) {
            FlutterError.reportError(FlutterErrorDetails(
              exception: exception,
              stack: stack,
              library: 'services library',
              context: ErrorDescription('during a platform message response callback'),
            ));
          }
        });
        return completer.future;
  }

  ///此方法与上面的 send 方法相对应,是服务于平台端调用flutter的方法。
  ///
  ///当我们通过方法 : 
  ///  channel.setMethodCallHandler(xxHandler) 
  ///在flutter侧对 channel绑定一个回调用于处理平台端的调用时,
  ///最终会转到此方法。
  ///
  ///通过channelBuffers,会记录下你的channel name以及对应的handler,
  ///当平台端调用flutter方法时,会查找对应channel的handler并执行。
  @override
  void setMessageHandler(String channel, MessageHandler? handler) {
       if (handler == null) {
          ui.channelBuffers.clearListener(channel);
        } else {
          ui.channelBuffers.setListener(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
            ByteData? response;
            try {
              response = await handler(data);
            } catch (exception, stack) {
              FlutterError.reportError(FlutterErrorDetails(
                exception: exception,
                stack: stack,
                library: 'services library',
                context: ErrorDescription('during a platform message callback'),
              ));
            } finally {
              callback(response);
            }
          });
        }
  }
}

通过上面的了解我们便知道了入手点:只需增加一个_DefaultBinaryMessenger的代理类即可。

实现

首先,我们需要自定义WidgetsFlutterBinding以混入我们自定义的ServicesBinding :

class ChannelObserverBinding extends WidgetsFlutterBinding with ChannelObserverServicesBinding{
  static WidgetsBinding ensureInitialized() {
    if(WidgetsBinding.instance == null) {
      ChannelObserverBinding();
    }
    return WidgetsBinding.instance!;
  }
}

随后我们在自定义的ServicesBinding中,添加我们的代理类BinaryMessengerProxy

mixin ChannelObserverServicesBinding on BindingBase, ServicesBinding{

  late BinaryMessengerProxy _proxy;

  @override
  BinaryMessenger createBinaryMessenger() {
    _proxy = BinaryMessengerProxy(super.createBinaryMessenger());
    return _proxy;
  }
}

这样我们就可以在代理类中,对平台通信进行记录了:

class BinaryMessengerProxy extends BinaryMessenger{

  BinaryMessengerProxy(this.origin);

  ///....省略代码

  @override
  Future<void> handlePlatformMessage(String channel, ByteData? data, PlatformMessageResponseCallback? callback) {
    return origin.handlePlatformMessage(channel, data, callback);
  }

   ///这里我们对flutter的调用做记录
  @override
  Future<ByteData?>? send(String channel, ByteData? message) async {
    //记录channel通信
    final ChannelModel model = _recordChannel(channel, message, true);
    if(model.isAbnormal) {
      return origin.send(channel, message);
    }
    final ByteData? result = await origin.send(channel, message);
    _resolveResult(model, result);
    return result;
  }

  ///这里我们可以对平台端的调用做记录
  /// * 对MessageHandler增加一个代理即可。
  @override
  void setMessageHandler(String channel, MessageHandler? handler) {
    origin.setMessageHandler(channel, handler);
  }

}

效果图

当我们捕捉到TypeCast error时,就可以将异常堆栈及channel的通信记录一同上传。开发同学便可借助堆栈信息和调用记录,定位到具体的异常channel

S20504-1311263820225251111561.gif

其他

项目地址

channel_observer_of_kit

其他文章

Flutter插件——简洁实用的图片编辑器 - 掘金 (juejin.cn)

Flutter&Flame在游戏上的实践——坦克大战

Flutter——原生View的Touch事件分发流程

Flutter在Android平台上启动时,Native层做了什么?

Flutter 仿同花顺自选股列表