理解Android无埋点技术

首先什么是无埋点呢,其实所谓无埋点就是开发者无需再对追踪点进行埋码,而是脱离代码,只需面对应用界面圈圈点点即可追加随时生效的事件数据点。

无埋点的好处

其实无埋点并不是完全不用写代码,而是尽可能的少写代码。开发者将SDK集成到项目中,配置并初始化SDK之后,接下来就可以进行可视化操作。这个可以不依赖开发者,一些实施人员都是通过后台的配制,就达到埋点的配制,还有新增埋点改动都是很方便的实现。最后就是配制和代码,可以很灵活地扩展,动态地更新。

全埋点采集的事件目前主要包括以下四种

$AppStart事件

指应用程序启动(冷启动和热启动)场景。热启动也就是指应用程序从后台恢复的情况。

$AppEnd事件

指应用程序退出 ,包括正常退出、按Home键进入后台、应用程序被强杀、应用程序崩溃等场景。

$AppViewScreen事件

是指应用程序页面浏览,对于Android应用程序来说,就是指切换Activity或Fragment。

$AppClick事件

是指应用程序控件点击,也即View被点击,比如点击Button、ListView等。

A p p C l i c k 是采集难度最大的事件,全埋点的解决方案基本也是围绕着如何采集 AppClick是采集难度最大的事件,全埋点的解决方案基本也是围绕着如何采集 AppClick是采集难度最大的事件,全埋点的解决方案基本也是围绕着如何采集AppClick事件来进行的。

#AppClick事件的整体解决思路,就是要找到那个被点击的控件处理逻辑,然后再利用一定的技术原理,对原处理逻辑进行"拦截",或者在原处理逻辑的执行前面或执行后面"插入"相应的埋点代码逻辑,从而达到自动埋点的效果。

拦截的原理,参考Android时间处理机制来进行。 插入的原理,参考编译器对Java代码的整体处理流程来进行。 JavaCode -> .java -> .class -> .dex,选择在不同的阶段"插入"埋点代码,所采用的技术或者原理也不尽相同。

$AppViewScreen全埋点方案

对于Activity,就是onResume方法,我们只要自动地在onResume里触发 A p p S c r e e n 事件,即可解决 AppScreen事件,即可解决 AppScreen事件,即可解决AppViewScreen事件的全埋点。

关键技术:Application.ActivityLifecycleCallbacks

可提供全局Activity的监控。

$ AppStart、$AppEnd全埋点方案

归根结底就是判断当前应用程序是处于前台还是处于后台,Android系统本身没有给应用程序提供相关的接口来判断这些状态。

应用程序如果有多个进程该如何判断是处于前台还是后台 ?

通过IPC机制实现数据共享

应用程序如果发生崩溃或者被强杀了该如何判断该应用程序是处于前台还是处于后台 ?

引入Session的概念:对于一个应用程序,当它的一个页面退出了,如果在30S之内没有新的页面打开,我们就任务这个应用程序处于后台 (触发$AppEnd事件)。 当它的一个页面显示出来了,如果与上一个页面的退出时间的间隔超过了30s,我们就认为这个应用程序重新处于前台了 (触发了 $AppStart 事件)。

30s之内没有新的页面进来 (按了Home键/返回键退出应用程序、应用程序发生崩溃、应用程序被强杀),则会触发 A p p E n d ,或者在下次启动的时候补发一个 AppEnd,或者在下次启动的时候补发一个 AppEnd,或者在下次启动的时候补发一个AppEnd事件。

$ AppClick全埋点方案1:代理View.OnClickListener

android.R.id.content

android.R.id.content对应的视图是一个FrameLayout布局,它目前就只有一个子元素,就是setContent时候的View。

需要注意 在不同的SDK版本下,android.R.id.content所指的显示区域有所不同。

  • SDK 14+(Native ActionBar):该显示区域指的是ActionBar下面的那部分。
  • Support Library Revision lower than 19: 使用AppCompat,则显示区域包含ActionBar
  • Support Library Revision 19(or greater):使用AppCompat,则显示区域不包含ActionBar,即与第一种情况相同。

原理概述

通过ActivityLifecycleCallbacks的onResume方法,我们可以取到当前正在显示的Activity实例,通过activity.findViewById(android.R.id.content)可以拿到id为content的这个FrameLayout,然后,再逐层遍历这个RootView,并判断当前View是否设置了mOnClickListener对象,如果已设置mOnClickListener对象并且mOnClickListener又不是我们自定义的WrapperOnClickListener类型,则通过WrapperOnClickListener代理当前View设置的mOnClickLIstener。

