以往的埋点方式都是人为进行定义名称和选择性埋点,版本迭代屡次后形成埋点数量持续增长。java
public interface ActivityLifecycleCallbacks {
void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
void onActivityStarted(@NonNull Activity activity);
void onActivityResumed(@NonNull Activity activity);
void onActivityPaused(@NonNull Activity activity);
void onActivityStopped(@NonNull Activity activity);
void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
void onActivityDestroyed(@NonNull Activity activity);
}
复制代码
在ActivityLifecycleCallbacks接口中监听start和pause,并使用SP和ContentProvider进行辅助记录应用的开启时间和pause时间,若是用户App在后台被强杀或者手动退出,那么下次从新使用APP的时候会进行检测Sp中的时间和当前的时间,而后进行对比,判断用户是否为从新启动APP,仍是仅仅切换到后台再切换回来。android
注意⚠️:start中进行检测,pause中进行时间数据更新。编程
总体思路:根据ActivityLifecycleCallbacks接口监听回调,在onActivityResume回调中拿到当前的Activity,而后利用DecorView递归遍历全部子view进行代理onClickListener方法。同时在Activity启动的时候进行ViewTree的observer,ViewTree改动的时候(好比设置了view的不可见不可点击等)从新进行一遍hook。json
hook:利用反射获取到View已经设置的onClickListener对象、区别view的对象类型(button,textView.....)进而设置不一样的listener。app
缺点:基本每一个View或者Viewgroup都会有本身的点击事件,而且点击事件接口都为class内部的借口,没有顶层的接口进行兼容检测,因此须要作大量的wrapperListener,工做繁琐重复。此外,每建立一个页面就要进行一次Hook,性能不高,效率低。框架
每次点击的事件分发函数——dispatchTouchEvent(MotionEvent event),进行hook,利用当前activity的RootView的信息再结合event的信息进行埋点。maven
具体:判断点击的坐标是否位于view(利用rootView循环判断)之中、该view是否处于可见状态;ide
缺点:每次点击都要去遍历一次rootView,而且逐个判断,效率低下。函数
面向切面编程。使用AspectJ,性能
思路:在程序编译期间,在相应的onClick方法调用前或后插入埋点代码。
字节码函数插桩目前有如下两种框架
思路:应用程序打包成APK以前会先编译成.class文件,而后打包成dex,最后组成apk。因此在打包成dex文件和编译成.class文件之间进行源文件的替换就行。
缺点:目前没什么缺点
与ASM思路一致,可是和ASM对比,效率不够高。
通过上述方案的对比,最终采用ASM进行字节码插桩。主要是对代码的侵入低,可定制化配置(过滤采集页面,过滤时长,配置页面映射等)。
下图箭头指向处就是进行函数插桩的位置。
方案实现是在代码文件编译成class文件以后进行方法的插入,无需在编写阶段进行。
比java中使用反射快,在ASM的官网中也有介绍。ASM的设计和实现是尽量的小和尽量快,因此它很是适合在动态系统中使用(但固然也能够以静态方式使用,例如在编译器中使用)。
更多关于框架ASM的远离和具体使用在这里就不赘述了。
在project的build.gradle添加:
buildscript {
repositories {
google()
jcenter()
maven {
url uri('repo')
}
}
dependencies {
classpath 'com.cage:autotrack.android:1.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
复制代码
在APP模块中:
apply plugin: 'com.cage.plugin'
dependencies{
implementation project(':cgtrack_support')
}
复制代码
初始化:
//Application中初始化
//kotlin
TrackApi.init(this)
//java
TrackApi.INSTANCE.init(this);
//配置
ConfigOptions.INSTANCE.addTrackInfoCallBack(new TrackInfoCallback() {
@Override
public void trackInfo(String eventName, JSONObject json) {
//这里进行埋点事件上报
//固然回调的类型也能够从JSONObjetc变为String
}
});
复制代码
在APP中进行点击浏览页面,相应的事件进行触发:
页面点击的时候触发:
页面退出的时候触发:
进入页面的时候触发:
目前已经覆盖了View,Dialog,CompoundButton,AdapterView,BottomNavigationView。
后续若是缺乏相应的控件,那么能够根据相应的控件进行添加对应的字节码描述便可:
例如在APP中的底部控件为Google的design控件,添加:
SDK_API_CLASS = "com/cage/cgtrack/TrackUtils"
//普通设置点击事件
if(mInterfaces.contains('android/support/design/widget/BottomNavigationView$OnNavigationItemSelectedListener') && nameDesc == 'onNavigationItemSelected(Landroid/view/MenuItem;)Z') {
//插入变量
methodVisitor.visitVarInsn(ALOAD, 1)
//插入方法
methodVisitor.visitMethodInsn(INVOKESTATIC, SDK_API_CLASS, "trackViewOnClick", "(Landroid/view/MenuItem;)V", false)
}
//使用Lambda形式设置
MethodCell onNavigationItemSelected = new MethodCell(
'onNavigationItemSelected',
'(Landroid/view/ MenuItem;)Z',
'Landroid/support/design/widget/BottomNavigationView$OnNavigationItemSelectedListener',
'trackViewOnClick',
'(Landroid/view/MenuItem;)V',
1, 1,
[Opcodes.ALOAD])
LAMBDA_METHODS.put(onNavigationItemSelected.parent + onNavigationItemSelected.name + onNavigationItemSelected.desc, onNavigationItemSelected)
复制代码
上述步骤的意思:
先判断该类中实现的接口是否包含OnNavigationItemSelectedListener接口,接着判断实现该接口的方法是否是onNavigationItemSelected,若是符合,那么表明这个类包含该接口并实现了方法,能够进行埋点代码的插入。