Android utils开源框架

Android 全埋点解决方案(一)

2021-06-26  本文已影响0人  旺仔_100

一、埋点方案总结
AppStart 、AppEnd 全埋点方案

二、埋点事件简介

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

在编译器对Java代码的处理流程中,可以采用不同的埋点方案。

                  APT                  AspectJ                ASM
JavaCode ----------.java ----------- .class ----------- .dex
                   AST                                             Javassit

四、全埋点综合方案考虑因素

静态代理明显优于动态代理,因为静态代理是在程序编译阶段处理的,不会对应用程序的整体性能有太大影响,而动态代理是在程序运行阶段发生的,所以对程序性能会有一定的影响。

五、埋点实现思路

    public interface ActivityLifecycleCallbacks {

        /**
         * Called as the first step of the Activity being created. This is always called before
         * {@link Activity#onCreate}.
         */
        default void onActivityPreCreated(@NonNull Activity activity,
                @Nullable Bundle savedInstanceState) {
        }

        /**
         * Called when the Activity calls {@link Activity#onCreate super.onCreate()}.
         */
        void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);

        /**
         * Called as the last step of the Activity being created. This is always called after
         * {@link Activity#onCreate}.
         */
        default void onActivityPostCreated(@NonNull Activity activity,
                @Nullable Bundle savedInstanceState) {
        }

        /**
         * Called as the first step of the Activity being started. This is always called before
         * {@link Activity#onStart}.
         */
        default void onActivityPreStarted(@NonNull Activity activity) {
        }

        /**
         * Called when the Activity calls {@link Activity#onStart super.onStart()}.
         */
        void onActivityStarted(@NonNull Activity activity);

        /**
         * Called as the last step of the Activity being started. This is always called after
         * {@link Activity#onStart}.
         */
        default void onActivityPostStarted(@NonNull Activity activity) {
        }

        /**
         * Called as the first step of the Activity being resumed. This is always called before
         * {@link Activity#onResume}.
         */
        default void onActivityPreResumed(@NonNull Activity activity) {
        }

        /**
         * Called when the Activity calls {@link Activity#onResume super.onResume()}.
         */
        void onActivityResumed(@NonNull Activity activity);

        /**
         * Called as the last step of the Activity being resumed. This is always called after
         * {@link Activity#onResume} and {@link Activity#onPostResume}.
         */
        default void onActivityPostResumed(@NonNull Activity activity) {
        }

        /**
         * Called as the first step of the Activity being paused. This is always called before
         * {@link Activity#onPause}.
         */
        default void onActivityPrePaused(@NonNull Activity activity) {
        }

        /**
         * Called when the Activity calls {@link Activity#onPause super.onPause()}.
         */
        void onActivityPaused(@NonNull Activity activity);

        /**
         * Called as the last step of the Activity being paused. This is always called after
         * {@link Activity#onPause}.
         */
        default void onActivityPostPaused(@NonNull Activity activity) {
        }

        /**
         * Called as the first step of the Activity being stopped. This is always called before
         * {@link Activity#onStop}.
         */
        default void onActivityPreStopped(@NonNull Activity activity) {
        }

        /**
         * Called when the Activity calls {@link Activity#onStop super.onStop()}.
         */
        void onActivityStopped(@NonNull Activity activity);

        /**
         * Called as the last step of the Activity being stopped. This is always called after
         * {@link Activity#onStop}.
         */
        default void onActivityPostStopped(@NonNull Activity activity) {
        }

        /**
         * Called as the first step of the Activity saving its instance state. This is always
         * called before {@link Activity#onSaveInstanceState}.
         */
        default void onActivityPreSaveInstanceState(@NonNull Activity activity,
                @NonNull Bundle outState) {
        }

        /**
         * Called when the Activity calls
         * {@link Activity#onSaveInstanceState super.onSaveInstanceState()}.
         */
        void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);

        /**
         * Called as the last step of the Activity saving its instance state. This is always
         * called after{@link Activity#onSaveInstanceState}.
         */
        default void onActivityPostSaveInstanceState(@NonNull Activity activity,
                @NonNull Bundle outState) {
        }

        /**
         * Called as the first step of the Activity being destroyed. This is always called before
         * {@link Activity#onDestroy}.
         */
        default void onActivityPreDestroyed(@NonNull Activity activity) {
        }

        /**
         * Called when the Activity calls {@link Activity#onDestroy super.onDestroy()}.
         */
        void onActivityDestroyed(@NonNull Activity activity);

        /**
         * Called as the last step of the Activity being destroyed. This is always called after
         * {@link Activity#onDestroy}.
         */
        default void onActivityPostDestroyed(@NonNull Activity activity) {
        }
    }

所以,我们可以直接在onResume里面做一个页面信息的统计。

