反射埋点方案: 全局点击埋点代理OnClickListener SDK 编写(1)

简介: 你在开发中是否遇到过这样的场景,当点击同一个dialog或者button的时候,如果暴击多次,该dialog或button的被点击行为会被瞬间执行多次,这时候有小伙伴可能要想了,我可以做一个view时间戳呀,让它延迟生效。

1681546832519.png

改不完的 Bug,写不完的矫情。公众号 杨正友 现在专注移动基础开发 ,涵盖音视频和 APM,信息安全等各个知识领域;只做全网最 Geek 的公众号,欢迎您的关注!本文相关代码Github地址autotrace_app_click,有帮助的话Star一波吧。

一. SDK业务背景

  你在开发中是否遇到过这样的场景,当点击同一个dialog或者button的时候,如果暴击多次,该dialogbutton的被点击行为会被瞬间执行多次,这时候有小伙伴可能要想了,我可以做一个view时间戳呀,让它延迟生效。

1681546876202.png可是你们有木有想过一个问题,这么做?是不是会绑定view?如果工程里面有10000个点击事件需要处理,那岂不是要改10000行代码,有没有一种优雅的方式能实现如下需求呢?

  • 1.1 可以设置全局控制点击行为的开关
  • 1.2 可以动态控制点击时间戳
  • 1.3butterknife,kotlin,lambada表达式,dialog,xml点击事件高度支持
  • 1.4 对不需要防重复点击的事件,可以通过注解进行额外逻辑处理
  • 1.5 可以借助沪江aop思想,也可以自己编写transform plugin实现
  • 1.6 将打点事件带到线上,线上分析用户行为

小朋友,你是否有很多问号?

本文介绍的内容会详细解释以上问题,并在最后给解答。稳住,别慌~

二. SDK全局点击控制原理

1681547199642.png

2.0.1 全局监控activity生命周期

  • 在app的onCreate()方法初始化SDK,然后在ActivityLifeCycleCallBack注册,这样就能监控activity所有生命周期方法了。

2.0.2 获取activity 对应的根视图rootview

  • 我们在该回调事件里面的对应生命周期方法如:onActivityPaused(Activity activity),通过 activity.findViewById(android.R.id.content)方法可以拿到整块区域所对应的rootview,其实就是framelayout

2.0.3 树形递归根视图rootview,织入埋点代码

  • 2.0.3.1 自定义OnClickListener派生类WrapperOnClickListener,实现OnClickListener接口
  • 2.0.3.2 逐层递归遍历rootview,判断当前view是否设置了mOnClickListener对象
  • 2.0.3.2.1 如果已经设置了mOnClickListener并且mOnClickListener不是我们自定义的WrapperOnClickListener类型,则通过WrapperOnClickListener代理当前view设置的mOnClickListener
  • 2.0.3.3WrapperOnClickListeneronClick方法里会先调用view的原有mOnClickListener处理逻辑
  • 调用埋点代码,实现"插入"埋点,达到自动埋点效果。

三. SDK埋点信息介绍

public interface ITrackClickEvent {
    /**
     * 控件的类型
     */
    String CANONICAL_NAME = "$element_type";
    /**
     * 控件的id,即android:id属性指定的值
     */
    String VIEW_ID = "$element_id";
    /**
     * 控件显示的文本信息
     */
    String ELEMENT_CONTENT = "$element_content";
    /**
     * 当前控件所属的 Activity 页面
     */
    String ACTIVITY_NAME = "$activity";
    /**
     * 点击空间行为事件名称
     */
    String APP_CLICK = "$AppClick";
    String APP_VERSION = "$app_version";
    String APP_NAME = "$app_name";
    String SCREEN_HEIGHT = "$screen_height";
    String SCREEN_WIDTH = "$screen_width";
    String ELEMENT_POSITION = "$element_position";
    String ELEMENT_ID = "$element_id";
    String ELEMENT_ELEMENT = "$element_element";
    String MODEL = "$model";
    String LIB_VERSION = "$lib_version";
    String OS = "$os";
    String OS_VERSION = "$os_version";
    String MANUFACTURER = "$manufacturer";
    String LIB = "$lib";
}

四. SDK风险点介绍

4.1 DataBinding绑定的函数的点击事件是无法采集的

  DataBinding框架给Button设置OnClickListener对象动作稍微晚于onActivityResumed 回调方法,DataBinding还没来得及给我们Button对象设置mOnClickListener对象,我们再遍历RootView的时,当前View不满足hasObClickListener的判断条件,因此没有去代理mOnClickListener对象,给出的解决方案是给DataBinding框架一点延迟事件处理设置mOnClickListener对象操作

 new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        delegateViewsOnClickListener(activity, activity.findViewById(android.R.content));
                    }
                },300);

4.2 mOnClickListener是无法采集MenuItem的点击事件

  我们通过android.R.content获取的RootView不包含Activity标题栏,其实也就是不包含MenuItem所对应的父容器的,自然当我们遍历RootView是无法获取MenuItem控件的,因此也无法代理mOnClickListener对象,间接导致MenuItem点击事件无法触发。我们可以借助DecorView来处理,那么什么是DecorView

1681547351806.png

官方解释是这样的:

