最近研习了美团等大厂的一些埋点方案。
还要感谢大神《xuhaoranLeo》的指点。(既然大神没空写博客、但我可以代劳哈)。

本文的宗旨是尽量全面、精简、满足我能想到尽量多的埋点需求。

主要通过以下这些方面来谈谈中埋点那些事:

  • 打点/上报的大概流程
  • 日志记录类型
  • 日志应该带有的数据
  • 打点的具体方式
  • 何时上报
  • 具体实现(iOS)

打点/上报的大概流程

  • 打点:当发生需要收集的行为/状态时、将其记录在日记中。
  • 上报:选择合适的时机将日志上报。

日志记录类型

根据业务需要大致可以具有以下类型

  • 页面/产品曝光
  • 用户点击
  • 性能打点(数据库操作效率、APP运行卡顿)
  • 网络监控

日志应该带有的数据

  • 一切分析时用得到的数据例子:
    行为(点击、浏览)、用户(uid)、业务信息(gid、gtype)等。

  • 关键业务的性能监听(其实性能打点我比较推荐单独进行、毕竟这是开发关心的、产品分析并不需要):

  • 网络请求失败率、错误码
  • 数据层操作耗时、App卡顿堆栈

打点的具体方式

代码埋点:具体业务代码处、手动添加埋点代码。比如衡量图片上传、数据解析、OI操作的时间等

  • 声明埋点: 通过将事件标识、业务字段作为属性添加在响应控件上。简化代码埋点的代码量。
  • 无痕埋点:获取全部操作、通过plist文件、决定需要上报的指定操作《美团:Mixpanel》
  • 无埋点:上报所有操作、由服务器筛选《GrowingIO》

具体实现:

由于每个项目的需求不同、具体实现也不一样。
这里只大概理顺思路。

周期内记录:

既然是统一上报、就需要在上报之前将本次周期中所有的指定操作记录下来。

  • 每次操作中。由一个指定的模型(json)进行存储。

  • 而整个周期中。我们采用一个单例、单例中有一个数组对单次模型进行存储。

  /** 数据存储模型*/@interface KTBehaviorData : NSObject@property (nonatomic, strong) NSString *op_type; // 1点击事件 2页面事件 3IO操作@property (nonatomic, strong) NSString *page_code; // 页面Id@property (nonatomic, strong) NSString *event_code; // 事件Id@property (nonatomic, strong) NSDictionary *object_dic; // 内容Id@property (nonatomic, strong) NSString *op_time; // 点击事件操作时间@property (nonatomic, strong) NSString *start_time; // 页面事件开始时间@property (nonatomic, strong) NSString *end_time; // 页面事件结束时间@end@interface KTBehaviorUpLoadData : NSObject@property (nonatomic, strong) NSString *app_type; //@property (nonatomic, strong) NSString *app_version;@property (nonatomic, strong) NSString *os_type; // 1苹果iOS@property (nonatomic, strong) NSString *os_version; // 系统版本@property (nonatomic, strong) NSString *device_id; // 设备id@property (nonatomic, strong) NSString *user_id; // 用户id@property (nonatomic, strong) NSString *login_account; // 用户账号@property (nonatomic, strong) NSString *screen; // 屏幕分辨率...@property (nonatomic, strong) NSMutableArray <KTBehaviorData *>*datas;@end

存储&&上报:

在APP结束时归档存储、APP启动时上传给服务器、上传失败则将归档数据重新写入单例追加。

  • 写入

    @implementation KTBehaviorDataManager
    + (void)load {//杀死程序 (但当程序位于后台呗杀死不执行)__block id observer1 = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {NSLog(@"杀死程序---将数据写入本地");//将数据写入本地[[KTBehaviorDataManager sharedManager] writeBehaviorData];[[NSNotificationCenter defaultCenter] removeObserver:observer1];}];//程序切换至后台__block id observer2 = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {NSLog(@"程序切换至后台---将数据写入本地");//将数据写入本地[[KTBehaviorDataManager sharedManager] writeBehaviorData];[[NSNotificationCenter defaultCenter] removeObserver:observer2];}];
    }
    
  • 上传

  • @implementation KTBehaviorDataUpLoader
    + (void)load {//程序启动、上报记录__block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {[KTBehaviorDataUpLoader upLoadData];[[NSNotificationCenter defaultCenter] removeObserver:observer];}];
    }
    

打点:

打点的方式有很多、但本质上都一样。只是打点的代码书写位置不同而已。

