Sensors Analytics可视化埋点代码阅读笔记
来源:互联网 发布:待遇 知乎 编辑:程序博客网 时间:2024/05/18 17:43
这是一个代码阅读笔记,而不是实现分析,想要深入学习亲自阅读源码才是最好的。
Sensors Analytics是一款sdk端开源的统计工具,并在各语言各平台上有相应的SDK。本文学习的是Android版本。由于对可视化埋点的实现感兴趣,于是写个笔记记录下阅读过程。
可视化埋点
功能包括两部分:
- 非代码埋点 允许手机连接应用后台管理界面,通过可视化操作设置埋点。
- 无需更新 服务端配置动态配置埋点位置,可配置的埋点必须是view的特定事件,例如click。
主要类
- ViewCrawler 可视化埋点门面封装,其
setEventBindings()
用于设置埋点配置。 - EditState 根据应用的生命周期管理埋点配置。
ViewCrawler
持有一个EditState
实例。 - ViewVisitor 用于遍历ViewTree。其内部有一个
Pathfinder
委托处理遍历逻辑,并以List<Pathfinder.PathElement>
形式存储查找路径。 - EditBinding 将埋点配置与
ViewVisitor
绑定。其自身会监听onGlobalLayout
并调用ViewVisitor.visit()
开始遍历。 - Pathfinder 封装真正的ViewTree遍历过程。
初始化
应用启动时加载本地缓存埋点配置,同时向服务端获取埋点配置。无论本地配置还是服务端配置,成功读取后,最终以Map<Activity, Set<EditBinding>>
形式保存在EditState
。
同时,启动时加载应用的R.id.class,通过反射获取id名和id的对应关系。这使得服务端可以通过id名而不是id配置埋点,提高了可读性。
获取服务端配置的埋点
埋点数据格式
path数据格式
运行过程
EditBinding
自身是OnGlobalLayoutListener
,实例化时将自身添加到ViewTree观察者:
public EditBinding(View viewRoot, ViewVisitor edit, Handler uiThreadHandler) { //... final ViewTreeObserver observer = viewRoot.getViewTreeObserver(); if (observer.isAlive()) { observer.addOnGlobalLayoutListener(this); } run();}
在onGlobalLayout()
中第一次遍历ViewTree,并且此后每隔5秒遍历ViewTree,寻找埋点View:
if (!mAlive) { return;}final View viewRoot = mViewRoot.get();if (null == viewRoot || mDying) { cleanUp(); return;}// ELSE View is alive and we are alivemEdit.visit(viewRoot);mHandler.removeCallbacks(this);mHandler.postDelayed(this, 5000);
mEdit.visit(viewRoot);
即为遍历过程,最终调用自身的findTargetsInMatchedView()
:
private void findTargetsInMatchedView(View alreadyMatched, List<PathElement> remainingPath, Accumulator accumulator) { if (remainingPath.isEmpty()) { // 已经匹配了View accumulator.accumulate(alreadyMatched); return; } //...嵌套匹配逻辑}
findTargetsInMatchedView()
根据id、id名(如果有的话)、index、prefix查找View。
index的官方解释:
The index attribute, counting from root to leaf, and first child to last child, selects a particular matching view amongst all possible matches. Indexing starts at zero, like an array
index. So E.index == 2 means “Select the third possible match for this element”.
prefix的官方解释:
The prefix attribute refers to the position of the matched views in the hierarchy, relative to the current position of the path being searched.
prefix有两种值,影响View遍历顺序:ZERO_LENGTH_PREFIX
和SHORTEST_PREFIX
。其中SHORTEST_PREFIX
允许深度优先遍历。
以上引用的代码中Accumulator.accumulate()
用于处理遍历结果。由于ViewVisitor
继承于Accumulator
,所以最后alreadyMatched交给ViewVisitor
自身处理。accumulate()
的实现与该ViewVisitor
监听的事件有关。例如,如果监听onClick,则对view设置代理监听:
@Overridepublic void accumulate(View found) { final View.AccessibilityDelegate realDelegate = getOldDelegate(found); if (realDelegate instanceof TrackingAccessibilityDelegate) { final TrackingAccessibilityDelegate currentTracker = (TrackingAccessibilityDelegate) realDelegate; if (currentTracker.willFireEvent(getEventName())) { return; // Don't double track } } if (SensorsDataAPI.ENABLE_LOG) { Log.i(LOGTAG, String.format("ClickVisitor accumulated. View %s", found.toString())); } // We aren't already in the tracking call chain of the view final TrackingAccessibilityDelegate newDelegate = new TrackingAccessibilityDelegate(realDelegate); found.setAccessibilityDelegate(newDelegate); mWatching.put(found, newDelegate);}
监听到事件后,在DynamicEventTracker
中处理事件。如果是click这样的单次事件,则立即发送报告;如果是edited事件,则缓存,延迟3秒发送。需要缓存的事件取eventName、triggerId、view三者hashCode的混合值作为唯一标识。后续异步更新这个事件时,通过唯一标识在缓存中查找。
@Overridepublic void OnEvent(View v, EventInfo eventInfo, boolean debounce) { final long moment = System.currentTimeMillis(); final JSONObject properties = new JSONObject(); try { properties.put("$from_vtrack", String.valueOf(eventInfo.mTriggerId)); properties.put("$binding_trigger_id", eventInfo.mTriggerId); properties.put("$binding_path", eventInfo.mPath); properties.put("$binding_depolyed", eventInfo.mIsDeployed); } catch (JSONException e) { Log.e(LOGTAG, "Can't format properties from view due to JSON issue", e); } // 对于Clicked事件,事件发生时即调用track记录事件;对于Edited事件,由于多次Edit时会触发多次Edited, // 所以我们增加一个计时器,延迟发送Edited事件 if (debounce) { final Signature eventSignature = new Signature(v, eventInfo); final UnsentEvent event = new UnsentEvent(eventInfo, properties, moment); synchronized (mDebouncedEvents) { final boolean needsRestart = mDebouncedEvents.isEmpty(); mDebouncedEvents.put(eventSignature, event); if (needsRestart) { mHandler.postDelayed(mTask, DEBOUNCE_TIME_MILLIS); } } } else { try { SensorsDataAPI.sharedInstance(mContext).track(eventInfo.mEventName, properties); } catch (InvalidDataException e) { Log.w("Unexpected exception", e); } }}
可视化埋点连接
通过enableEditingVTrack()
开启可视化埋点。
实现原理:监听ActivityLifecycleCallbacks
,每次在onResume()
中连接服务端,即握手过程。握手成功后,响应服务端需要的数据:
@Overridepublic void onMessage(String message) { try { final JSONObject messageJson = new JSONObject(message); final String type = messageJson.getString("type"); if (type.equals("device_info_request")) { mService.sendDeviceInfo(messageJson); } else if (type.equals("snapshot_request")) { mService.sendSnapshot(messageJson); } else if (type.equals("event_binding_request")) { mService.bindEvents(messageJson); } else if (type.equals("disconnect")) { mService.disconnect(); } } catch (final JSONException e) { Log.e(LOGTAG, "Bad JSON received:" + message, e); }}
潜在问题
代码不灵活
大量socket封装用于实现可视化埋点,而一旦用户不需要这个功能,这些代码显得冗余。
数据库操作
这种统计SDK一般都会将信息存在本地数据库,并在合适的时机上传。然而本SDK的数据库实现过于简单,数据库操作不基于事务,容错差。
- Sensors Analytics可视化埋点代码阅读笔记
- Tango 点云 demo 代码阅读笔记
- 阅读代码点
- android Sensors学习笔记
- 数据采集与埋点简介之 代码埋点、可视化埋点与无痕埋点
- 《软件需求与可视化模型》阅读笔记
- Sensors
- Sensors
- OpenOBEX代码阅读笔记
- 《代码大全》阅读笔记
- OpenOBEX代码阅读笔记
- larbin代码阅读笔记
- OpenOBEX代码阅读笔记
- 代码整洁阅读笔记
- live_media_代码阅读笔记
- 代码阅读笔记
- JSVM代码阅读笔记
- 代码阅读笔记-MDnet
- 颜色那点事
- Ambari安装,配置和部署Hadoop集群
- Linux基础——grep匹配命令
- QCoreApplication
- extjs显示格式化返回JSON格式的日期
- Sensors Analytics可视化埋点代码阅读笔记
- 工作小笔记
- 数组
- spring中通过注解来实例化对象
- 图像处理与计算机视觉基础,经典以及最近发展
- 基本 SPARQL 查询
- QQ通信原理及QQ是怎么穿透内网进行通信的?
- 移动浏览器的四大内核
- nodejs新建一个工程环境的搭建