The DecorView is the view that actually holds the window’s background drawable. Calling getWindow().setBackgroundDrawable() from your Activity changes the background of the window by changing the DecorView‘s background drawable. As mentioned before, this setup is very specific to the current implementation of Android and can change in a future version or even on another device.

  我的理解是: DecorView是整个Window最顶层View,他有且只有一个字孩子LinearLayout,其实就包括了通知栏,标题栏,内容显示栏,LinearLayout里面包括两个FrameLayout,第一个是标题栏显示的Title,第二个FrameLayout才是我们所说的android.R.content。所以针对我么上面提到的无法采集MenuItem点击事件的问题,我们只需要将activity.findViewById(android.R.content) 换成 activity.getWindow().getDecorView()就可以采集到MenuItem的点击事件了

       new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        delegateViewsOnClickListener(activity, activity.getWindow().getDecorView());
                    }
                },300);
            }

 但是上报的时候,我们发现却没有Button文本信息,这是为什么呢?element_content这个字段没问题呀,后面我查了一下,MenuView.ItemView的派生类是ActionMenuItemView,而非View,所以这里需要强转一下

 else if (view instanceof ActionMenuItemView) {
            text = ((ActionMenuItemView) view).getText().toString();
        } 

4.3 无法采集Button点击,在OnClickListener里动态创建一个Button,然后通过addView添加到页面上,这个动态添加的Button无法采其点击事件

ViewGroup rootView = findViewById(R.id.rootView);
        AppCompatButton button = new AppCompatButton(this);
        button.setText("动态创建的button");
        button.setOnClickListener(v -> {
        });
        rootView.addView(button);

当点击这个动态创建的 rootView ,当前方案是无法采集点击事件的。为啥会这样呢?这是因为我们在ActivityonResume之前去遍历整个rootView 并代理其mOnClickListener对象的,如果在onResume动态创建View当时肯定无法被遍历到的,后来我没没有再次遍历,所以它的mOnClickListener对就没有被代理过,因此点击控件是没有效果的,那么该怎么办呢?用OnGlobalLayoutListener来解决,什么是OnGlobalLayoutListener?官方解释是这样的:

Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.

我的理解是:  当一个View视图树发生改变的时候,我没给当前的View设置了OnGlobalLayoutListener监听器,就能回调 onGlobalLayout()方法,基于这个原理我们可以给我们的ActivityRootView注册一个这样的监听器,这样就能实时观察视图树布局的变化呢,我们重新遍历一次RootView,然后找到那些没有被代理过的OnGlobalLayoutListener对象View进行代理即可解决上面的问题

            private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;
            @Override
            public void onActivityCreated(@NonNull final Activity activity, @Nullable Bundle savedInstanceState) {
                onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        delegateViewsOnClickListener(activity,
                                SensorsDataHelper.getRootViewFromActivity(activity, false));
                    }
                };
            }
            @Override
            public void onActivityResumed(@NonNull final Activity activity) {
                SensorsDataHelper
                         .getRootViewFromActivity(activity, true)
                         .getViewTreeObserver()
                         .addOnGlobalLayoutListener(onGlobalLayoutListener);
            }
            @Override
            public void onActivityStopped(@NonNull Activity activity) {
                /*
                 * 移除顶层Activity的监听
                 */
                SensorsDataHelper.getRootViewFromActivity(activity, false)
                        .getViewTreeObserver()
                        .removeOnGlobalLayoutListener(onGlobalLayoutListener);
            }
            // -----------------省-------------------------
             }


相关文章
|
13天前
|
API 开发工具 C#
神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?
本文将介绍,ClkLog针对神策不支持全埋点的客户端实现用户访问基础统计分析 1。
神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?
|
4月前
|
存储 开发工具
通用快照方案问题之快照SDK的安装如何解决
通用快照方案问题之快照SDK的安装如何解决
40 0
|
5月前
|
Linux 调度 开发工具
云桌面系统镜像文件快速分发方案分享SDK
为了解决云桌面环境下批量升级系统镜像的效率问题,传统的1对多FTP/HTTP方式因服务器带宽限制导致传输慢。一种基于优化的Bittorrent协议的P2P解决方案被提出,利用P2P技术将文件切块并让终端互相分享,提高下载速度,尤其适合大文件如256GB分区镜像的分发。通过自定义IO接口、跳过校验、超大分块、多分块支持及局域网自建Tracker等功能,实现更快的传输和镜像更新,适用于系统镜像、游戏更新等领域。该方案已广泛应用于各行业,可根据不同场景定制优化。
57 1
|
5月前
|
缓存 算法 Java
反射埋点方案: 全局点击埋点代理OnClickListener SDK 编写
反射埋点方案: 全局点击埋点代理OnClickListener SDK 编写
30 0
|
6月前
|
负载均衡 算法 Java
SDK并发调用优化方案
SDK并发调用优化方案
|
11月前
|
供应链 安全 开发工具
供应链安全情报 | 恶意py包伪装代理SDK进行后门攻击,目标锁定python开发者
2023年11月28号,悬镜供应链安全实验室在Pypi官方仓库(https://pypi.org)监测到两起伪装成http和socks5代理SDK的开源组件投毒事件。python开发者一旦下载安装这些投毒Py包(libproxy、libsocks5),会触发执行Py包中的恶意代码,最终将导致开发者系统被投毒者植入恶意后门。
73 0
|
3月前
|
JavaScript 前端开发 Java
[Android][Framework]系统jar包,sdk的制作及引用
[Android][Framework]系统jar包,sdk的制作及引用
77 0
|
3天前
|
Java Linux API
Android SDK
【10月更文挑战第21天】
15 1
|
13天前
|
程序员 开发工具 Android开发
Android|使用阿里云推流 SDK 实现双路推流不同画面
本文记录了一种使用没有原生支持多路推流的阿里云推流 Android SDK,实现同时推送两路不同画面的流的方法。
38 7
|
3月前
|
开发工具 Android开发
解决Android运行出现NDK at /Library/Android/sdk/ndk-bundle did not have a source.properties file
解决Android运行出现NDK at /Library/Android/sdk/ndk-bundle did not have a source.properties file
163 4
解决Android运行出现NDK at /Library/Android/sdk/ndk-bundle did not have a source.properties file

热门文章

最新文章