因为工做调整的缘由,后面可能将再也不接触flutter开发,本着技术共享不埋没的原则,开源一下flutter无痕埋点的技术方案,此方案完成于半年前,应该在闲鱼的无痕埋点方案开源前,与闲鱼的方案不太同样,你们有什么建议能够普遍留言。另外特别感谢永葵同窗在此技术中的参与和共同努力。app
visitChildElements(ElementVisitor visitor)
的方法,也就是说,经过这个方法,咱们能够遍历指定element下全部的elements,而后经过element拿到他所对应的widget。NavigatorObserver
确实就能够监听到页面的push和pop,可是官方的这个类提供的方法并不能拿到具体跳转页面的信息(静态页面除外)。那么是否是能够当监听到页面push和pop时,直接遍历element树拿到widget信息呢?实践证实,当监听到push的同时去遍历会报错,由于这个时候页面还正在渲染,flutter的元素正在生成,因此遍历会有问题。因此须要监听页面的渲染完成的时机。void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
复制代码
而后咱们再观察下WidgetsFlutterBinding
这个类,框架
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding 复制代码
在这个类里作了很是多的绑定,渲染的绑定,手势的绑定等等。 SchedulerBinding
这是一个调度器,调度任务的安排。 在SchedulerBinding
中有一个控制渲染的方法函数
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
assert(() {
if (debugPrintEndFrameBanner)
debugPrint('▀' * _debugBanner.length);
_debugBanner = null;
return true;
}());
_currentFrameTimeStamp = null;
}
}
复制代码
根据源码咱们能够看到当布局完成后会调用已经注册的回调postFrameCallbacks
,官方很友好的开放了添加回调的方法。布局
void addPostFrameCallback(FrameCallback callback) {
_postFrameCallbacks.add(callback);
}
复制代码
因此咱们就能够当监听到导航栏路由变化时,而且监听到布局渲染后去遍历element树,拿到咱们想要的widget,去获取widget上面的信息。post
TapGestureRecognizer
中有一个以下方法。void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown(pointer);
_wonArenaForPrimaryPointer = true;
_checkUp();
}
}
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
if (pointer == primaryPointer) {
// Another gesture won the arena.
assert(state != GestureRecognizerState.possible);
if (_sentTapDown)
_checkCancel('forced ');
_reset();
}
}
复制代码
上面的方法是手势的拒绝和添加。从源码中咱们能够看出当另外一个手势从竞技场胜出时,不会执行手势的成功回调,而是会执行手势的取消回调。那么咱们是否是能够添加一个全局的手势,而后重写它的拒绝方法,让它内部执行手势的接受方法呢,这样咱们本身添加的全局手势也能执行成功回调了?实践见证确实能够这样作。ui
把Ink到Scroffld的中间路径为做为一个点击位的标示,固然中间能够过滤一些不须要的widget,否则路径会很长。另外能够获取按钮或者子控件的一些其余信息,如title和Image name。spa
无痕埋点的方案基本上是上面的这些,可是中间咱们仍是一路踩坑,细节上有些须要特殊处理。debug