Flutter—— 一个有意思的工具:行为录制器 behavior_recorder

3,748 阅读3分钟

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

前言

此工具提供用户行为(手势文字输入)的录制及回放,同时支持记录的导出和导入。

例如,可以由测试人员进行bug复现步骤的录制及上传,并由对应开发人员下载导出再重放。当然也可以用于线上用户的反馈。

Tip:

1.此工具目前支持 flutter 2.5.3-null-safety,

->如果是其他版本的flutter可以查看Beike_AspectD是否支持,或者自行fork进行相关适配即可

2.动图可能有点儿大。

行为的录制和回放

行为录制和回放

行为记录的导出、导入及回放

录制的导出和导入以及回放.gif

实现

两种行为的录制都是基于hook实现的, 这里做一下简单介绍。

手势录制及回放

录制

手势的录制通过hook GestureBinding-handlePointerEvent方法,对平台端所传递过来的pointer event进行转换并生成PointerEventBundle 存储在队列中。

void _hookHandlePointerEvent(PointCut pointCut) {
  gestureRecorder.handleHook(pointCut);
  pointCut.proceed();
}
class GestureRecorder extends Recorder<PointerEventBundle>{

  ///...其它代码

  @override
  void handleHook(PointCut pointCut) {
    if(pointCut.positionalParams.isEmpty) {
      return;
    }
    startTime ??= timeStamp;
    final event = pointCut.positionalParams.first;
    _cacheBucket.add(event);
    if(event is PointerUpEvent || event is PointerCancelEvent) {
      final int endTime = DateTime.now().millisecondsSinceEpoch;
      enqueue(PointerEventBundle.load(startTime!, endTime, type, Queue.from(_cacheBucket)));
      startTime = null;
      _cacheBucket.clear();
    }
  }

  @override
  SourceType get type => SourceType.gesture;

}

回放

PointerEventBundle内部存储原始的pointer event(队列形式),只需将它们按序传递给GestureBindinghandlePointerEvent方法即可实现回放。

@override
Future perform() async {
  while(_eventQueue.isNotEmpty) {
    await WidgetsBinding.instance?.endOfFrame;
    GestureBinding.instance?.handlePointerEvent(_eventQueue.removeFirst());
  }
  return Future.value();
}

文字输入的录制和回放

录制

文字输入的录制是通过hookTextInput_handleTextInputInvocation方法,对平台端所传递的文字输入事件进行转换,并生成TextInputEventBundle存储在队列中。

class TextInputRecorder extends Recorder<TextInputEventBundle> {

  ///...其它代码

  @override
  void handleHook(PointCut pointCut) {
    if(pointCut.positionalParams.isEmpty) {
      return;
    }
    final int startTime = DateTime.now().millisecondsSinceEpoch;
    final methodCall = pointCut.positionalParams.first;
    if(methodCall is MethodCall && methodCall.arguments is List) {
      final list = methodCall.arguments as List;
      if(list.isNotEmpty) {
        id = list.first;
      }
    }
    final int endTime = DateTime.now().millisecondsSinceEpoch;
    enqueue(TextInputEventBundle(startTime, endTime, type, methodCall));
  }



}

回放

文字输入事件的回放与上面大致相同:

1. 首先我们将`TextInputEventBundle`内部存储的事件对象,
   通过SystemChannels.textInput.codec编码器转化为 bytedata
2. 之后,将bytedata交给ServicesBinding.instance?
                      .defaultBinaryMessenger
                      .handlePlatformMessage方法即可
@override
Future perform() async {
  _updateRecord();
  final bytedata = SystemChannels.textInput.codec.encodeMethodCall(methodCall);
  ServicesBinding.instance?.defaultBinaryMessenger
      .handlePlatformMessage(SystemChannels.textInput.name, bytedata, null);
  return Future.value();
}

功能介绍

首先添加 import 'package:behavior_recorder_of_kit/behavior_recorder_of_kit.dart';main.dart文件。

录制和播放

通过 RecordPlayer().startRecord()开始录制,以及RecordPlayer().finishRecord()结束录制。

录制结束后,通过 RecordPlayer().play(); 进行重播。

Tip: 所录制的行为事件是一次性的,即:在播放后将会失效。 
     如果需要重复播放,可以考虑通过RecordPlayer().exportTape()将事件导出。
     

记录的导出和导入

如果需要将录制的记录导出,可以通过 RecordPlayer().exportTape()方法。

Tip: 导出后的序列化方式需要自行定义。

当我们需要从外部导入行为记录时,可以通过 RecordPlayer().loadRecords(); 方法:

                  //导入录制前,要开启录制功能
                  RecordPlayer().startRecord();
                  while(cache.isNotEmpty) {
                    RecordPlayer().loadRecords(cache.removeFirst());
                  }
                  //结束导入后,建议关闭录制。
                  RecordPlayer().finishRecord();
                  
                  //之后通过下方法即可播放记录。
                  RecordPlayer().play()

其它用法,可以见项目的Readme, 谢谢阅读。

项目地址:

behavior_recorder

其它文章

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

【翻译】识别&处理Android构建时的内存问题

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

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

Flutter 仿同花顺自选股列表