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

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

五. SDK拓展采集能力

5.0.1 怎样获取TextView的显示文本?

((TextView) view).getText().toString();

5.0.2 怎样获取ImageView的显示文本信息?

view.getContentDescription().toString();

5.0.3 怎样采集CheckBox的点击事件?

5.0.3.1 自定义WrapperOnCheckedChangeListener并且 织入埋点代码
public class WrapperOnCheckedChangeListener implements CompoundButton.OnCheckedChangeListener {
    private CompoundButton.OnCheckedChangeListener source;
    public WrapperOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener source) {
        this.source = source;
    }
    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
        //调用原有的 OnClickListener
        try {
            if (source != null) {
                source.onCheckedChanged(compoundButton, b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //插入埋点代码
        SensorsDataPrivate.trackViewOnClick(compoundButton);
    }
}
5.0.3.2 获取 CheckBox 设置的 OnCheckedChangeListener
    /**
     * CheckBox  代理事件
     *
     * @param view compoundButton代理ui
     */
    public void compoundButtonItemClick(View view) {
        if (view instanceof CheckBox){
            final CompoundButton.OnCheckedChangeListener onCheckedChangeListener =
                    SensorsDataHelper.getOnCheckedChangeListener(view);
            if (onCheckedChangeListener != null &&
                    !(onCheckedChangeListener instanceof MkOnCheckedChangeListener)) {
                ((CompoundButton) view).setOnCheckedChangeListener(
                        new MkOnCheckedChangeListener(onCheckedChangeListener));
            }
        }
    }

5.0.4 怎样采集RadioGroup的点击事件?

  • 5.0.4.1 确定 RadioGroup 设置的Listener类型OnGroupClickListener,然后自定义MkOnGroupClickListener
public class MkOnGroupClickListener implements ExpandableListView.OnGroupClickListener {
    private ExpandableListView.OnGroupClickListener source;
    public MkOnGroupClickListener(ExpandableListView.OnGroupClickListener source) {
        this.source = source;
    }
    @Override
    public boolean onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long id) {
        SensorsDataManager.trackAdapterView(expandableListView, view, groupPosition, -1);
        if (source != null) {
            source.onGroupClick(expandableListView, view, groupPosition, id);
        }
        return false;
    }
}
  • 5.0.4.2 判断当前ViewRadioGroup
 if (view instanceof RadioGroup) {
            try {
                RadioGroup radioGroup = (RadioGroup) view;
                Activity activity = SensorsDataHelper.getActivityFromView(view);
                if (activity != null) {
                    int checkedRadioButtonId = radioGroup.getCheckedRadioButtonId();
                    RadioButton radioButton = activity.findViewById(checkedRadioButtonId);
                    if (radioButton != null) {
                        text = radioButton.getText().toString();
                    }
                }
            } catch (Exception e) {
                Log.getStackTraceString(e);
            }
        }
  • 5.0.4.3如果反射已经设置了mOnClickListener,且mOnClickListener不为空,比去不是我们自定义的MkOnGroupClickListener
  • 通过MkOnGroupClickListener代理
     if (onGroupClickListener != null && !(onGroupClickListener instanceof MkOnGroupClickListener)) {
                   ((ExpandableListView) view).setOnGroupClickListener(new MkOnGroupClickListener(onGroupClickListener));
               }

5.0.5 怎样采集RattingBar的点击事件?

5.0.6 怎样采集 SeekBar 的点击事件?

    public void seekBarItemClick(View view) {
        final SeekBar.OnSeekBarChangeListener onSeekBarChangeListener =
                SensorsDataHelper.getOnSeekBarChangeListener(view);
        if (onSeekBarChangeListener != null &&
                !(onSeekBarChangeListener instanceof MkOnSeekBarChangeListener)) {
            ((SeekBar) view).setOnSeekBarChangeListener(
                    new MkOnSeekBarChangeListener(onSeekBarChangeListener));
        }
    }

5.0.7 怎样采集 Spinner 的点击事件?

5.0.8 怎样采集 ListView ,GridView 的点击事件?

  ListView ,GridViewAdapterView子类,设置代理逻辑判断都大同小异

  • 5.0.8.1 设置 ListView/ GridView的代理MkAdapterViewOnItemClick
    /**
     * 列表代理事件
     *
     * @param view gridView代理ui
     */
    public void gridViewItemClick(View view) {
        if (view instanceof ListView || view instanceof GridView){
            AdapterView.OnItemClickListener onItemClickListener = ((AdapterView) view).getOnItemClickListener();
            if (onItemClickListener != null && !(onItemClickListener instanceof MkAdapterViewOnItemClick)) {
                ((AdapterView) view).setOnItemClickListener(new MkAdapterViewOnItemClick(onItemClickListener));
            }
        }
    }
  • 5.0.8.1 自定义 MkAdapterViewOnItemClick源码如下:
