Android全埋点技术介绍

一、全埋点是什么

预先收集用户所有的行为数据,而后根据需求从中提取行为数据,也叫无埋点、无码埋点、自动埋点

主要为处理四种事件:

  1. $AppStart事件:启动App,包括冷热启动
  2. $AppEnd事件:退出App,包括正常退出、进入后台、crash、force kill
  3. $AppViewScreen事件:浏览App页面
  4. $AppClick事件:App控件点击

这四点中$AppClick是难度最大的,所以全埋点也是围绕着如何解决这个问题,现有的技术方案中有两种大方向去解决这个问题:‘拦截’和‘插入

  • 拦截:参考点击事件的事件分发机制
  • 插入:参考编译器对java的处理流程:javaCode->.java->.class->,dex

又这两种思想产生的技术方案有很多,所以选择技术方案时需从效率、兼容性、扩展性等方面综合考虑

  • 效率:比如进行对点击事件进行代理,静态代理在编译期完成处理,无论在处理速度和对源代码的影响方面都优于动态代理
  • 兼容性:不同的开发语言,不同的java版本,甚至不同的IDE等等都会产生兼容性问题
  • 扩展性:随着业务的发展是必定会出现的问题

二、$AppViewScreen全埋点

页面浏览事件,onResume代表页面显示出来,那么如何统一监听这个事件呢

Application.ActivityLifecycleCallbacks 他是API14开始提供的接口,该接口提供的回调可以集中处理Activity所有生命周期事件,通过application.registerActivityLifecycleCallback注册。
注意:他会将其中的逻辑插入到实际onResume之前

缺点也很明显,依赖于API版本

三、$AppStart、$AppEnd全埋点

实质问题就是如何判断页面处于前台还是后台,正常状况下技术方案有很多,但是难点在于如何处理异常状况,如crash、force kill 、multiprocessing 等等。

multiprocessing方面,我们可以采用ContentProvider机制来解决,由于ContentProvider基于Binder,并且Android提供了一系列IPC的回调监听,如ContentObserver等,使其更满足于我们的需求。那么我们还需要标记的记录,显然Sqlite存这个就太重量级了,所以ContentProvider+Sharedpreferences是比较好的方案

crash、force kill 方面,可以引入Session的概念,设置一个时间如30s,如果一个页面退出了,30s内没有新页面打开,则判断处于后台;若上一个页面退出时间的间隔大于30s,则认为重新回到前台

四、$AppClick全埋点

这一点就是整个全埋点的重点了,分为:

  1. 代理View.OnlickListener
  2. 代理Window.Callback
  3. 代理View.accessibilityDelegate
  4. AspectJ
  5. ASM
  6. Javassist
  7. AST

1.代理View.OnlickListener

主要思想就是拿到页面内的所有设置了点击事件的View,在点击之前插入埋点代码。

android.R.id.content对应的 View 是一个 FrameLayout ,只有一个子元素,就是我们平时开发的时候,在 onCreate 方法中通过 setContentView 设置的 View 。也即,当我们在 layout 文件中设置一个布局文件时,实际上该布局会被一个 FrameLayout 容器所包含,这个 FrameLayout 容器的 android : id 属性值就是android.R.id.content

需要引起我们注意的是,在不同的 SDK 版本下,android.R.id.content所指的显示区域也是有所不同的。
具体的差异如下:

  • 在 SDK 14 + ( Native ActionBar ) ,该显示区域指的是 ActionBar 下面的那部分
  • 在 Support Library Revision lower than19 ,使用 AppCompat ,则显示区域包含 ActionBar
  • 在 Support Library Revision 19 ( or greater ) ,使用 AppCompat ,则显示区域不包含 ActionBar ,即与第一种情况相同

所以如果不使用 Support Library 或使用 Support Library 的最新版本,则 android.R.id.content所指的区域都是 ActionBar 以下的内容。

原理 activity->rootView->设置了View.OnClickListener的View->代理这个View插入埋点

如何获取rootView如果通刚才所讲采用android.R.id.content找到的是界面内容区域,但他少了MenuItem,所以我们可以使用DecorView去遍历,activity.getWindow().getDecorView();

如何获取onResume之后动态创建的View面对这个问题我们可以采用ViewTreeObserver.OnGlobalLayoutListener去监听布局发生改变这个事件

缺点

  • 反射效率低
  • ActivityLifecycleCallbacks要求API 14+
  • View.hasOnClickListener要求API 15+
  • removeGlobalLayoutListener要求API 16+
  • 不支持Dialog、PopupWindow之类的点击事件

2.代理Window.Callback

这个接口包含了一系列diaptchXXXXXX和onXXXXXX接口,当Window接收到外界状态改变时会回调其中相应方法。比如点击控件会回调Window.Callback中的dispatchTouchEvent(MotionEvent ev)。

原理activity.getWindow().getCallback()->用自定义WrapperWindowCallback代理这个Callback对象->在其中的dispatchTouchEvent(MotionEvent ev)通过MotionEvent找到这个View,进行埋点->再调用原dispatchTouchEvent
缺点

  • 每次触发都要遍历RootView,效率低
  • ActivityLifecycleCallbacks要求API 14+
  • View.hasOnClickListener要求API 15+
  • 不支持Dialog、PopupWindow之类的点击事件

3.AspectJ

AOP即面向切面编程,可以再编译期对代码进行动态管理,可以无浸入的在宿主中插入代码逻辑实现如埋点、日志、权限等功能

通过定义PointCut标记切点,并得到所在方法JoinPoint,得到Target之后通过Weaving增强方法

它是一个规范,本身并没有语言实现,是对AOP思想的实现,与Java配合使用,他的核心是ajc编译器,将AspectJ的代码在编译器插入到目标程序被切出的cutPoint当中

缺点

  • 无法植入第三方库
  • 不兼容Lambda语法
  • 不兼容D8、Gradle4.x

4.ASM

下面是Java打包流程
Android全埋点技术介绍_第1张图片
通过看图实际上我们可以从任何一个主线中间修改编译完的文件插入我们所需代码完成相关功能,ASM从字节码入手解决问题。

5.Javassist

也是通过修改字节码,略

4.APT

注解处理器,他是javac的一个注解工具,他可以在编译期通过注解控制生成的.java文件来进行注入

这里面有个AST,是抽象语法树,是编译器对代码第一步加工后的结果

流程JavaTxt->语法分析->生成AST->语义分析->字节码

缺点

  • com.sun.tool.javac.tree比较难懂
  • APT无法扫描其他module
  • 不兼容Lambda语法
  • 带返回值的方法很难插入到方法后

你可能感兴趣的:(埋点)