ASM字节码插桩

为什么进行全埋点?

以往手动形式埋点

以往的埋点方式都是人为进行定义名称和选择性埋点,版本迭代多次后造成埋点数量持续增加。

* 在各个代码块进行基本相同的代码调用,侵入性高,如果后期进行更换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中初始化

//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后


在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,如果符合,那么代表这个类包含该接口并实现了方法,可以进行埋点代码的插入。

文章来源于互联网:ASM字节码插桩

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/21140,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?