public class MkAdapterViewOnItemClick implements AdapterView.OnItemClickListener {
    private AdapterView.OnItemClickListener source;
    public MkAdapterViewOnItemClick(AdapterView.OnItemClickListener source) {
        this.source = source;
    }
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
        if (source != null) {
            source.onItemClick(adapterView, view, position, id);
        }
        SensorsDataManager.trackAdapterView(adapterView, view, position);
    }
}

5.0.9 怎样采集 ExpandableListView的点击事件?

   ExpandableListViewAdapterView的子类,同时也是ListView的子类,ListView的点击事件分为GroupClickChildClick,它设置的监听器也有两种,这里我们就需要增加额外的方式去做处理啦,我们来看看 ExpandableListView的点击事件是如何处理的吧

    public static void trackAdapterView(AdapterView<?> adapterView, View view, int groupPosition, int childPosition) {
        try {
            final JSONObject jsonObject = new JSONObject();
            jsonObject.put(ITrackClickEvent.CANONICAL_NAME, adapterView.getClass().getCanonicalName());
            jsonObject.put(ITrackClickEvent.ELEMENT_ID, SensorsDataHelper.getViewId(adapterView));
            if (childPosition > -1) {
                jsonObject.put(ITrackClickEvent.ELEMENT_POSITION, String.format(Locale.CHINA, "%d:%d", groupPosition, childPosition));
            } else {
                jsonObject.put(ITrackClickEvent.ELEMENT_POSITION, String.format(Locale.CHINA, "%d", groupPosition));
            }
            StringBuilder stringBuilder = new StringBuilder();
            String viewText = SensorsDataHelper.traverseViewContent(stringBuilder, view);
            if (!TextUtils.isEmpty(viewText)) {
                jsonObject.put(ITrackClickEvent.ELEMENT_ELEMENT, viewText);
            }
            final Activity activity = SensorsDataHelper.getActivityFromView(adapterView);
            if (activity != null) {
                jsonObject.put(ITrackClickEvent.ACTIVITY_NAME, activity.getClass().getCanonicalName());
            }
            SensorsReporter.getSensorsDataApiInstance().track(ITrackClickEvent.APP_CLICK, jsonObject);
        } catch (Exception e) {
            Log.getStackTraceString(e);
        }
    }

那么我们是如何获取ExpandableListView的呢?这里主要体现在 $element_position这个参数上,我们需要根据是group或者child做不同的逻辑处理

        /**
     * ExpandableListView代理事件
     *
     * @param view ExpandableListView代理view
     */
    public void expandableItemClick(View view) {
       if (view instanceof  ExpandableListView){
           try {
               final Class viewClazz = Class.forName("android.widget.ExpandableListView");
               // ---------------------------------------Child---------------------------------------
               Field mOnChildClickListenerField = viewClazz.getDeclaredField("mOnChildClickListener");
               if (!mOnChildClickListenerField.isAccessible()) {
                   mOnChildClickListenerField.setAccessible(true);
               }
               ExpandableListView.OnChildClickListener onChildClickListener =
                       (ExpandableListView.OnChildClickListener) mOnChildClickListenerField.get(view);
               if (onChildClickListener != null && !(onChildClickListener instanceof MkOnChildClickListener)) {
                   ((ExpandableListView) view).setOnChildClickListener(new MkOnChildClickListener(onChildClickListener));
               }
               // ---------------------------------------Group---------------------------------------
               Field mOnGroupClickListenerField = viewClazz.getDeclaredField("mOnGroupClickListener");
               if (!mOnGroupClickListenerField.isAccessible()) {
                   mOnGroupClickListenerField.setAccessible(true);
               }
               ExpandableListView.OnGroupClickListener onGroupClickListener =
                       (ExpandableListView.OnGroupClickListener) mOnGroupClickListenerField.get(view);
               if (onGroupClickListener != null && !(onGroupClickListener instanceof MkOnGroupClickListener)) {
                   ((ExpandableListView) view).setOnGroupClickListener(new MkOnGroupClickListener(onGroupClickListener));
               }
           } catch (Exception e) {
               Log.getStackTraceString(e);
           }
       }
    }

先通过反射获取mOnGroupClickListener对象和mOnChildClickListener,如果listener不为空,并且不是我们需要的Listener类型,那么分别通过自定义的的MkOnGroupClickListenerMkOnChildClickListener区代理完成。

5.0.9.1 OnGroupClickListene源码如下:
/**
 * @author 杨正友(小木箱)于 2020/10/4 15 22 创建
 * @Email: yzy569015640@gmail.com
 * @Tel: 18390833563
 * @function description:
 */
public class MkOnGroupClickListener implements ExpandableListView.OnGroupClickListener {
    private ExpandableListView.OnGroupClickListener source;
    public MkOnGroupClickListener(ExpandableListView.OnGroupClickListener source) {
        this.source = source;
    }
    @Override
    public boolean onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long id) {
        SensorsDataManager.trackAdapterView(expandableListView, view, groupPosition, -1);
        if (source != null) {
            source.onGroupClick(expandableListView, view, groupPosition, id);
        }
        return false;
    }
}

在其onGroupClick方法内部,我们首先调用埋点代码,然后调用原有的listenerOnGroupClickListener方法,这样即可实现插入埋点代码的效果

5.0.9.2 OnChildClickClickListener源码如下:
/**
 * @author 杨正友(小木箱)于 2020/10/4 15 21 创建
 * @Email: yzy569015640@gmail.com
 * @Tel: 18390833563
 * @function description:
 */
public class MkOnChildClickListener implements ExpandableListView.OnChildClickListener {
    private ExpandableListView.OnChildClickListener source;
    public MkOnChildClickListener(ExpandableListView.OnChildClickListener source) {
        this.source = source;
    }
    @Override
    public boolean onChildClick(ExpandableListView expandableListView, View view, int groupPosition, int childPosition, long id) {
        SensorsDataManager.trackAdapterView(expandableListView, view, groupPosition, childPosition);
        if (source != null) {
            return source.onChildClick(expandableListView, view, groupPosition, childPosition, id);
        }
        return false;
    }
}

5.1.0 怎样采集 Dialog 的点击事件?

   目前这种全埋点的方案是无法采集activity上游离的view,如: Dialog ,因为无法遍历到被点击的view。对于这样的dialog,我们可以通过如下方式解决

    /**
     * Track Dialog 的点击
     * @param activity Activity
     * @param dialog Dialog
     */
    public void trackDialog(@NonNull final Activity activity, @NonNull final Dialog dialog) {
        if (dialog.getWindow() != null) {
            dialog.getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    SensorsDataManager.delegateViewsOnClickListener(activity, dialog.getWindow().getDecorView());
                }
            });
        }
    }

然后在Dialogshow之前调用即可

        AlertDialog alertDialog = new AlertDialog.Builder(this)
                .setNegativeButton("取消", (dialog, which) -> {})
                .setPositiveButton("确定", (dialog, which) -> {})
                .setTitle("小木箱").setMessage("一定要加油努力哦~").create();
        SensorsReporter.getSensorsDataApiInstance().trackDialog(this,alertDialog);

六. SDK缺陷

6.1 由于使用反射,效率比较低,对App的整体性能有一定影响,也可能会伴随着一些兼容问题

6.2 无法直接采集游离与activity上的view的点击,比如dialog,popuWindow

七. 参考资料

相关文章
|
22天前
|
API 开发工具 C#
神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?
本文将介绍,ClkLog针对神策不支持全埋点的客户端实现用户访问基础统计分析 1。
神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?
|
4月前
|
存储 开发工具
通用快照方案问题之快照SDK的安装如何解决
通用快照方案问题之快照SDK的安装如何解决
42 0
|
5月前
|
Linux 调度 开发工具
云桌面系统镜像文件快速分发方案分享SDK
为了解决云桌面环境下批量升级系统镜像的效率问题,传统的1对多FTP/HTTP方式因服务器带宽限制导致传输慢。一种基于优化的Bittorrent协议的P2P解决方案被提出,利用P2P技术将文件切块并让终端互相分享,提高下载速度,尤其适合大文件如256GB分区镜像的分发。通过自定义IO接口、跳过校验、超大分块、多分块支持及局域网自建Tracker等功能,实现更快的传输和镜像更新,适用于系统镜像、游戏更新等领域。该方案已广泛应用于各行业,可根据不同场景定制优化。
63 1
|
5月前
|
缓存 算法 Java
反射埋点方案: 全局点击埋点代理OnClickListener SDK 编写
反射埋点方案: 全局点击埋点代理OnClickListener SDK 编写
31 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的制作及引用
82 0
|
12天前
|
Java Linux API
Android SDK
【10月更文挑战第21天】
39 1
|
22天前
|
程序员 开发工具 Android开发
Android|使用阿里云推流 SDK 实现双路推流不同画面
本文记录了一种使用没有原生支持多路推流的阿里云推流 Android SDK,实现同时推送两路不同画面的流的方法。
41 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
166 4
解决Android运行出现NDK at /Library/Android/sdk/ndk-bundle did not have a source.properties file