22

DJMTA APP埋点框架的实现

 2 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAwMzg1ODMwNw%3D%3D&%3Bmid=2653792129&%3Bidx=1&%3Bsn=f932c01755386deb34166539b4b5678a
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

埋点框架升级背景

  随着业务的快速发展,跨端融合的快速推进,传统的埋点框架已不能适应快速迭代的业务需求,需要解决如下问题:

  1、日志存在丢失问题

  2、Java层频繁的I/O读写导致的性能问题

  3、Android&iOS共享底层日志采集和上报策略,降低维护成本

  4、各种资源位和商品需要精准曝光带来的挑战

  5、跨端融合RN、Flutter在App中混合模式下如何支持埋点共享

  6、数据埋点在线问题如何快速降级和修正

日志丢失问题

随着埋点日志类型的增多(点击:click 页面展示:pv 资源曝光:ep),日志如果在内存中聚合上报,会存在当程序异常退出或者置后台被杀的时候,现场的数据就会丢失,造成数据上报的误差。到家App会优先记录日志到本地文件,当本地文件满足阈值4k,自动触发批量上报。同时当页面离开onPause()的时候,会Flush本地未上报完的残留日志。该方式虽然确保了准确性,但Java层频繁的I/O读写容易触发GC,导致卡顿和内存抖动问题,我们进行了优化,升级为Native方式实现底层日志。不仅能满足性能要求,同时也统一了Android和 i OS 两端。

Native层的设计

  C层日志库的设计初衷是解决 Android 与 iOS 平台的统一日志记录系统,并解决 Android 系统GC的问题。主要是通过简单的 C (Natvie) API,完成对日志的缓冲、分类、内容分片操作。目的是减少频繁上报与每包大小统一的问题,在到家日志系统一般分片大小为 4KB 。设计如下图所示

IfAVjyy.jpg!web

  需要说明的是 Final 区和 Cache 区做了隔离,这样可以将 Cache 区当作一个沙盒来看,外界不论怎样操作都影响不了缓存日志,当进入 final 区就将日志的操作权交个上传业务了。另外,在切换日志类型时就会触发 flush_log 操作,也就是强制的将 Cache 日志 写入 Final  区,这样可以缓解数据上传时效不同步的问题。

Native层的实现

  通过对缓存路径和上传路径与最大的埋点日志分片大小进行设置,就可以将日志系统进行初始化,当记录日志时,传入日志类型、与日志内容,系统自动的就可以对内容进行分类与分片,当有待上传日志时就会对上传日志组件进行回调,这样上传模块就可以根据待上传的日志文件列表进行分批次上传了。当然也提供 flush_djmta_log 操作,强制的将缓冲中的文件直接写入待上传目录,这样满足有立即上报的需求。

/** 初始化log文件系统

@param cache_dir_path 缓冲文件路径

@param output_dir_path 输出文件路径

@param max_file_size 最大文件大小 byte

@return 是否初始化成功

*/

int init_djmta_log_file_system(const char *cache_dir_path, const char *output_dir_path, int max_file_size);

/** 写日志

@param type 日志类型 (app / show)

@param log 日志内容

@param log_len 日志长度

@return 是否有待上传日志产生,有返1 ,无返0

*/

djmta_bool write_djmta_log(DJMTA_LOG_TYPE type, const char *log ,long long log_len);


/** 强制清空缓存区到输出日志目录

@return 如果有输出日志,有返1 ,无返0

*/

djmta_bool flush_djmta_log(void);


/** 根据文件名返回log类型

@param log_file_name 文件名

@return 日志类型(app / show)

*/

DJMTA_LOG_TYPE judge_type_djmta_log(char *log_file_name);


/** 根据文件路径返回log内容

@return 字符串 (注意 用完之后 手动释放 返回char *)

*/

char * fetch_djmta_log(const char *log_file_path);

精准曝光的挑战

  • 简单列表(recycleview单item)

/** 从屏幕外进入屏幕内 */

@Override protected void onAttachedToWindow()

{

super.onAttachedToWindow();

}

/** 从屏幕内移除到屏幕外 */

@Override protected void onDetachedFromWindow()

{

super.onDetachedFromWindow();

}

  • item过于复杂的列表

简单列表可以通过生命周期的捕获,来处理item露出和移出问题。但是有缺陷,比如大部分 App采用的楼层化设计,楼层的样式相对较复杂,此时就需要对数据model进行切割,既能保证曝光精准,也能达到最小颗粒度的局部渲染

RJfuEfY.jpg!web

  • 其他滚动列表(Scrollview和自定义ViewGroup)

为了提升性能,在处理Scrollview和普通自定ViewGroup滚动曝光埋点上报时,我们做了如下优化:

1. 把整个视图映射成一个与之对应的数据结构图(视图坐标+数据结构),数据源AllCache[]、当前屏幕currentCache[]、屏幕外首尾topBottom[2],操作数据源相当于操作视图。

2. 模拟RecycleView的滚动缓存视图实现方案,在滑动的过程中,只操作topBottomCache的最顶部据topBottom[0]和最底部数据topBottom[1]即可,对已在缓存中的数据无需重复计算和添加,未在缓存中的数据进行快速选举替换topBottom[0]或者topBottom[1],通过模拟 R e cycleV iew 源码的视图缓存和复用机制,避免了滑动频繁计算和快速滑动带来的性能开销,同时也能满足精准曝光。

nimAfqV.png!web

混合栈埋点方案

  随着跨端融合的推进,App中跨平台的业务RN、Flutter的广泛使用,我们需要支持混合栈的埋点方案。拿Android举例,RN、Flutter容器在App中都是Activity来承载,那么pv和click的产生都是root容器Activity级别,此时如果产生了RN、Flutter内部自己的跳转,对应的Activity还是不变的,所以统计的pv路径会不精准。如图:  

MreMFrE.png!web

  混合栈埋点很好的解决了这个问题,同时能够满足RN、Flutter等所有跨平台的业务场景。enterPv和exitPv是埋点栈stack暴露的两个方法。RN通过bridge,Flutter通过plugin ,来实现和原生App埋点的无缝接入,但埋点混合栈在Android上需要解决以下几个问题:

  • Intent跳转执行了clearTop,清理掉了其上的所有Activity怎么办?

  • Intent跳转前执行了finish,销毁了当前Activity怎么办?

  • 置后台被系统异常杀死重启恢复界面怎么处理?

  混合栈的设计如下:

FrquEnM.png!web

上图是整个混合栈的流转过程,需要注意的是后台重启异常恢复时,需要清理掉本地的埋点栈,恢复数据的时候重新压栈。

数据降级策略

  埋点SDK插件化,各业务插件的埋点数据路由到埋点插件,通过统一的intercept拦截加工处理,完成和C层的对接存储、读取和上报。同时,通过动态更新,来解决线上埋点crash以及其他问题。

ziEVf2n.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK