| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> 51信用卡 Android 自动埋点实践 -> 正文阅读 |
|
[移动开发]51信用卡 Android 自动埋点实践 |
背景随着公司业务的发展,对业务团队的敏捷性和创新性提出了更高的要求,而通过大数据的手段在一定程度上可以帮助我们实现这个愿景,同时良好的数据分析可以也帮助我们进行更好更优的决策。对于数据本身,其处理流程主要可以归结为以下几点:
其中所谓的数据采集是针对特定用户行为或事件进行捕获、处理,这一步骤无疑是十分重要的,因为数据采集的准确性和多样性也会直接对后续的步骤产生影响。本文也主要是讨论数据采集的几种方式,而我们常说的『埋点』就是数据采集领域的术语,数据采集的方式也可以说是埋点的几种方式。 现状、痛点目前公司内部主要使用代码埋点的方式进行数据采集,所谓代码埋点指的是
基于预先编码实现的代码埋点,其优点是:控制精准、采集灵活性强,可以自由的选择什么时候发送什么样的数据;但缺点也同样十分明显,开发、测试成本高,对于客户端而言需要等待发版才能修改线上的埋点。 日常的开发过程中,经常有同事反馈埋点的错埋及漏埋,其根本原因都是代码埋点本身特点导致,这样的情况推动着我们去尝试使用其他埋点方式。 业内情况无痕埋点无痕埋点也可称为无埋点或者全埋点,即在端上自动采集并上报尽可能多的数据,在计算时筛选出可用的数据。其优点是:很大程度上减少开发、测试的重复劳动,数据可以回溯并且全面。缺点是:采集信息不够灵活,并且数据量大。 可视化埋点可视化埋点是通过可视化工具选择需要收集的埋点数据,下发配置给客户端,从而解析配置采集相应埋点的方式。其优点是:很大程度上减少开发、测试的重复劳动,数据量可控,可以在线上动态的进行埋点配置,无需等待 App 发版。其缺点同样是采集信息不够灵活,并且无法解决数据回溯的问题。 阶段一:无痕埋点分析公司常用的一些数据指标,我们发现对于大部分指标而言,我们只需要有页面的曝光事件、控件的点击事件等一些发送时机、内容相对固定的埋点即可,而这部分埋点,恰恰可以比较方便的使用自动埋点(相对于代码埋点这种手动埋点来说,无痕埋点及可视化埋点均可被称为自动埋点)来进行采集。 相对于可视化埋点来说,无痕埋点在前期不需要可视化工具进行埋点收集,SDK 开发投入较小,因此我们进行了第一步从手动埋点到无痕埋点的迭代。 无痕埋点技术实现无痕埋点需要自动采集数据,因此针对页面、控件等元素需要生成其 ID,该 ID 需尽量具备『唯一性』和『稳定性』。『唯一性』非常好理解,因为对于任意元素而言,其 ID 应该是与其他所有元素都不同的,这样我们才能根据 ID 唯一标识出那个我们想要的元素,采集上来的数据才是准确的,不重复的。而『稳定性』则是说,元素的 ID 应尽量不受版本的变动而改变,这样后期关联业务含义的操作才会更加便捷。 页面ID规则页面的 ID 较容易定义,参考上文提到的『唯一性』和『稳定性』,我们很容易就可以想到将页面所在类的类名作为 ID。类名作为 ID,首先它是相对唯一的,除了页面复用,不存在其他类名相同的页面,而页面复用的情况可以通过页面标题名称等方式进行规避;其次它是相对稳定的,只有在页面类名被修改的情况下 ID 才会改变,而我们日常开发的过程中,除了一些页面重大的改版之外不会轻易修改类名。在 Android 中,页面有两种类型 Activity 和 Fragment,Fragment 可以镶嵌在不同的 Activity 内,因此两者的 ID 定义规则有些不同:
页面PV、UV有了页面的唯一 ID 生成的规则,我们只需要在页面曝光的时候,生成这个 ID,然后上传即可实现页面的 PV、UV 指标。至于页面曝光的时机,在 Android 开发中很容易可以找到,因为对于 Activity 和 Fragment 而言都有标准的生命周期。针对业务中 PV、UV 的定义,我们可以将 Activity 的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NICcWJYl-1630047158392)(https://user-gold-cdn.xitu.io/2019/2/22/16914132fcc34c02?imageView2/0/w/1280/h/960/ignore-error/1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRn99Zc2-1630047158393)(https://user-gold-cdn.xitu.io/2019/2/22/16914147eba3c43d?imageView2/0/w/1280/h/960/ignore-error/1)] 控件ID规则相对于页面而言,控件的 ID 定义规则要更加复杂。起初我们会想到用『R.id』,在编译时 Android aapt 会给每个写在 xml 里的控件生成一个唯一 ID,但是从 aapt 的生成规则来看,这个 ID 并不是固定不变的,在资源文件发生变化的时候,id 也可能会出现变化,也就是不同版本的相同控件的 ID 是有可能不同的。根据 ID 需要具备的『唯一性』和『稳定性』来看,这个 ID 具备『唯一性』,但『稳定性』非常差,因此这个方案不可行。 紧接着我们想到,每个界面所有的控件根据其父子关系可以绘制出页面的视图树,从控件本身出发,根据控件的类名加上其所处层级的位置等特征信息,并逐级的向上遍历,直至找到根节点位置,这样我们就能得到一个控件在该视图树中的一个控件路径;反过来说,根据这个控件路径,我们就能在这个视图树中唯一确定一个控件。下图是一个简单的 ViewTree 模型: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjM833sw-1630047158394)(https://user-gold-cdn.xitu.io/2019/2/22/169141530d109321?imageView2/0/w/1280/h/960/ignore-error/1)] 根据上文所述控件路径生成规则,对于 Button 而言,其路径为: 上文页面 ID 的生成规则中我们说到,对于 Android 来说,页面有 Activity 和 Fragment 两种,因为一个 Activity 可以包含不同的 Fragment,所以控件如果是存在于 Fragment 中的,则页面 ID 需要为其所在的 Fragment 的页面 ID,如果不在 Fragment 中,则包含 Activity 的页面 ID 即可,那么如何能够从控件本身的实例获取到其所在的 Activity 或者 Fragment。对于 Activity 而言比较简单,我们可以通过如下代码实现: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6uuoLFDG-1630047158395)(https://user-gold-cdn.xitu.io/2019/2/22/16914170e84952aa?imageView2/0/w/1280/h/960/ignore-error/1)] 对于 Fragment 则相对比较麻烦,我们只能事先将 Fragment 对应的页面 ID 和控件本身绑定,即通过打 tag 的方式,在 Fragment 的 OnViewCreated 方法中,拿到 Fragment 容器中的根 View,并打上 Fragment 的页面 ID,然后遍历该 View,为其所有的子控件都打上标记,核心代码如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8F02xx8-1630047158396)(https://user-gold-cdn.xitu.io/2019/2/22/1691417f2d466b17?imageView2/0/w/1280/h/960/ignore-error/1)] 所以当我们拿到一个 View 的实例时,我们先看是否能拿到这个 tag 对应的页面 ID,如果拿不到再去找其所属的 Activity,然后得到页面 ID,随后根据它本身的控件路径,拼凑出控件的 ID,核心代码如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPPGBRWg-1630047158397)(https://user-gold-cdn.xitu.io/2019/2/22/1691418ebbb5ea6b?imageView2/0/w/1280/h/960/ignore-error/1)] 控件ID的优化基于我们上述的控件 ID 定义,在页面元素不发生变动的情况下,基本能够保证『稳定性』和『唯一性』,但是页面元素发送动态变化,或者不同版本之间 UI 进行改版的情况下,我们的控件 ID 就会变得不够稳定,比如以下情况: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCUoHoYU-1630047158397)(https://user-gold-cdn.xitu.io/2019/2/22/1691419a52c454f5?imageView2/0/w/1280/h/960/ignore-error/1)] 在插入一个 FrameLayout 之后,我们 Button 的控件路径就变成了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XWXJz853-1630047158398)(https://user-gold-cdn.xitu.io/2019/2/22/169141a8e14229d8?imageView2/0/w/1280/h/960/ignore-error/1)] 控件的点击、长按指标有了控件 ID 的生成规则,控件的点击和长按指标我们就能很方便的进行统计,因为在 Android 中,控件的点击和长按都有非常标准的回调函数,即
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y8zV52ct-1630047158398)(https://user-gold-cdn.xitu.io/2019/2/22/169141b5e2b6a995?imageView2/0/w/1280/h/960/ignore-error/1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxuambhl-1630047158399)(https://user-gold-cdn.xitu.io/2019/2/22/169141b5e6d8f326?imageView2/0/w/1280/h/960/ignore-error/1)] 代码插桩通过上文的描述,我们得到了页面和控件的 ID 的定义规则,也知道了只需要在相应的回调函数中写入 SDK 代码获得我们想要的对象,就能够计算出我们想要的指标,那么如何才能自动的往我们现有的工程中写入获得对象的代码。 在指定的切点插入指定的代码,这个业务场景可能很多同学都非常熟悉,我们常用 AOP 的方式来解决这类问题,将所有的代码插桩逻辑集中在一个 SDK 内处理,这样可以最大程度的不侵入业务。 JavassistJavassist 是一个基于字节码操作的 AOP 框架,它允许开发者自由的在一个已经编译好的类中添加新的方法,或是修改已经存在的方法。但是和其他的类似库不同的是,Javassist 并不要求开发者对字节码方面具有多么深入的了解,同样的,它也允许开发者忽略被修改的类本身的细节和结构。一个简单的修改方法体的例子如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fc79CuYs-1630047158399)(https://user-gold-cdn.xitu.io/2019/2/22/169141ef8ca92a07?imageView2/0/w/1280/h/960/ignore-error/1)] gradle 插件Javassist 需要操作已经编译好的类,Android 的打包流程从下图可以了解,我们可以在 Java 编译器编译完工程代码,.class 文件转成 dex 之前使用 Javassist 来进行我们需要的代码插桩工作。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1aja8v2-1630047158400)(https://user-gold-cdn.xitu.io/2019/2/22/169141ef8af43215?imageView2/0/w/1280/h/960/ignore-error/1)] 了解过 gradle 插件的同学可能知道,在 Android Gradle Plugin 版本在 1.5.0 及以上,我们可以使用官方提供的最新的 Transform API,在打包编译时 .class 打包成 dex 之前对 class 文件进行处理。具体的自定义插件过程不在赘述,我们只需要定义一个自己的 Transform,继承系统的 Transform,重写 transform 方法即可。 在 transform 方法的第二个参数里,我们可以获取到工程内所有的源码编译出来的 .class 文件以及所有依赖的 jar 包,我们挨个遍历所有的 .class 文件,以及解压缩所有的 jar 包,拿到 jar 包内的 .class 文件,即可实现对所有的文件进行代码插桩的需求,核心代码如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbkY2qQW-1630047158400)(https://user-gold-cdn.xitu.io/2019/2/22/16914200ae914681?imageView2/0/w/1280/h/960/ignore-error/1)] 拿到 .class 文件之后,我们会按照上述 Javassist 的工作流程进行代码插桩:
将项目中所有的源文件遍历一边后,我们就完成了整个项目代码的插桩,在我们想要的切入点(页面的曝光、控件的点击等回调函数),就成功的插入了相应捕获页面、控件对象的代码,在页面曝光或者控件点击时,就能够获得相应的对象,生成唯一 ID 并上报相应的埋点事件,完成整一个无痕埋点的流程了。 阶段二:可视化管理后台完成阶段一的无痕埋点之后,我们可以通过接入一个 SDK 来轻松的实现页面曝光、控件点击等指标的数据获取,但是通过上文我们可以知道,我们定义的 ID 其实对于业务方(产品、运营、BI 等非业务开发人员)而言是不友好的,他们无法根据 ID 中的类名、Resource ID 等特征信息来关联到埋点具体的业务含义,因此我们需要通过一些工具来帮助他们将埋点元素 ID 和具体的业务含义进行关联,甚至是跨平台(Android、iOS 的自动埋点 ID 是不一致的)的关联。 从另外一个角度来说,有了这样的可视化管理后台,我们还可以通过下发配置表的方式来收集想要的埋点,这其实就是我们开篇说的可视化埋点。所以有了这样的管理后台并基于自动埋点的数据采集方式,我们可以根据具体的业务场景,灵活的选择是无痕埋点(全量采集)还是可视化埋点(根据配置表定向采集)。 一个简单的用户操作可视化管理后台的时序图如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqF76ANz-1630047158400)(https://user-gold-cdn.xitu.io/2019/2/22/1691421b034aa8cd?imageView2/0/w/1280/h/960/ignore-error/1)] 从图中我们可以知道,可视化管理后台的核心内容就是上传手机界面截图及控件相关信息,可以让用户在后台对相关的页面、控件与自定义的业务 ID 进行绑定并在后台生成配置,界面实际效果如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iRnGUydM-1630047158401)(https://user-gold-cdn.xitu.io/2019/2/22/1691421fa28f6760?imageView2/0/w/1280/h/960/ignore-error/1)] 在上图的可视化管理平台中,主要有这么几大块内容,最上方是当前和管理后台建立连接的设备信息,左下方是当前界面已经绑定过自定义业务 ID 的埋点元数据,右下方是手机当前界面在管理平台上的映射,并标记出界面内所有可埋点的控件,已绑定过自定义业务 ID 的控件标记绿色,未绑定的标记红色,这样用户就可以非常方便的选择自己想要的控件进行操作。 要实现上图这样的效果,我们只需要遍历当前页面,并上传所有可被埋点的控件信息,对于目前我们想要实现的数据指标而言,我们只关心控件的点击和长按事件,换句话说就是我们只需要找到当前页面内所有的可被点击或长按的控件即可。 上报控件信息对于需要上报的控件需要满足以下几个条件:
对于控件是否可被点击或长按,我们没法直接通过系统的 API 来获取,但是通过源码我们可以看到,View 内部还是有私有变量来存储点击或长按的监听器的,在 API14 之前的 mOnClickListener 对象和 API14 之后的 mListenerInfo 对象,均可用来判断当前 View 对象是否被设置了点击监听函数,我们可以通过反射来拿到这些对象,并进行判断,长按的判断也同理,核心代码如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hybb7kP6-1630047158401)(https://user-gold-cdn.xitu.io/2019/2/22/1691423e66690b6b?imageView2/0/w/1280/h/960/ignore-error/1)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qgBR8CGi-1630047158402)(https://user-gold-cdn.xitu.io/2019/2/22/1691424396a15b80?imageView2/0/w/1280/h/960/ignore-error/1)] 处理完可被点击或长按的条件后,我们要判断控件在当前界面是否可见,因为我们需要在截图上把控件全选出来,如果控件本身是不可见的也被圈出来,用户就会比较迷茫。通过一定的调研,我们发现满足以下几点条件,即表示该控件在屏幕内可见:
控件符合上述的可被点击或长按且在当前界面可见这两个条件,其信息就会被并上传至管理后台,用户就可以对这个控件进行编辑,绑定自定义的业务 ID,管理后台得到控件与自定义业务 ID 的关联关系后,即可生成配置表,并下发至 App。这样采集上来的埋点就会带上自定义业务 ID,用户在后续的数据使用过程中就可以非常方便的查看相应的业务指标。 可视化管理后台核心的逻辑就是上述的客户端和管理后台建立连接并上传相应信息,其他配置的生成、下发等都非常容易处理,就不在赘述。 阶段三:埋点DSL文章开头我们有提到过,无论是无痕埋点还是可视化埋点,都是基于自动化采集埋点的方式来做的,在这样的采集方式下,我们无法通过埋点携带更多的信息,这也是我们面临的一个痛点。基于这样的需求之下,我们考虑可以用DSL来解决这个问题。 什么是DSLDSL 即 Domain-specific language,翻译为领域特定语言,意为在特定领域解决特定任务的语言。 哪些场景下需要用到DSL上文提到的自动埋点以页面和控件为切入点,hook 页面曝光和控件点击事件,并获取页面及控件相关信息作为特征值写入埋点。在简单的场景下,这样的逻辑尚可胜任,但在某些复杂的场景,比如典型的 banner 轮播、资源位曝光等,控件相同但实际内容不同的埋点,无法根据控件信息来区分。对于手动埋点而言,获取接口内的信息,然后传入埋点就能进行区分,但是自动埋点无法关联这部分接口信息,于是需要 DSL 来定义简单的规则,通过运行时的方式来获取内存中的这部分数据,从而写入埋点,进行更加精细的区分。 如何实现DSLDSL 的构建与编程语言其实比较类似,想想我们在重新实现编程语言时,需要做那些事情;实现编程语言的过程可以简化为定义语法与语义,然后实现编译器或者解释器的过程,而 DSL 的实现与它也非常类似,我们也需要对 DSL 进行语法与语义上的设计。总结下来,实现 DSL 总共有这么两个需要完成的工作:
设计语法和语义这部分其实是千人千面的,我们可以根据自己的业务需求来不断的迭代,但是核心思路是定义一些特殊的字符串,并对应调用各自的 API,一些简单的语法大致有以下这些:
实现解释器说是解释器,其实只是一段预先写好在 SDK 内的代码逻辑。通过预先约定好的语法和语义,业务开发者在可视化平台针对某个控件进行代码编写,然后下发这部分代码,SDK 根据规则解析这部分代码,然后通过反射(runtime)的方式来获取相应的数据并写入自动埋点。 平台配套可视化平台在元素录入的时候或者后期编辑的时候,可以额外录入事件发生时想要获取的数据的路径,这部分内容需要由业务开发人员根据 SDK 这边给出的规则进行路径的录入。成功录入后,生成配置文件下发至 App。SDK 在事件发生时,获取到相应事件携带的数据路径,根据 DSL 约定的规则解析路径并获取相应的数据,存放至埋点相应字段内上传。 总结从最早的手动埋点到后续的无痕埋点,再到可视化管理平台的搭建,以及 DSL 的实现,一步步的走来我们可以看到虽然相比手动埋点而言,自动埋点有许多优势,但同样其劣势也非常明显,即使我们通过一些工具、技术去不断的优化和弥补它的不足,但他依旧不能完全的替代手动埋点。所以结合业务本身的特点,选择最合适的埋点采集方式才是最正确的做法,在一些相对稳定,不常变动的页面、控件中使用自动埋点,可以极大的节省各个环节的时间;但如果页面、控件本身是频繁迭代的那自动埋点就不如手动埋点来的合适。 作者介绍
最后按照国际惯例,给大家分享一套十分好用的Android进阶资料:《全网最全Android开发笔记》。 整个笔记一共8大模块、729个知识点,3382页,66万字,可以说覆盖了当下Android开发最前沿的技术点,和阿里、腾讯、字节等等大厂面试看重的技术。 因为所包含的内容足够多,所以,这份笔记不仅仅可以用来当学习资料,还可以当工具书用。 如果你需要了解某个知识点,不管是Shift+F 搜索,还是按目录进行检索,都能用最快的速度找到你要的内容。 相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照整个知识体系编排的。 (一)架构师必备Java基础1、深入理解Java泛型 2、注解深入浅出 3、并发编程 4、数据传输与序列化 5、Java虚拟机原理 6、高效IO …… (二)设计思想解读开源框架1、热修复设计 2、插件化框架设计 3、组件化框架设计 4、图片加载框架 5、网络访问框架设计 6、RXJava响应式编程框架设计 …… (三)360°全方位性能优化1、设计思想与代码质量优化 2、程序性能优化
3、开发效率优化
…… (四)Android框架体系架构1、高级UI晋升 2、Android内核组件 3、大型项目必备IPC 4、数据持久与序列化 5、Framework内核解析 …… (五)NDK模块开发1、NDK开发之C/C++入门 2、JNI模块开发 3、Linux编程 4、底层图片处理 5、音视频开发 6、机器学习 …… (六)Flutter学习进阶1、Flutter跨平台开发概述 2、Windows中Flutter开发环境搭建 3、编写你的第一个Flutter APP 4、Flutter Dart语言系统入门 …… (七)微信小程序开发1、小程序概述及入门 2、小程序UI开发 3、API操作 4、购物商场项目实战 …… (八)kotlin从入门到精通1、准备开始 2、基础 3、类和对象 4、函数和lambda表达式 5、其他 ……
|
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 13:37:13- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |