目前数据统计已经是一个产品常见的需求趋势,尤其在业务模式探索的前期,或者产品成熟期,埋点功能更是必不可少的功能,下面将介绍最简单的App和前端全埋点方案。后续我(最新没怎么写技术文章,后台被很多人diss了)也会从产品角度全面介绍一个业务如何从0到1实现埋点。包括这个过程中遇到的所有难题。
背景
目前统计已经是一个产品常见的需求,尤其在业务模式探索的前期,和项目成熟后期,埋点功能更是必不可少的功能,下面将介绍最简单的App全埋点方案!
什么是数据埋点
数据埋点是一般项目采用统计UV,PV,Action,Time等一系列的数据信息,对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。
为什么要数据埋点
产品或运营分析人员,基于埋点数据分析需要,对用户行为的每一个事件进行埋点布置,并通过SDK上报埋点的数据结果,进行分析,并进一步优化产品或指导运营。
数据埋点包括哪些
这里有我之前写的一篇文章App优质精准的用户行为统计和日志打捞方案
具体要采集的数据有哪些?
上报策略场景如何?
读者可直接移步上面的文章。
数据埋点采集模式
自动埋点
App通过代理,调用Sdk相关API,进行的将数据埋点上报的模式.
无痕埋点
项目无需通过专门提供代理类,直接由sdk提供相关接口,或者通过编译工具,预编译替换代码等,直接由Sdk全部负责采集上报。
可视化埋点
可视化埋点指 前端或者App端基于dom 元素和控件 精准自动埋点并上报的方案。
对比分析:
自动埋点:
缺点:
1 开发人员工作量大,需对业务提供唯一的ID,来区分每一个业务,无论是否提供sdk代理,业务开发人员至少需要多次调用sdk相关API.
2 业务人员和产品沟通成本提高,需要对具体业务制定相关的业务标识,以便于产品分析和统计
优点:
产品运营工作量少,对照业务映射表,就能分析出还原相关业务场景, 数据比较精细,无需大量的加工和处理。
无痕埋点
缺点:
1 sdk开发人员需提供一套无痕埋点技术成品,包括能正确获取PV,UV,Action,Time等多项统计指标。前期技术投入大。
2 数据量大,需后端落地进行大量处理,并由产品进行自我还原业务员场景。 无论采用智能系统平台,还是通过原生的数据库查询数据,都是一种大量的分析精力。
优点:
1 开发人员工作量小,无需对业务标识进行唯一区分,由sdk自动进行生成,ID规则由sdk和产品进行约定。减少业务人员的沟通成本和使用步骤。
2 数据量全面,覆盖面广,产品可按需进行分析。做到毫无遗漏。
3 支持动态页面和局部动效的统计。
可视化埋点
优点:
1 相对数据量而言
相比较于无埋点相而言对较低,但是这个可视化元素的识别和遍历技术是客户端或者前端所要实现的,唯一id生成也无需客户端去自定义规则,这套生成规则由相关产品在自动化工具的情况下生成配置表,下发到客户端,再由客户端按坑就班到相关界面去实现。
2 数据量相对精确
缺点:
1 可视化工具的平台的搭建,静态页面的元素识别都需要额外开发。
2 动态效果可能会遗漏。
实现方案:
埋点需求可参考我之前的写的一篇文章:
App优质精准的用户行为统计和日志打捞方案
App打造自定义的统计SDK
自动埋点实际上也很简单,只是提供一个base类,由业务类继承base类,在base里面做相关统计api调用可参考我的Github:https://github.com/Tamicer/SkyMonitoring
可视化核心实现:
1: SDK技术实现:
2 落地数据后台和前端可视化平台
3 可视化元素页面生成OA工具
前端,移动端,和后端统一路径的打通和唯一用户的识别技术。
以Android作为列子,iOS和前端大致相同:
提供自动遍历元素 并能扑捉点击的控件的Activity和viewControler, 并能在对应的生命周期统计pv的打开和关闭,调用我开源的SkyMonitoring的对应的api.
复写dispatchTouchEvent(MotionEvent ev) 事件函数,确定被点击的view的相关位置,并生成唯一的ID,企业级App都是从服务器下发对应的ID,对应页面去调用埋点sdk Api,实现事件行为可调用:
这个Path就是view的路径。 页面的深度路径,包括打开和关闭sdk在SkyMonitoring中都已能自动获取。
本次demo是id生成规则是按照 :包名+ Activity+ Viewgroup+ Layout+ View + View index + ViewID实现的。
业务直接去继承TamicActivity即可,就能去实现所有可视化View的埋点功能。
代码如下:
publicabstractclassTamicActivityextendsAppCompatActivity{
privateintstatusBarHeight;
View rootView;
String rootViewTree;
String bigDataPrefix;
String bigDataIngorePrefix;
String bigDataEventPrefix;
privateString TAG ="Tamic";
@Override
publicvoidonAttachedToWindow(){
super.onAttachedToWindow();
//获取到根节点的view
rootView = getWindow().getDecorView();
//控件在视图树上的根路径
rootViewTree = getPackageName() +"."+ getClass().getSimpleName();
//前缀名 bigData
bigDataPrefix ="Tamic_test";
//前缀名 bigData_
bigDataIngorePrefix = bigDataPrefix +"";
//前缀名 bigdata_ignore
bigDataEventPrefix = bigDataIngorePrefix +"Igmore";
}
@Override
protectedvoidonResume(){
super.onResume();
TcStatInterface.recordPageStart(TamicActivity.this);
}
@Override
protectedvoidonPause(){
super.onPause();
TcStatInterface.recordPageEnd();
}
@Override
protectedvoidonDestroy(){
super.onDestroy();
// APP退出
TcStatInterface.recordAppEnd();
}
@Override
publicbooleandispatchTouchEvent(MotionEvent ev){
if(ev.getAction() == MotionEvent.ACTION_DOWN){
ViewPath path = findClickView(ev);
if(path !=null) {
Log.e(TAG,"path -->"+ path.viewTree);
//调用sdk事件统计
TcStatInterface.initEvent(path.viewTree);
}
}
returnsuper.dispatchTouchEvent(ev);
}
privateViewPathfindClickView(MotionEvent ev){
Log.e(TAG,"bigdata-->findClickView");
ViewPath clickView =newViewPath(rootView, rootViewTree);
returnsearchClickView(clickView, ev,);
}
privateViewPathsearchClickView(ViewPath myView, MotionEvent event,intindex){
ViewPath clickView =null;
View view = myView.view;
if(isInView(view, event)) {
myView.level++;
if(myView.level ==2&& !"LinearLayout".equals(view.getClass().getSimpleName())) {
myView.filterLevelCount++;
}
if(myView.level > myView.filterLevelCount) {
myView.viewTree = myView.viewTree +"."+ view.getClass().getSimpleName() +"["+ index +"]";
}
Log.i(TAG,"bigdata-->tag = "+ view.getTag());
if(view.getTag() !=null) {
// 主动标记不需要统计时,不进行自动统计
String tag = view.getTag().toString();
if(tag.startsWith(bigDataIngorePrefix)) {
returnnull;
}elseif(tag.startsWith(bigDataPrefix)) {
if(tag.startsWith(bigDataEventPrefix)) {
myView.specifyTag = tag.replace(bigDataEventPrefix,"");
}
returnmyView;
}
}
if(viewinstanceofViewGroup) {
if(viewinstanceofAbsListView) {
Log.i(TAG,"bigdata-->AbsListView ");
returnnull;
}
ViewGroup group = (ViewGroup) view;
intchildCount = group.getChildCount();
if(childCount ==) {
returnmyView;
}
for(inti = childCount -1; i >=; i--) {
myView.view = group.getChildAt(i);
clickView = searchClickView(myView, event, i);
if(clickView !=null) {
returnclickView;
}
}
}else{
clickView = myView;
}
}
returnclickView;
}
privatebooleanisInView(View view, MotionEvent event){
if(view ==nullview.getVisibility() != View.VISIBLE) {
returnfalse;
}
intclickX = (int) event.getRawX();
intclickY = (int) event.getRawY();
int[] location =newint[2];
view.getLocationOnScreen(location);
intx = location[];
inty = location[1];
intwidth = view.getWidth();
intheight = view.getHeight();
returnclickX > x && clickX < (x + width) && clickY > y && clickY < (y + height);
}
}
APP项目集成使用,初始化url和相关统计配置字典,这个字典可以从服务器下发下来,本次我只是通过简单的加载本地文件做实践。
publicclassStatAppliationextendsApplication{
@Override
publicvoidonCreate(){
super.onCreate();
// you app id
intappId =21212;
// assets
String fileName ="my_statconfig.json";
String url ="https://github.com/Tamicer/TamicAppMonitoring";
// init statisSdk
TcStatInterface.initialize(this, appId,"you app chanel", fileName);
TcStatInterface.setUrl(url);
TcStatInterface.setUploadPolicy(TcStatInterface.UploadPolicy.UPLOAD_POLICY_DEVELOPMENT, TcStatInterface.UPLOAD_TIME_ONE);
}
}
可视化也可以通过Aop技术插桩实现,但实现方案对代码的入侵性太高,这里不做介绍。
领取专属 10元无门槛券
私享最新 技术干货