这里有一点需要注意一下:
在将捕获的信息写入manager的时候、记得加上安全保障。因为整个app里有很多地方都将会对manager进行操作、虽然出现资源抢夺的问题不大、但是并不代表永远不会。

 #import "KTBehaviorDataManager.h"- (void)pushKTBehaviorDataWithModel:(KTBehaviorData *)model {//线程锁、保证数据完整性@synchronized(self) {[self.data.datas addObject:model];}}

代码埋点

看着多、但如果你把代码封装一下。就会发现少很多了

  - (void)submitBtnClick {KTBehaviorData *data = [[KTBehaviorData alloc] init];data.op_type = @"2";data.page_code = @"push";data.event_code = @"submitBtnClick";data.object_id = @{@"title":@"xx",@"content":@"xx"};data.op_time = [NSDate getCurrentTimeStamp];[[KTBehaviorDataManager sharedManager] pushKTBehaviorDataWithModel:data];}

当然、你可以把打点的方法抽离一下、更精简一些而不使用Model。不过到了方法内部之后、都一样。

  [[KTBehaviorDataManager sharedManager] pushKTBehaviorDataWithPageId:@"xxx" objectId:@"xxx"];

稍微高级点、一个记录图片上传速度的埋点。

  - (void)upLoadPic {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Do the work in backgroundKTBehaviorData * data = [KTBehaviorData new];data.op_type = @"3";data.page_code = @"ViewController";data.event_code = @"upLoadPic";data.start_time =[KTBehaviorData getNowTimeTimestamp];//图片上传[NSThread sleepForTimeInterval:5];data.end_time = [KTBehaviorData getNowTimeTimestamp];[[KTBehaviorDataManager sharedManager] pushKTBehaviorDataWithModel:data];NSLog(@"页面IO埋点----%@",[data dicValue]);});}

这样、就完成了一次提交按钮被点击的记录。包括时间、控制器、事件、参数等。
只要你的模型结构足够健壮、我们完全可以用一个模型记录APP内的各种事件。

  • 结合刚才说的写入&&上报。大概这样的效果

  • 上报


声明埋点

通过runtime为控件动态添加属性。
然后在创建控件时为属性赋值。

  KTBehaviorData *parameter = [[KTBehaviorData alloc] init];parameter.bid = @"bid";parameter.lab = @{@"poi_id":@"1"};button.kt_clickParams = parameter;

然后在事件发生时进行记录。


无痕埋点

简而言之、有两点。

  • 替换方法:通过swizzle对事件进行hook。
    这里、我提供两种方式。
  • 1、通过类别Hook原生方法:网上最普遍的方式。对event事件、table代理、页面生命周期等方法进行Hook、但是无法直接对业务参数进行捕获。

解决方案可以通过对NSObject扩展出一个打点专用结构体来获取、但是本质上需要污染了业务代码。

  • 2、hook指定Class中的指定方法:然后在指定方法中通过获取class指定属性值的方式捕获参数。这要感谢《xuhaoranLeo》提供的方案。

在下文中我会对两种方式进行说明并且举例。

  • 筛选记录:通过plist文件。通过文件名:pageId、方法名:enevtId等方式、自动为模型参数赋值。

替换方法:

  • 通过类别Hook原生方法

现在还在这个阶段大家对swizzle应用都比较频繁了、没什么必要解释太多。直接贴代码吧

  • 举个例子
    页面进出、停留时间:

    @implementation UIViewController (KTHook)+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{SEL originalSelector1 = @selector(viewWillAppear:);SEL swizzledSelector1 = @selector(kt_viewWillAppear:);[KTHook swizzlingInClass:[self class] originalSelector:originalSelector1 swizzledSelector:swizzledSelector1];SEL originalSelector2 = @selector(viewWillDisappear:);SEL swizzledSelector2 = @selector(kt_viewWillDisappear:);[KTHook swizzlingInClass:[self class] originalSelector:originalSelector2 swizzledSelector:swizzledSelector2];});}
    #pragma mark - Method Swizzling
    - (void)kt_viewWillAppear:(BOOL)animated
    {NSLog(@"进入");[[KTBehaviorDataManager sharedManager]pushKTBehaviorDataWithPageId:NSStringFromClass([self class]) time:[KTBehaviorData getNowTimeTimestamp]];[self kt_viewWillAppear:animated];
    }- (void)kt_viewWillDisappear:(BOOL)animated
    {NSLog(@"离开");[[KTBehaviorDataManager sharedManager]pushKTBehaviorDataWithPageId:NSStringFromClass([self class]) time:[KTBehaviorData getNowTimeTimestamp]];[self kt_viewWillDisappear:animated];
    }
    