2.Window.Callback
Window.callback是Window类的一个内部类。该接口包含了一系列类似于dispatchXXX和onXXX是接口。当用户点击某个控件时,就会回调Window.Callback中的dispatchTouchEvent(MotionEvent event)方法。

原理概述
在Application中初始化埋点sdk,然后注册监听Application.ActivityLifecycleCallbacks的onCreate,获取当前的Activity对象,通过activity拿到当前的window,activity.getWindow(),再通过window.getCallback()可以拿到Window.callback对象。然后使用自定义的WrapperWindowCallbcak代理这个Window.Callback对象。WrapperWindowCallbcak里面主要是重写了dispatchTouchEvent(MotionEvent event)方法,通过MotionEvent参数(点击的坐标)找到被点击的那个view,并插入埋点代码,最后在调用原有的dispatchTouchEvent(MotionEvent event)方法,即达到“插入”埋点代码的效果。
缺点:

3.Accesibility
辅助功能,Android系统通过辅助功能帮助一些功能损失的人更好的使用APP。我们知道,点击事件是会调用performClik()的,里面调用了mOnclickListener.onClick之后,还会调用到sendAccessibilityEvent(AccessbilityEvent.TYPE_VIEW_CLICKED),它里面是调用了mAccessbilityDelegate对象的sendAccessibilityEvent方法,并传入View对象和mAccessbilityDelegate.TYPE_VIEW_CLICKED参数。

原理概述
首先还是通过Application来监听activity的onResume方法,拿到DecordView,然后遍历所有view,设置自定义的SensorsDataAccessbilityDelegate代理当前View.sendAccessbiityEvent方法。在布局改变的时候做上面相同的操作(监听ViewTreeObserve)。在自定义SensorsDataAccessbilityDelegate中会调用原有的sendAccessibilityEvent方法,并判断是否是AccessbilityEvent.TYPE_VIEW_CLICKED类型,如果是,说明有点击事件,就做对应的代码插入。

缺点

4.透明层
原理概述
由于Android的事件分发都是会经过onTouchEvent方法。我们可以获取到当前的Activity,在布局的最上层添加一个自定义透明的view。重写view的onTouchEvent方法,获取当前点击的坐标,从RootView中找到点击的view,然后交给自定义的WrapperOnClickLitener处理。

5.AspectJ
AOP,面向切面编程,AspectJ实际上是其中的一种。对于ApsectJ不了解的可以自行了解。也需要使用到 Gradle plugin 不了解可以自行学习一下。

原理概述
我们可以把AspectJ的处理脚本放到我们自定义的插件里面,然后编写相应的切面类,再定义合适的PointCut用来匹配我们的织入目标的方法(listener对象的相应回调方法),比如Android.view.View.OnClickListener的onClick方法,就可以在编译期间插入埋点代码,从而达到自动埋点即全埋点的效果。
缺点

6.ASM
ASM可以在.class 文件打包成.dex文件之前修改.class文件。
Gradle Transform 可以在编译的时候遍历所有.class文件,并可以转换成所有需要的.class输出。

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

缺点:目前来看ASM是最完美的方法,没有什么缺点。

7.Javassist
java字节码以二进制的形式存储在.class文件中,每一个.class文件包含一个java类和接口。Javassist框架就是一个用来处理java字节码的类库。它可以在一个已编译好的类中添加新的方法,或者修改已有的方法,并且不需要对字节码方面有深入的了解。
javassist可以绕过编译,直接操作字节码,从而实现代码的注入。所以,使用javassist框架的最佳时机就是构建工具Gradle将源文件编译成.class文件之后,在将.class打包成.dex文件之前。

原理概述
跟上面ASM的原理一样,只是把ASM换成了javassist。

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

AST,是Abstract Syntax Tree的缩写,即“抽象语法树”,是编辑器对代码的第一步加工之后的结果,是一个树形式表示的源代码。源代码的每个元素映射到一个节点或者子树。
java的编译分为三个阶段:
第一阶段:所有的源文件都会被解析成语法树。
第二阶段:调用注解解析器,即APT模块。如果注解解析器产生了新的源文件,新的源文件也要参与编译。
第三个阶段:语法树会被分析并转化为类文件。

原理概述
JavaTXT-->词语法分析-->生成AST-->编译字节码、
通过操作AST,可以达到修改源代码的功能。
在自定义的注解解析器的process方法里,通过roundEnvironment.getRootElements()方法可以拿到所有的Element对象,通过tree.getTree(element)方法可以拿到对应的抽象语法树(AST),然后我们自定义一个TreeTranslator,在visitMethodDef里面即可对方法进行判断。如果是目标处理方法,则通过AST框架的相关API即可插入埋点代码,从而实现全埋点效果。
缺点

本文参考资料《Android 全埋点解决方案》

上一篇 下一篇

猜你喜欢

热点阅读