因爲工做調整的緣由,後面可能將再也不接觸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