页面停留时间

同理、我们通过对UIControl的Event事件、UITableView代理等进行hook、进行无痕埋点。
具体方式网上有很多、千篇一律。我就不写了、因为不符合我想要获取页面参数的需求、贴出两个教学帖想要这么实现的可以自取。
《iOS 打点方案设计》、《iOS动态性(二)可复用而且高度解耦的用户统计埋点实现》

  • hook指定Class中的指定方法

思路就是上面写的。实现的代码也不难、hook过SDK文件的童鞋应该都知道。这里为了方便、我们用了一个封装好的工具。《Aspects》

  @implementation NSObject (KTAspectsHook)+ (void)load {__block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {[self setupBehaviorObj];[[NSNotificationCenter defaultCenter] removeObserver:observer];}];}#pragma mark - private method//hook所有需要打点的对象方法- (void)setupBehaviorObj {Class clazz = NSClassFromString(@"ViewController");//具体事件方法SEL selector = NSSelectorFromString(@"upLoadPic");[clazz aspect_hookSelector:selector withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo) {NSLog(@"ViewController中upLoadPic方法被调用、参数:aaa==%@",[[aspectInfo instance] valueForKey:@"aaa"]);} error:NULL];}@end

打印:

2018-01-24 14:43:21.419519+0800 kTBehaviorDemo[4587:363679] ViewController中upLoadPic方法被调用、参数:aaa==我是参数aaa

这样、调用者、调用方法、参数。三大要素就都已经可以获取到了。
但如何进行批量埋点?

筛选记录

用plist、这个网上也很多帖子。之前提的两个帖子也都提及了。