引入DecorView

当前方案是无法采集MenuItem控件的点击事件的,这是因为我们通过android.R.id.content取到的RootView是不包含Activity标题栏的,也就是不包括MenuItem的父容器。 我们可以使用DecorView来解决

activity.getWindow().getDecorView()

这样,我们就可以遍历到MenuItem了。

@Overridepublic void onActivityResumed(@NonNull Activity activity) {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {delegateViewsOnClickListener(activity,activity.getWindow().getDecorView());}}, 300);}

引入ViewTreeObserver.OnGlobalLayoutListener

当前方案还有一个问题,无法采集onResume()生命周期之后动态创建的View点击事件。 可以通过ViewTreeObserver.OnGlobalLayoutListener来解决这个问题。

OnGlobalLayoutListener是ViewTreeObserver的一个内部接口。当一个视图树的布局发生变化时,可以被ViewTreeObserver.OnGlobalLayoutListener监听到。

所以,基于这个原理,我们可以给当前Activity的RootView也添加一个ViewTreeObserver.OnGlobalLayoutListener监听器,当收到onGlobalLayout方法回调时(即视图树的布局发生变化,比如新的View被创建),我们重新去遍历一次RootView,然后找到那些没有被代理过的mOnClickListener对象的View并进行代理,即可解决上面提到的问题。

另外,关于ViewTreeObserver.OnGlobalLayoutListener监听器,建议在页面退出的时候remove掉,即在onStop的时候调用removeOnGlobalLayoutListener方法。

由于该方案遍历的是Activity的RootView,所以游离于Activity之上的点击是无法采集的,比如Dialog、PopupWindow等。

可以采用代码埋点的方法辅助解决这个问题。

对于Dialog,可以通过dialog.getWindow().getDecorView()拿到它的RootView,然后手动触发遍历并代理即可。

public void trackDialog(final Activity activity,final Dialog dialog){if (dialog.getWindow() != null) {dialog.getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {SensorsDataPrivate.delegateViewsOnClickListener(activity, dialog.getWindow().getDecorView());}});}
}

然后在Dialog创建之后show之前调用即可。

$ AppClick全埋点方案2:代理Window.Callback

Window.Callback是Window类的一个内部接口,该接口包含了一系列类似于dispatchXXX和onXXX的接口。 当Window接收到外部状态改变的通知时,就会回调其中的相应方法。 比如,当用户点击某个控件时,就会回调Window.Callback中的dispatchTouchEvent(MotionEvent event)方法。

原理概述

我们可以在ActivityLifecycleCallbacks的onActivityCreated方法回调中,拿到当前正在显示的Activity对象,通过activity.getWindow().getCallback()就可以拿到当前对应的Windo.Callback对象,最后通过自定义的WrapperWindowCallback代理这个Window.Callback对象。然后,在WraperWindowCallback的dispatchTouchEvent()方法中通过MotionEvent参数找到那个被点击的View对象,并插入埋点代码,最后再调用缘由Window.Callback的dispatchTouchEvent()方法,即可达到"插入"埋点代码的效果。

缺点

每次点击时,都要去遍历一次RootView,所以效率相对来说较低,对应用程序的整体性能影响也比较大。 无法采集像Dialog、PopupWindow等游离于Activity之外的控件的点击事件。

$ AppClick全埋点方案3:代理View.AccessibilityDelegate

Accessibility

Accessibility,即辅助功能。

View.AccessibilityDelegate

View.performClick()源码中,系统会先调用当前View已设置的mOnClickListener对象的onClick()方法,然后再调用sendAccessibilityEvent()方法,在sendAccessibilityEvent()方法的内部实现里,其实是调用mAccessibilityDelegate对象的sendAccessibilityEvent方法,并传入当前View对象和AccessibilityEvent.TYPE_VIEW_CLICKED参数。

所以,我们只需要代理View的mAccessibilityDelegate对象,当一个View被点击时,在原有mOnClickListener对象的相应方法执行之后,我们就能收到这个点击的回调。

原理概述

在ActivityLifecycleCallbacks的onActivityResumed方法中,我们可以通过activity.getWindow().getDecorView()方法拿到当前Activity的RootView,通过rootView.getViewTreeObserver()对象,然后再通过addOnGlobalLayoutListener()方法给RootView注册ViewTreeObserverOnGlobalLayoutListener监听器,这样,可以在当前Activity的视图状态发生改变时去主动遍历一次RootView。 并且,用我们自定义的WraperAccessibilityDelegate代理当前View的mAccessibilityDelegate对象。在我们自定义的WraperAccessibilityDelegate类中的sendAccessibilityEvent()方法实现里,我们先调用原有的mAccessibilityDelegate对象的sendAccessibilityEvent方法,然后再插入埋点代码,其中host就是被点击的View对象,从而可以做到自动埋点的效果。

