掌握聚合最新动态了解行业最新趋势
API接口,开发服务,免费咨询服务

App 可视化埋点技术实践精要

背景

目前统计已经是一个产品常见的需求,尤其在业务模式探索的前期,和项目成熟后期,埋点功能更是必不可少的功能,下面将介绍最简单的App全埋点方案!

什么是数据埋点

数据埋点是一般项目采用统计UV,PV,Action,Time等一系列的数据信息,对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。

为什么要数据埋点

产品或运营分析人员,基于埋点数据分析需要,对用户行为的每一个事件进行埋点布置,并通过SDK上报埋点的数据结果,进行分析,并进一步优化产品或指导运营。

数据埋点包括哪些

这里有我之前写的一篇文章App优质精准的用户行为统计和日志打捞方案

地址:http://blog.csdn.net/sk719887916/article/details/50931485

App打造自定义的统计SDK, 是时候和友盟说分手了

  1. 具体要采集的数据有哪些?

  2. 上报策略场景如何?

读者可直接移步上面的文章。

数据埋点采集模式

自动埋点

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,实现事件行为可调用:

TcStatInterface.initEvent(path.viewTree);。

这个Path就是view的路径。 页面的深度路径,包括打开和关闭sdk在SkyMonitoring中都已能自动获取。

本次demo是id生成规则是按照 :包名+ Activity+ Viewgroup+ Layout+ View + View index + ViewID实现的。

业务直接去继承TamicActivity即可,就能去实现所有可视化View的埋点功能。

代码如下:

public abstract class TamicActivity extends AppCompatActivity {
 
   private int statusBarHeight;
   View rootView;
   String rootViewTree;
   String bigDataPrefix;

   String bigDataIngorePrefix;
   String bigDataEventPrefix;
   private String TAG  = "Tamic";

   @Override
   public void onAttachedToWindow() {
       super.onAttachedToWindow();
       //获取到根节点的view
       rootView = getWindow().getDecorView();
       //控件在视图树上的根路径
       rootViewTree = getPackageName() + "." + getClass().getSimpleName();
       //前缀名 bigData
       bigDataPrefix = "Tamic_test"
;
       //前缀名 bigData_
       bigDataIngorePrefix = bigDataPrefix + "";
       //前缀名 bigdata_ignore
       bigDataEventPrefix =  bigDataIngorePrefix +"Igmore";
   }

   @Override
   protected void onResume() {
       super.onResume();

       TcStatInterface.recordPageStart(TamicActivity.this);
   }


   @Override
   protected void onPause() {
       super.onPause();

       TcStatInterface.recordPageEnd();
   }


   @Override
   protected void onDestroy() {
       super.onDestroy();
       // APP退出
       TcStatInterface.recordAppEnd();
   }



   @Override
   public boolean dispatchTouchEvent(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);
           }
       }
       return super.dispatchTouchEvent(ev);
   }

   private ViewPath findClickView(MotionEvent ev) {
       Log.e(TAG, "bigdata-->findClickView");
       ViewPath clickView = new ViewPath(rootView, rootViewTree);
       return searchClickView(clickView, ev, 0);
   }


   private ViewPath searchClickView(ViewPath myView, MotionEvent event, int index) {
       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)) {

                   return null;
               } else if (tag.startsWith(bigDataPrefix)) {
                   if (tag.startsWith(bigDataEventPrefix)) {
                       myView.specifyTag = tag.replace(bigDataEventPrefix, "");
                   }
                   return myView;
               }
           }
           if (view instanceof ViewGroup) {   
               if (view instanceof AbsListView) {
                   Log.i(TAG, "bigdata-->AbsListView ");
                   return null;
               }

               ViewGroup group = (ViewGroup) view;
               int childCount = group.getChildCount();
               if (childCount == 0) {
                   return myView;
               }
               for (int i = childCount - 1; i >= 0; i--) {
                   myView.view = group.getChildAt(i);
                   clickView = searchClickView(myView, event, i);

                   if (clickView != null) {
                       return clickView;
                   }
               }
           } else {
               clickView = myView;
           }
       }
       return clickView;
   }


   private boolean isInView(View view, MotionEvent event) {
       if (view == null || view.getVisibility() != View.VISIBLE) {
           return false;
       }
       int clickX = (int) event.getRawX();

       int clickY = (int) event.getRawY();
       int[] location = new int[2];
       view.getLocationOnScreen(location);
       int x = location[0];
       int y = location[1];
       int width = view.getWidth();
       int height = view.getHeight();
       return clickX &g

声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com

  • 个人/企业涉诉查询

    通过企业关键词查询企业涉松详情,如裁判文书、开庭公告、执行公告、失信公告、案件流程等等。

    通过企业关键词查询企业涉松详情,如裁判文书、开庭公告、执行公告、失信公告、案件流程等等。

  • 账号黑产风险识别

    根据手机号来查询是否命中黑产风险

    根据手机号来查询是否命中黑产风险

  • IP反查域名

    IP反查域名是通过IP查询相关联的域名信息的功能,它提供IP地址历史上绑定过的域名信息。

    IP反查域名是通过IP查询相关联的域名信息的功能,它提供IP地址历史上绑定过的域名信息。

  • 人脸卫士

    结合权威身份认证的精准人脸风险查询服务,提升人脸应用及身份认证生态的安全性。人脸风险情报库,覆盖范围广、准确性高,数据权威可靠。

    结合权威身份认证的精准人脸风险查询服务,提升人脸应用及身份认证生态的安全性。人脸风险情报库,覆盖范围广、准确性高,数据权威可靠。

  • 全国城市空气质量

    全国城市和站点空气质量查询,污染物浓度及空气质量分指数、空气质量指数、首要污染物及空气质量级别、健康指引及建议采取的措施等。

    全国城市和站点空气质量查询,污染物浓度及空气质量分指数、空气质量指数、首要污染物及空气质量级别、健康指引及建议采取的措施等。

0512-88869195
数 据 驱 动 未 来
Data Drives The Future