埋点系统设计

埋点系统的设计

  1. 插件化
  2. 回调统计
  3. 支持滚动列表的展示统计
  4. 支持时长统计
  5. 数据批量打包上传
  6. 完善的统计上传支持,中途断网数据不丢

1. 整体流程图

image

接口封装

/**
 * @param event_type  事件类型
 * @param c_id  事件Id(服务器定义)
 * @param md  moudleId
 * @param is_anchor  是否立即上传
 * @param args  附加参数
 */
public static void eventTongji(String event_type, int c_id, int md, int is_anchor, String args) 

2. 插件

2.1 插件的初始化

public boolean initController(Context initContext)

2.2 插件统计入口

埋点数据在主App里面会把数据转成Json,然后以反射的形式调用插件里面UGCLoader的addUgcEvent方法

public void addEventUGC(final Context context, final String eventData)  

然后会把把事件解析成 UGCEvent,根据实际情况需要需要回调Url的话会直接开始回调给第三方统计平台.最后事件会封装成LoadEventUGCRequest:

private static class LoadEventUGCRequest implements LoadRequest {
        public TongjiEvent bean;

        public LoadEventUGCRequest(TongjiEvent bean) {
            this.bean = bean;
        }

        @Override
        public void processRequest(UGCLoader dataLoader) {

        }
    }  

常见的几种LoadRequest

类名 作用
LoadEventUGCRequest 打点事件
StoreUGCRequest 存储事件
UploadEventUGCRequest 强制上传事件

2.3 LoaderThread

事件加入阻塞队列loaderQueue后,负责取出事件,执行其中processEvent

 while (true) {
    try {
            LoadRequest request = queue.take();
            ......
            request.processRequest(loader);
    } catch (InterruptedException e) {
       e.printStackTrace();
    }

}

2.4 数据暂存和上传

数据取出来后悔有再次加入到等待上传队列eventList
当数据达到一定条件后执行上传操作

if (bean.is_anchor == 1 || eventList.size() >= PeacockController.UPLOAD_LIMIT_COUNT) {
                   //满30条上传或is_anchor立即上传不为0
                   UgcUploadManager manager = new UgcUploadManager();
                   ArrayList<TongjiEvent> uploadBeans = new ArrayList<TongjiEvent>();
                   uploadBeans.addAll(eventList);
                   eventList.clear();
                   manager.uploadLogUgc(mContext, uploadBeans);
               }

上传流程

  • 先判断EventLogTable是否有未上传的事件,有则加入到eventList,同时清空EventLogTable
  • 再判断UuidDataCache是否有上传失败的报文,统一放入post参数中
  • 然后把这条即将上传报文插入UuidDataCache

因此最后待上传的报文uploadData包含有:

[   currentUploadEventData,
    faliedUploadEventData_0,
    faliedUploadEventData_1
] 

ps:最后的本地生成的UUID 其实没有作用

  • 加密压缩上传
  • 上传成功后删除UuidDataCache里面对应的UUID报文

3.2 埋点自查

  • 一个系统级的浮层

image

  • mock接口自查
    直接mock这个中间数据解析,统计结果,方便单客户端自查

3.高阶封装

3.1 页面时长统计

在基类的onResume开始计时,在onPause时候停止计时,然后上报,即可统计本次展示时长

3.2 列表展示统计

在ListView Idle时候,递归循环遍历所有的子View,找出所有的TongjiLayout,然后调用其统计方法(统计数据已经预设进去了)

    /**
     * 获取ViewGroup里所有展现的 TongjiLayout(坑位)
     * @param top    统计区间的顶部坐标
     * @param bottom 统计区间的底部坐标
     */
    public synchronized static void viewAllTongjiLayouts(ViewGroup group, int top, int bottom) {
        try {
            if (group == null) {
                return;
            }

        if (group.getVisibility() == View.VISIBLE && group instanceof TongjiLayout) {
            TongjiLayout layout = (TongjiLayout) group;
            layout.tongjiView(top, bottom);
            return;
        }

        int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = group.getChildAt(i);
            if (child.getVisibility() == View.VISIBLE && child instanceof ViewGroup) {
                if (child instanceof TongjiLayout) {
                    TongjiLayout layout = (TongjiLayout) child;
                    layout.tongjiView(top, bottom);
                } else {
                    viewAllTongjiLayouts((ViewGroup) child, top, bottom);
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

3.2.1 PV统计的限制条件

  • 展示1/2之上
/**
 * 检测是否该坑位坐标满足,漏出1/2即满足条件
 */
public boolean checkIsItemLocalRight(int top, int bottom) {
    try {
        int[] localtion = new int[2];
        /**获取该View在屏幕中的位置*/
        getLocationOnScreen(localtion);
        //高和宽都显示大于一半则为true
        return localtion[1] > top - getHeight()/2 && localtion[1] < bottom - getHeight() / 2&& localtion[0] > -getWidth() / 2 && localtion[0] < MidData.main_screenWidth - getWidth() / 2;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}
  • 10s内统计一次
String key = ad_item_id + "#" + md + "#" + pos + "#" + args + "#" + card_id;
if (ETADUtils.getItemTimeMap().containsKey(key)) {
    return;
}

3.2.2 fragment的生命周期统计

参考文章
https://blog.csdn.net/tongcpp/article/details/41978751
https://www.jianshu.com/p/850556d33f63
同时也可以考虑统计View的可见性

参考文档:

Umeng统计设计: https://developer.umeng.com/docs/67953/detail/68140

埋点的设计问题

必须分模块,因为现在的组件都是相互集成,不知道,有可能在别的App集成了这个模块,这时候需要注意埋点参数必须有至少一个subCat表述所在位置

Moudle
SubMoudle
ItemId
EventType
InstantUpload
Properties