缺点

辅助功能需要用户手动启动,而且在部分ROM上辅助功能可能会失效。 无法采集Dialog、PopupWindow等游离于Activity之外的控件的点击事件。

$ AppClick全埋点方案4:透明层

该方案主要用到了Android系统事件处理机制方面的知识。

原理概述

onTouchEvent是在View中定义的一个方法,用来处理传递到View的手势事件。 该方案就是基于View的onTouchEvent方法来实现的。

我们可以自定义一个透明的View,然后添加到每个Activity的最上层。这样,每当用户点击任何控件时,直接点击的其实就是我们的这个自定义的透明View。 重写这个View的onTouchEvent方法,就可以根据MontionEvent里的点击坐标信息(x,y),在当前Activity的RootView里找到实际上被点击的那个View对象。 找到被点击的View之后,我们再通过自定义的WrapperOnClickListener代理当前View的mOnClickListener对象。 在WrapperOnClickListener的onClick方法里,先调用View原有的mOnClickListener.onClick,然后再插入埋点代码,就能达到自动埋点的目的了。

缺点

无法采集Dialog、PopupWindow的点击事件 每次点击都要遍历一次RootView,效率比较低

$ AppClick全埋点方案5:AspectJ

AOP是Aspect Oriented Programming 的缩写,即面向切面编程。

AspectJ

AspectJ最核心的模块就是它提供的ajc编译器,它其实就是将AspectJ的代码在编译器插入到目标程序当中。

自定义Gradle Plugin

原理概述

对于Android系统中的View,它的点击处理逻辑,都是通过设置相应的listener对象重写相应的回调方法实现的。 我们可以把AspectJ的处理脚本放到我们自定义的插件里,然后编写相应的切面类,再定义合适的PointCut用来匹配我们的织入方式 (listener对象的相应回调方法),比如android.view.View.OnClickListener的onClick(android.view.View)方法,就可以在编译期间埋入埋点代码,从而达到自动埋点的效果。

缺点

由于定义的切点依赖编程语言,目前该方案无法兼容Lambda语法。

$ AppClick全埋点方案6:ASM

实现一套Transform,去遍历所有.class文件的所有方法,然后进行修改 (在特定listener的回调方法中插入埋点代码),最后再对原文件进行替换,即可达到插入代码的目的。

关键技术

Gradle Transform是Android官方提供给开发者在项目构建阶段 (即由.class到.dex转换期间)用来修改.class文件的一套标准API。目前比较经典的应用是字节码插桩、代码注入等。

ASM

ASM是一个功能比较齐全的Java字节码操作与分析框架哦度会使用ASM,我们可以动态生成类或者增强既有类的功能。

原理概述

我们可以自定义一个Gradle Plugin,然后注册一个Transform对象。在transform方法里,可以分别编列目录和jar包,然后我们就可以遍历当前应用程序所有的.class文件,然后再利用ASM框架的相关API,去加载相应的.class文件、解析.class文件,就可以找到满足特定条件的.class文件和相关方法,最后去修改相应的方法以动态插入埋点字节码,从而达到自动埋点的效果。

缺点

目前来看,实现全埋点,使用ASM框架是一个相对完美的选择,暂时没有发现有什么缺点。

$ AppClick全埋点方案7:Javassist

Java字节码以二进制的形式存储在.class文件中,每一个.class文件包含一个Java类或接口。Javaassist框架就是一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方法有深入的了解。

Javassist可以绕过编译,直接操作字节码,从而实现代码的注入。 所以,使用Javassist框架的最佳时机就是在构建工具Gradle将源文件编译成.class文件之后,在将.class打包成.dex文件之前。

原理概述

在自定义的Plugin里,我们可以注册一个自定义的Transform,从而可以分别对当前应用程序的所有源码目录好jar包进行遍历。在遍历过程中,利用Javassist框架的API可以对满足特定条件的方法进行修改,比如插入相关埋点代码。 整个原理与使用ASM框架类似,此时只是把操作.class文件的框架由ASM换成Javassist了。

$ AppClick全埋点方法8:AST

APT

APT (Annotation Processing Tool),即注解处理器,是一种处理注解的工具。确切来说,它是javac的一个工具,用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,以生成.java文件作为输出。简单来说,就是在编译器通过注解生成.java文件。

