Android可视化埋点(无埋点)的具体实现

1.现有的几种埋点技术的实现原理和优劣分析

(1)代码埋点:将收集数据的代码直接写在需要的地方,当用户点击某个控件或者打开某个页面时调用到该部分代码完成数据的收集。 优势:准确性高,收集数据和发送数据都能精确控制,同时能方便的设置自定义属性,自定义控件,自定义View等。 劣势:埋点工作量大,更新代价大。 (2)可视化埋点:根据配置文件收集用户行为,从而获取数据进行分析。 优势:无须手动埋点,配置文件可动态更新。 劣势:配置文件的配置比较耗时,弹出框,隐藏控件等行为不能收集。收集的数据比较简单,只能收集用户行为,不能收集到与行为相关的具体数据。 (3)无埋点:与可视化埋点基本一致。不同点在于可视化埋点是根据配置文件收集数据,无埋点是预先收集所有的用户行为,然后根据配置文件来提取数据。无埋点可以通过修改配置文件追溯之前的用户行为数据。 (4)后端埋点:Sensors Analytics 这个平台有解决方案,优点是能收集到详细的与行为相关的数据,适用于电商等大平台。比如用户选择了一件商品,点击了加入购物车,那么可以收集到用户信息,商品信息,商品价格,商品库存,卖家等诸多信息。 埋点技术的选择

(1)代码埋点:既可以自己与后台定义接口,也可以使用第三方,常用的有友盟,百度统计等。 (2)可视化埋点和无埋点:移动端可以自己实现数据采集(下面有Android端的实现原理和demo)。第三方有诸葛IO,GrowingIO 。在知乎上查了关于这两个平台的信息,GrowingIO隐藏收费,官网并没有说到收费,但是使用15天后发邮件通知收费并停止数据采集和分析。诸葛IO免费模式的数据量是每月200万条,还有其他收费模式。 (3)后端埋点:Sensors Analytics 总结:根据当前公司产品特点和对埋点的要求,建议用可视化埋点;虽然诸葛IO的免费数据量对目前公司App使用规模来说够用,但是一方面以后数据量会越来越大,另一方面用户数据会被第三方掌握;同时在实现上面没有技术难点,所以,建议自己实现。 附录:Android实现可视化埋点技术

原理解析: (1)页面跳转:Activity的生命周期,创建BaseActivity基类,实现对Activity生命周期的监听。 (2)控件的点击:根据UI布局的特性和Android点击事件传递机制实现。让创建的BaseActivity基类重写Activity的dispatchTouchEvent方法,当touch button时,可以获取到按下(DOWN)和抬起(UP)时产生的MotionEvent对象。这个MotionEvent对象有两个方 法,getRawX()和getRawY(),通过这两个方法我们可以获取到“点击位置”在界面中的坐标。然后搜索所有的子View或者控件的布局区域是否包含“点击位置”,从而来判断哪个View或控件被点击。 难点:如何标识点击的控件,这里我们用该控件实例化所在的类名和该控件的UI路径来做唯一标识。 下面实现一个具体Demo:

(1)先写一个简单的登陆界面:有两个EditText分别输入用户名和密码,一个登陆Button,一个去注册的TextView。

<?xml version=”1.0” encoding=”utf-8”?>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

(2)然后模拟埋点数据,实际中就是我们放在服务器的埋点文件,通过动态修改这个文件实现动态埋点的效果。

@Override public void onWindowFocusChanged (boolean hasFocus){ super.onWindowFocusChanged(hasFocus);

    if(allView != null)
        allView.clear();
    allView = getView((ViewGroup) rootView);
    for(int i=0;i<allView.size();i++) {
        Log.v("out", allView.get(i).toString());
        viewPath.add(mClassName+"."+allView.get(i).toString().split("\\{")[0]);
    }
    Log.v("out" , allView.size()+"");
    Rect outRect = new Rect();
    this.getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect);
    statusBarHeight = outRect.top;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

遍历布局文件

/*获取所有View和没有子View的ViewGroup*/
public ArrayList<View> getView(ViewGroup viewGroup){
    if(views == null)
        views = new ArrayList<View>();
    if(viewGroup == null) return null;
    //views.add(viewGroup);
    int count = viewGroup.getChildCount();
    for(int i=0;i<count;i++){
        if(!(viewGroup.getChildAt(i) instanceof ViewGroup)){
            views.add(viewGroup.getChildAt(i));
        }else this.getView(viewGroup);
    }
    return views;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

点击时的处理

/重写dispatchTouchEvent/ public boolean dispatchTouchEvent(MotionEvent ev) { if(ev.getAction() == MotionEvent.ACTION_UP){ /获取当前点击位置,遍历布局,获取当前点击位置对应的view,根据view映射路径,与json文件中的对比/ double x = ev.getRawX(); double y = ev.getRawY() - statusBarHeight; if(allView2 != null) allView2.clear(); allView2 = getView((ViewGroup) rootView); for(int i=0;i<allView2.size();i++) { /获取点击位置的view/ int left = allView2.get(i).getLeft(); int right = allView2.get(i).getRight(); int top = allView2.get(i).getTop(); int bottom = allView2.get(i).getBottom(); if(x > left && x < right && y > top && y < bottom){ /判断这个view是否是我们要埋点的/ String s = mClassName+”.”+allView2.get(i).toString().split(“\{“)[0]; if(viewPath.contains(s)){ Log.v(“out” , “这是我们的埋点:”+s); } } } } return super.dispatchTouchEvent(ev); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

运行结果: enter description here

总结:根据无埋点原理初步实现了,目前还有几个问题: (1)每个View都必须有唯一标识,当前采用的是用view的路径,但是这样当布局文件层级比较复杂的时候,获取路径还有问题。 (2)其二,当布局有margin,标题栏等情况时需要额外考虑。 将以上两个问题完善后可以在实际应用中使用,后续做这部分工作。

Table of Contents