上段代码可以修改如下:

  + (void)setupBehaviorObj {NSDictionary *behaviorPlist = [self getBehaviorEvents];for (NSString * className in behaviorPlist) {//需要hook的ClassClass clazz = NSClassFromString(className);//对应Class需要hook的方法名NSDictionary *events = behaviorPlist[className];if (events[kBehaviorEvents]) {//事件数组for (NSDictionary *event in events[kBehaviorEvents]) {//具体事件方法SEL selector = NSSelectorFromString(event[kBehaviorEventSelectorName]);[clazz aspect_hookSelector:selector withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo) {//获取参数NSMutableDictionary * parameterDic = [NSMutableDictionary new];if (event[kBehaviorParameter]) {NSDictionary * dic = [NSObject properties_apsWithObj:[aspectInfo instance]];for (NSString * parameterStr in event[kBehaviorParameter]) {if ([dic valueForKey:parameterStr]) {[parameterDic setValue:[dic valueForKey:parameterStr] forKey:parameterStr];}}}KTBehaviorData * data = [KTBehaviorData new];data.op_time = [KTBehaviorData getNowTimeTimestamp];data.event_code = event[kBehaviorEventId];data.object_dic = parameterDic;data.page_code = event[kBehaviorPageId];data.op_type = event[kBehaviorType];[[KTBehaviorDataManager sharedManager]pushKTBehaviorDataWithModel:data];} error:NULL];}}}}

控制台信息:

这样、只要你的plist足够健壮。确实可以做到几乎完全无痕的埋点。

结束语:

《demo在此》

年前比较忙、但开了帖总要填完。所以可能有些错别字和语法坑。

每个项目的需求不同、情况也不同。所以这只是个demo、希望能为大家提供一个思路、并没有封装成一个SDK。

  • 不同的情况、可以用不同的打点方案。所谓无痕、并不一定是最好的、太暴力了。
  • 还有就是当项目很庞大的时候、进行hook操作、会不会影响性能。如果影响了、有没有什么改进的方式。
  • 如果你有什么好的想法、或者是项目中有什么更好的方案。还望指教。

补充:

经测。
当导入方法为300时、肉眼无感。
当导入方法为3000时、约1s。
当导入方法为30000时、约15s。
由于在+load中加载、这段时间会算入app启动白屏的时间内。


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。

作者:kirito_song
链接:https://www.jianshu.com/p/ddbfa8037e64
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

iOS 打点上报、无痕埋点相关推荐

  1. iOS 最优无痕埋点方案

    iOS 最优无痕埋点方案 在移动互联网时代,对于每个公司.企业来说,用户的行为数据非常重要.重要到什么程度,用户在这个页面停留多久.点击了什么按钮.浏览了什么内容.什么手机.什么网络环境.App什么版 ...

  2. ios无痕埋点_移动端无痕埋点实践详解(二)

    0x01 前言 在移动端无痕埋点实践详解(一)这篇文章大致总结了移动端无痕埋点的基本原理.主要介绍了什么是无痕埋点,无痕埋点的基础数据流程以及在Android系统上总体思路.这篇文章着重总结下无痕埋点 ...

  3. ios无痕埋点_iOS可视化埋点方案

    前言 随着公司业务的发展,数据的重要性日益体现出来. 数据埋点的准确和全面性显得尤为重要. 通过精准和详细的数据,后面的分析才有意义.随着业务的不断变化,动态化埋点也越来越重要. 三大埋点方式 为了解 ...

  4. ios无痕埋点_iOS无痕埋点方案分享探究

    原标题:iOS无痕埋点方案分享探究 作者丨SandyLoo https://www.jianshu.com/p/b8a67c4acfb3 前言 当前互联网行业的竞争已经是非常激烈了, "功能 ...

  5. ios无痕埋点_掌握数据生命周期:初识数据埋点

    谈到数据驱动业务,离不开数据是怎么来的,数据收集是整个数据生命周期的初始环节. 数据生命周期的大体介绍,在过去的一篇文章中有提到.虽然文章的部分内容我准备重新构造,但是对于这部分的基础环节,并没有太多 ...

  6. 美团点评前端无痕埋点实践

    构建一个数据平台,大体上包括数据采集.数据上报.数据存储.数据计算以及数据可视化展示等几个重要的环节.其中,数据采集与上报是整个流程中重要的一环,只有确保前端数据生产的全面.准确.及时,最终产生的数据 ...

  7. 无痕埋点的设计与实现

    在移动互联网时代,对于每个公司.企业来说,用户的行为数据非常重要.重要到什么程度,用户在这个页面停留多久.点击了什么按钮.浏览了什么内容.什么手机.什么网络环境.App什么版本等都需要清清楚楚.一些大 ...

  8. iOS-史上最强、最详细无痕埋点方案

    在移动互联网时代,对于每个公司.企业来说,用户的行为数据非常重要.重要到什么程度,用户在这个页面停留多久.点击了什么按钮.浏览了什么内容.什么手机.什么网络环境.App什么版本等都需要清清楚楚.一些大 ...

  9. 无痕埋点在Android中的实现

    无痕埋点在Android中的实现 目标 解决手动打点效率低下问题 自动化埋点 本篇技术实现主要是运行是代理,不涉及到插桩技术,不引入插件,对业务影响点最小 技术难点 1. 如何拦截到所有的view的点 ...

最新文章

  1. Hinton等人最新研究:大幅提升模型准确率,标签平滑技术到底怎么用?
  2. 解决CPC撰写文档报错问题“无法获取“AxforApplication”控件的窗口句柄。不支持无窗口的 ActiveX 控件”
  3. 批处理删除编译产生的多余文件
  4. Form学习入门系列(一)
  5. SpringBoot与日志配置
  6. 杂项:Java un
  7. python用什么来写模块-史上最详细的python模块讲解
  8. pytorch基础API介绍
  9. LaserJet 5000 及 5100 系列打印机出现 pcl xl error 怎么办
  10. c语言提供三种逻辑运算符,按优先级高低它们分别是,c语言逻辑运算符优先级
  11. 【马克思主义基本原理】--导论
  12. page8-JQ的点击隐藏与显示
  13. Spring Security和 JWT两大利器来打造一个简易的权限系统。
  14. 985助理教授与二本教授哪个水平高?
  15. Java的思考01 - 当大老板还是小老板
  16. linux下的软件包
  17. multisim 高低电平点亮灯证明
  18. 【小5聊】C# Json字符串转Model实体类以及Model实体类转Json字符串
  19. 蓄冷罐布水器仿真matlab,用于蓄冷罐的布水器的制作方法
  20. 中兴远航10 root获取 ZTE 7530N 刷机magisk 刷机包TWRP下载安装LS框架

热门文章

  1. iPad Pro如何当外置显示器(win、mac)
  2. Unity游戏配置存储方案
  3. Eclipse的TPTP使用方法
  4. 保证RabbitMQ消息的可靠性总结
  5. 香港第一金:黄金3月27行情操作分析预判
  6. ThinkPHP5 实现短信验证码注册功能
  7. matlab去除图片水印_初试 Matlab 之去除水印
  8. 流行病学名词解释和简答题集锦
  9. VS2010向工具箱中添加控件解决 Microsoft Communications Control,未能实例化 设计时授权
  10. cad打开卡死_求助,cad 一打开就卡死,完全没办法运行,怎么处理