为什么进行全埋点?

以往手动模式埋点

以往的埋点形式都是人为进行定义名称和选择性埋点,版本迭代屡次后造成埋点数量继续减少。

  • 在各个代码块进行基本相同的代码调用,侵入性高,如果前期进行更换SDK,有可能会进行大量改变
  • 手动进行埋点可能导致认为忽略造成的埋点失落
  • 只能依据埋点进行用户行为回溯,有些细节和流程无奈连接上,无奈还原用户应用场景
  • 每个版本迭代都须要PM,RD进行埋点梳理,工夫进行耗费

全埋点

  • 无奈在每个按钮,页面加载调用代码,只须要在利用初始化加载即可
  • 用户行为触发主动上报,无需PM思考应该在哪个页面进行埋点
  • 可配置化,能够抉择过滤上报页面,事件,或者特定页面减少属性上报
  • 版本迭代不须要从新进行埋点

如何进行?

  • 页面操作:Application.ActivityLifecycleCallbacks接口
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);}

利用启动完结:AppStart,AppEnd

在ActivityLifecycleCallbacks接口中监听start和pause,并应用SP和ContentProvider进行辅助记录利用的开启工夫和pause工夫,如果用户App在后盾被强杀或者手动退出,那么下次从新应用APP的时候会进行检测Sp中的工夫和以后的工夫,而后进行比照,判断用户是否为重新启动APP,还是仅仅切换到后盾再切换回来。

留神⚠️:start中进行检测,pause中进行工夫数据更新。

利用点击控件

计划1:hook控件的点击事件接口进行代理

整体思路:依据ActivityLifecycleCallbacks接口监听回调,在onActivityResume回调中拿到以后的Activity,而后利用DecorView递归遍历所有子view进行代理onClickListener办法。同时在Activity启动的时候进行ViewTree的observer,ViewTree改变的时候(比方设置了view的不可见不可点击等)从新进行一遍hook。

hook:利用反射获取到View曾经设置的onClickListener对象、区别view的对象类型(button,textView…)进而设置不同的listener。

毛病:根本每个View或者Viewgroup都会有本人的点击事件,并且点击事件接口都为class外部的借口,没有顶层的接口进行兼容检测,所以须要做大量的wrapperListener,工作繁琐反复。此外,每创立一个页面就要进行一次Hook,性能不高,效率低。

计划2:利用Window点击的回调

每次点击的事件散发函数——dispatchTouchEvent(MotionEvent event),进行hook,利用以后activity的RootView的信息再联合event的信息进行埋点。

具体:判断点击的坐标是否位于view(利用rootView循环判断)之中、该view是否处于可见状态;

毛病:每次点击都要去遍历一次rootView,并且一一判断,效率低下。

计划3:AOP(Aspect Oriented Programming)

面向切面编程。应用AspectJ,

思路:在程序编译期间,在相应的onClick办法调用前或后插入埋点代码。

计划4:字节码插桩

字节码函数插桩目前有以下两种框架

ASM

思路:应用程序打包成APK之前会先编译成.class文件,而后打包成dex,最初组成apk。所以在打包成dex文件和编译成.class文件之间进行源文件的替换就行。

毛病:目前没什么毛病

Javassist

与ASM思路统一,然而和ASM比照,效率不够高。

ASM框架进行字节码函数插桩

通过上述计划的比照,最终采纳ASM进行字节码插桩。次要是对代码的侵入低,可定制化配置(过滤采集页面,过滤时长,配置页面映射等)。

下图箭头指向处就是进行函数插桩的地位。

代码侵入性低

计划实现是在代码文件编译成class文件之后进行办法的插入,无需在编写阶段进行。

  • 应用android提供的Transform API获取project的文件
  • 检测到文件后缀为class的时候进行文件批改

    • ASM框架相应API进行字节码读取和剖析和插入
    • 先拿到类的详细信息(类名,修饰符,继承的父类,实现的接口等信息)
    • 接着扫描到该类的办法,进行判断插入咱们预设的埋点代码
    • 而后笼罩原来的class文件
  • 接着gradle持续编译生成dex

效率

比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中初始化//kotlinTrackApi.init(this)//javaTrackApi.INSTANCE.init(this);//配置ConfigOptions.INSTANCE.addTrackInfoCallBack(new TrackInfoCallback() {                @Override                public void trackInfo(String eventName, JSONObject json) {                   //这里进行埋点事件上报                   //当然回调的类型也能够从JSONObjetc变为String                }            });

接入APP后

在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,如果合乎,那么代表这个类蕴含该接口并实现了办法,能够进行埋点代码的插入。

相干视频举荐:
【Android组件化设计】字节码插桩优化框架初始化速度

本文转自 https://juejin.cn/post/6844904194445426702,如有侵权,请分割删除。