作为国内最大分类信息生活服务平台,58集团旗下各个产品都会投入大量人力进行用户行为的分析,来提升运营效率。但是各个产品对用户行为的分析需求基本是相似的。在这样的背景下,我们自研了WMDA 无埋点用户行为分析平台,并提供对PC、M、APP三端支持,帮助各个业务线更好的挖掘用户真实行为。
对于SDK的使用,业务方不需要手动埋点,几行代码,即可实现数据的全量采集。对于移动端SDK来说,采集数据的准确性、及时性、全面性等因素直接决定后续用户行为的分析。本文将从技术选型、技术实现方案角度详细介绍Android端无埋点数据采集技术。
一、技术选型
首先,技术是为需求提供服务的,WMDA的定位是采用无埋点技术来实现用户行为的分析。同时辅助解决手动埋点不易维护,容易出现错埋、漏埋等痛点问题。所以SDK在采集用户行为数据的同时,对开发效率、采集性能、准确性、实时性等有很高的要求,而且需要支持数据的可回溯。
通过对市面上现有埋点技术调研,目前技术方案上大体分为三类:
- 传统代码埋点
实现方案:Coding阶段手动埋点。
代表解决方案:友盟、百度统计。
优点:灵活、准确,可以定制化。
缺点:业务埋点量非常大,开发成本高,不易维护,如果要修改、新增埋点,需要重新发版。
- 动态埋点
实现方案:利用AccessibilityDelegate对每个view实例设置代理,监听控件点击事件。
代表方案:Github上开源的Mixpanel
优点:无需手动埋点,通过可视化圈选,动态下发配置监听指定控件。
缺点:不支持数据可回溯,采集不到Fragment页面数据,只支持API 14及以上,同时该监听方式对app性能影响严重,每个控件都需要动态绑定,在界面变更时,需要重新刷新ViewTree,效率低下。
- 编译时字节码插桩埋点
实现方案:利用Gradle插件,在编译阶段在代码中插入埋点代码,进行数据采集。
代表方案:GrowingIO、美团的替换UI控件方案。
优点:开发效率高,无需手动埋点,编译时插入代码,性能高,支持数据可回溯。
缺点:埋点灵活性低。
通过以上简短分析,我们可以看出三种方案的优缺点都比较明显。最后,我们采取了利用Gradle插件自动注入埋点代码为主,并辅以手动埋点进行数据定制化补全的技术方案。
注:通过查看GrowingIO官方文档,GrowingIO现在也已提供对手动埋点的支持
二、技术实现
WMDA SDK Android端整体架构主要分为圈选模块、事件采集上报、配置管理三部分,如下图所示。
下面根据事件采集上报流程分别来介绍事件采集、处理、存储、上报和圈选。
2.1 事件采集
WMDA移动端数据采集类型主要分三种:页面浏览事件、控件点击事件和自定义事件。作为无埋点解决方案,SDK核心点就是事件的无痕采集。 其中,这三种事件又对应不同的采集处理方式,WMDA通过不同的技术方案进行采集,最后将事件统一处理,然后存储、上报。
2.1.1 插桩入口
事件采集是无埋点技术的核心,其中WMDA对Fragment和控件点击事件拦截,使用的是自己开发的gradle插件wmda plugin,编译时使用ASM以字节码插桩的方式注入代码,实现事件的采集。
对于事件拦截,首先需要确定插入时机和待修改字节码文件。这里我们使用Transform API作为插桩入口,在Java Compiler之后,class文件打包成dex文件之前修改字节码文件。由于Transform API是在Gradle插件版本1.5.0出现的,所以项目开发中Gradle插件版本不能低于1.5.0。
classpath 'com.android.tools.build:gradle:1.5.0'
然后在transform中遍历并操作字节码文件,因为现在很多大型项目,都会进行组件化操作,拆分成多个Library。所以这里除了要修改我们的应用源码,还需要对第三方库中的字节码文件进行扫描操作。
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { inputs.each { TransformInput input -> input.directoryInputs.each { DirectoryInput directoryInput -> //对应用源码生成的class操作 } input.jarInputs.each { JarInput jarInput -> //对第三方库中class操作 } }}
同时我们也引入了注入白名单机制,如果app不希望某些包名下的类被注入代码,比如使用的第三方sdk,可以在项目级别根目录下创建wmda-rules.properties文件并填写不希望注入的类路径。以下为示例,可以添加多个,每行一条,以 “/” 结尾:
com/wuba/sdk/com/wuba/test/
2.1.2 页面事件
针对页面浏览事件,WMDA分两种不同的方式进行采集。
2.1.2.1 Activity采集 针对Activity
,WMDA采用的方式是使用LifecycleCallback
来监听页面的开启和关闭。 在页面开启时,拦截生命周期方法onResume
,然后在事件处理模块处理,将其格式化成事件结构,并进行存储上报。
@Overridepublic void onActivityResumed(Activity activity) { // 页面浏览事件采集处理 PageManager.getInstance().onActivityResumed(activity);}
不过该方案有个适配问题,在Android4.0(API14)以下,系统并不支持该方法。
注:目前App最低版本基本都是android 4.0及以上
在低版本中,我们也可以通过Hook方式拦截。通过拦截主线程的Instrumentation
实例,来实现低版本页面的监听。这块同时还需要考虑第三方插件也Hook该实例的情况,执行Hook前对应方法,保证对app中其他插件没有影响。缺点是如果其他SDK也使用了这种方式,可能会影响我们的拦截。
@Overridepublic void callActivityOnResume(Activity activity) { // 页面浏览事件采集处理 PageManager.getInstance().onActivityResumed(activity); //执行Hook前Instrumentation实例的onResume方法 oldInstrumentation.callActivityOnResume(activity);}
2.1.2.2 Fragment采集 针对Fragment
,由于Android系统并没有关于Fragment
生命周期的回调监听,所以这里WMDA通过Gradle插件,在编译时期,利用ASM库进行字节码操作,对Fragment
注入WmdaAgent
相应的页面采集方法,完成事件采集。在注入策略上,我们只需要对Fragment父类为下面两个的页面注入采集代码即可。
android/app/Fragmentandroid/support/v4/app/Fragment
对Fragment相关方法注入代码示例:
@OverrideMethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor wrappedMv = mv; if (wrappedMv != null) { // 在onResume中插入WmdaAgent.onFragmentResumed方法 if (name.equals("onResume") && desc.equals("()V")) { wrappedMv.visitCode() wrappedMv.visitVarInsn(Opcodes.ALOAD, 0) wrappedMv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/wuba/wmda/autobury/WmdaAgent", "onFragmentResumed", "(Landroid/app/Fragment;)V") } }}
WmdaAgent
代码:
public static void onFragmentResumed(Fragment fragment) { // 页面浏览事件采集处理 PageManager.getInstance().onFragmentResumed(fragment);}
2.1.3 控件点击事件
关于点击事件的采集,WMDA在早期研发过程中采取的是Mixpanel开源方案。在上文中已经提到过,该方案开发效率不错,不过性能问题、Fragment页面采集不到问题、版本适配问题,导致该方案存在瓶颈和风险。
在后续的持续探索中,我们发现,使用Gradle插件在编译时埋点可以完美继承Mixpanel方案的各项优点,同时又可以规避其性能、数据准确性和版本适配问题。于是,在控件点击事件的采集上,我们调整了技术实现方案,从动态对View设置代理演进为编译时插入埋点代码。
WMDA对点击事件拦截支持常用的一些第三方框架,比如:
ButterKnife、databinding、AndroidAnnotations、RxBinding
具体的技术和之前的Fragment
插桩埋点是一样的,编译时对onClick
方法注入代码,以AOP方式对事件拦截处理。核心实现思路如下:
插件代码
文末
架构师不是天生的,是在项目中磨练起来的,所以,我们学了技术就需要结合项目进行实战训练,那么在Android里面最常用的架构无外乎 MVC,MVP,MVVM,但是这些思想如果和模块化,层次化,组件化混和在一起,那就不是一件那么简单的事了,我们需要一个真正身经百战的架构师才能讲解透彻其中蕴含的深理。
一线互联网Android面试题总结含详解(初级到高级专题)
EFVrZpv-1725871600848)]
[外链图片转存中…(img-F89zStuX-1725871600848)]
一线互联网Android面试题总结含详解(初级到高级专题)
[外链图片转存中…(img-dOfwkNnY-1725871600848)]