AST

AST是Abstract Syntax Tree的缩写,即抽象语法树,是编译器对代码的第一步加工之后的结果,是一个树形式表示的源代码。源代码的每个元素映射到一个字节或子树。

Java的编译过程可以分为三个阶段: 第一阶段:所有的源文件会被解析成语法树。 第二阶段:调用注解处理器,即APT模块。如果注解处理器产生了新的源文件,新的源文件也要参与编译。 第三阶段:语法树会被分析并转化为类文件。

原理概述

编辑器对代码处理的流程大概是

JavaTXT -> 词语法分析 -> 生成AST -> 语义分析 -> 编译字节码

通过AST,可以达到修改源代码的功能。

在自定义注解处理器的process方法里,通过roundEnvironment.getRootElements方法可以拿到所有的Element对象,通过trees.getTree(element)方法可以拿到对应的抽象语法书(AST),然后我们自定义一个TreeTranslator,在visitMethodDef里即可对方法进行判断。如果是目标处理方法,则通过AST框架的相关API即可插入埋点代码,从而实现全埋点的效果。

缺点

  • com.sun.tools.javac.tree相关API语法晦涩,理解难度大,要求有一定的编译原理基础。
  • APT无法扫描其他module,倒是AST无法处理其他module
  • 不支持Lambda语法
  • 带有返回值的方法,很难把埋点代码插入到方法之后

本文主要是对Android无埋点技术的简答解析,更多的Android核心技术可以参考《Android核心技术手册》点击可以查看详细的类目。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/175441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础学编程轻松学编程,分享一款中文编程工具,编程构件简介

零基础学编程轻松学编程,分享一款中文编程工具,编程构件简介 中文编程开发语言工具编辑区界面截图如上图。 给大家分享一款中文编程工具 零基础轻松学编程,不需英语基础,编程工具可下载。 这款工具不但可以连接部分硬件&#…

数据库应用:Ubuntu 20.04 安装MongoDB

目录 一、理论 1.MongoDB 二、实验 1.Ubuntu 20.04 安装MongoDB 三、问题 1.Ubuntu Linux的apt 包管理器更新安装软件报错 2.Ubuntu20.04安装vim报错 3.Ubuntu20.04如何更换阿里源 4.Ubuntu22.04如何更换阿里源 一、理论 1.MongoDB (1)概念 …

6、Qt使用Log4Qt日志

一、知识点 1、Log4Qt有三部分 logger:负责捕获日志信息 layout:负责使用不同的样式输出日志 appender:负责输出信息到不同的目的地,比如数据库、文件、控制台等等 2、 日志级别如下,从上往下依次递增 ALL:…

css之svg 制作圆及旋转

1.代码 <template><div class"loading-box"><div class"circle-container"><svg width"75" height"75" class"move-left-to-right"><circle cx"37.5" cy"37.5" r"26&…

高端影像仪:打破微小产品测量局限

在现代工业生产中&#xff0c;影像仪以CCD数位影像为基石&#xff0c;将计算机屏幕测量技术与空间几何运算的能力融为一体&#xff0c;可以用于测量微小产品的各种尺寸和形状&#xff0c;为生产过程中的质量控制提供重要的参考依据。 影像仪产品内置高精度光学电动双倍镜头&am…

什么是动态住宅IP?它有什么用途?

随着网络的迅速发展&#xff0c;许多人对代理IP已经有了比较深刻的认识&#xff0c;并且广泛地运用到了各自的业务中&#xff0c;尤其在跨境的相关业务中表现尤其卓越。对于代理IP的类别&#xff0c;也需要根据自己的业务类型具体选择最合适的&#xff0c;那么今天IPFoxy就给大…

网页设计--第5次课后作业

1、快速学习JavaScript的基本知识第1-10章 JavaScript入门 - 绿叶学习网 2、使用所学的知识完成以下练习。需求如下3个&#xff1a; 1&#xff09;点亮灯泡 2&#xff09;将所有的div标签的标签体内容后面加上&#xff1a; very good 3&#xff09;使所有的复选框呈现被选…

【javaWeb】HTTP协议

HTTP (全称为 “超文本传输协议”) 是一种应用非常广泛的应用层协议 HTTP 是一个文本格式的协议. 可以通过 Chrome 开发者工具或者 Fiddler 抓包, 分析 HTTP 请求/响应的细节. 上图是通过Fiddler对访问百度搜索页时抓取的一个http协议的包。 观察抓包结果,可以看到,当前 http…

myAGV 2023 Pi 全新升级!

Introduction 在高速发展的自动化和机器人技术领域&#xff0c;我们的公司一直致力于提供高效、灵活且可靠的轻量级机械臂解决方案。然而&#xff0c;我们也深知&#xff0c;传统的机械臂需要固定在一个地方&#xff0c;这在一定程度上限制了其在空间上的应用范围。为了突破这一…

【论文阅读】ActiveNeRF:通过不确定性估计候选新视图

【论文阅读】ActiveNeRF: Learning where to See with Uncertainty Estimation Abstract1 Introduction3 Background4 NeRF with Uncertainty Estimation5 ActiveNeRF5.1 Prior and Posterior Distribution5.2 Acquisition Function5.3 Optimization and Inference 6 Experimen…

ECharts配置项手册了解及使用

1&#xff1a;点击文档然后选择配置项手册或者&#xff1a;Documentation - Apache ECharts 2&#xff1a;

中间件安全:Weblogic 漏洞.(使用工具可以利用多种类型漏洞)

中间件安全&#xff1a;Weblogic 漏洞.&#xff08;使用工具可以利用多种类型漏洞&#xff09; WebLogic 是美国 Oracle 公司出品的一个 application server&#xff0c;确切的说是一个基于 JAVA EE 架构的中间件&#xff0c;WebLogic 是用于开发、集成、部署和管理大型分布式…

亚信科技AntDB数据库与库瀚存储方案完成兼容性互认证

近日&#xff0c;亚信科技AntDB数据库与苏州库瀚信息科技有限公司自主研发的RISC-V数据库存储解决方案进行了产品兼容测试。经过双方团队的严格测试&#xff0c;亚信科技AntDB数据库与库瀚数据库存储解决方案完全兼容、运行稳定。除高可用性测试外&#xff0c;双方进一步开展TP…

基于springboot-“有光”摄影分享网站系统(2023年☆全网唯一)【附源码|数据库|表结构|万字文档(LW)|技术文档|说明文档】

主要功能 前台登录&#xff1a; 注册用户&#xff1a;用户账号、密码、姓名、手机号、身份证号、性别、邮箱 用户&#xff1a; ①首页、公告资讯展示、图片素材展示、活动展示、视频素材展示、查看更多 ②论坛、发布帖子、活动、活动标题、活动类型、公告资讯、公告标题、公告…

C#,数值计算——插值和外推,径向基函数插值(RBF_interp)的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// 径向基函数插值 /// Object for radial basis function interpolation using n points in dim /// dimensions.Call constructor once, then interp as many times as desir…

2023年亚太杯APMCM数学建模大赛B题玻璃温室小气候调控

2023年亚太杯APMCM数学建模大赛 B题 玻璃温室小气候调控 原题再现 温室作物的产量受各种气候因素的影响&#xff0c;包括温度、湿度和风速[1]。其中&#xff0c;适宜的温度和风速对植物生长至关重要[2]。为了调节玻璃温室内的温度、风速等气候因素&#xff0c;在温室设计中常…

vue3+ts 指令简写

<template><div class"btns"><button v-has-show"shop:create">创建</button><button v-has-show"shop:edit">编辑</button><button v-has-show"shop:delete">删除</button></div…

集软件库、论坛、社区、工具箱、积分商城、会员体系、在线商城一体的后台系统+HBuilderX 前端软件社区

HBuilderX前端软件社区thinkphp后端源码 搭建好后台 在前端找到 util 这个文件 把两个js文件上面的填上自己的域名 电脑需要下载&#xff1a;HBuilderX 下载后 登录账号 没有账号就注册账号 然后上传文件 在选择你上传的文件 即可 打包选择 “发行” 可以打包app h5等等 …

开源语音大语言模型——Qwen-Audio

论文链接&#xff1a;https://arxiv.org/pdf/2311.07919.pdf 开源代码&#xff1a;https://github.com/QwenLM/Qwen-Audio 一、背景 大型语言模型&#xff08;LLMs&#xff09;由于其良好的知识保留能力、复杂的推理和解决问题能力&#xff0c;在通用人工智能&#xff08;AGI…

Matplotlib线形图的创建_Python数据分析与可视化

线形图的创建 绘制线形图设置颜色和风格设置坐标轴上下限设置图形标签 绘制线形图 在所有图形中&#xff0c;最简单的应该就是线性方程y f (x) 的可视化了。来看看如何创建这个简单的线形图。要画Matplotlib图形时&#xff0c;都需要先创建一个图形fig 和一个坐标轴ax。创建图…