关于Android大数据收集,埋点统计的详细讲解以及案例代码分析附github代码

关于Android大数据收集,埋点统计的详细讲解以及案例代码分析附github代码关于Android大数据收集,埋点统计的详细讲解以及案例代码分析附github代码一、背景分析目前大数据的分析对一款成熟的APP来说至关重要,特别是商业性的APP和金融类的APP都会对用户的行为进行分析,所以在APP中集成大数据的收集就显得很重要。目前来说,第三方的数据收集也挺多的,像是友盟,AOP切面收集等等,但是他们就是简单的集成,如果说在某些极端的情况下,项目中禁止添加额外的辅助,例

大家好,又见面了,我是你们的朋友全栈君。

关于Android大数据收集,埋点统计的详细讲解以及案例代码分析附github代码

一、背景分析
目前大数据的分析对一款成熟的APP来说至关重要,特别是商业性的APP和金融类的APP都会对用户的行为进行分析,所以在APP中集成大数据的收集就显得很重要。目前来说,第三方的数据收集也挺多的,像是友盟,AOP切面收集等等,但是他们就是简单的集成,如果说在某些极端的情况下,项目中禁止添加额外的辅助,例如jar包,依赖库等等,这样我们就需要自己来手写代码了。写这篇博客之前也参考了许多网上的文章,很多是互相借鉴的,而且代码也不全,往往在我们参考思路到一半的时候就没有了,所以写此文章,来总结分析一下,本文参考了一些优秀博客,尾篇附录。
二、思路分析
一般我们进行大数据收集埋点会收集那些数据呢?一般有:点击事件的收集,下拉刷新的收集,Dialog弹出的收集,APP唤醒、挂起、启动等的收集。为了能使我们的项目达到低耦合,高内聚,以及方便我们后续的维护,所以我们写代码不能采用代码埋点的方式,也就是说哪里需要埋哪里的这种观点。所以我们就要进行封装。拿获取点击事件为例,我们想获取屏幕的点击事件,一般我们会想到监听用户的点击事件,也就是说,给控件设置上标识,我们通过监听点击事件的时候,获取到标识,根据标识在基类进行埋点,那么怎么获取到点击事件呢?同时我们又怎么能点击的时候不仅仅处理了我们的点击事件,还执行了我们的方法。这个时候我们的反射和动态代理思想就用到了。我们通过反射获取到点击事件,在通过代理在执行点击之后有执行了我们自己的方法,这样不就行了。可是问题又来了,每点击一次要遍历一遍View,还要走反射,是不是太消耗内存了,我们又会想到另外的一种方式,为什么不监听触摸事件呢,用户点击获取坐标,然后根据坐标判断位置,判断是哪个控件,不就好了。灵机一开,赶紧写代码,但是写着发现了两个问题,第一,ReecyclwerView根本获取不到条目,这样我们还不是整体封装。第二我们还是遍历了所有的ID,来判断的,最后为了解决问题,只能综合考虑,整体采用触摸事件统计的方式,采用数组,保存的方式来保存只需要遍历的ID即可。如果RecyclerView获取不到条目的埋点,只能通过设置标识来统计,这样我们传建一个基类,让需要通过反射获取埋点的来继承基类,这样,我们就不用遍历所有。
三、代码分析

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            View view = getCurrentFocus();
            if (needHideKeyboard(view)) {
                hideKeyboard(view.getWindowToken());
            }
        }

        if (ev.getAction() == MotionEvent.ACTION_UP) {
            int rawX = (int) ev.getRawX();
            int rawY = (int) ev.getRawY();
            for (int i = 0; i < bigDataActivityIdArrays.length; i++) {
                View view = findViewById(bigDataActivityIdArrays[i]);
                if (view == null) {
                    continue;
                }
                Rect rect = new Rect();
                view.getGlobalVisibleRect(rect);
                if (rect.contains(rawX, rawY)) {
                    switch (arrays[i]) {   //遍历添加的数组的id
// case R.id.login:
// ToastUtils.showLongToast("Activity埋点'');
// break;
                    }
                    return super.dispatchTouchEvent(ev);
                }
            }
            if (dispatchTouchEventTable != null) {
                dispatchTouchEventTable.point(rawX, rawY);
            }
            if (dispatchTouchEventLoadSuccess != null) {
                dispatchTouchEventLoadSuccess.loadSuccess();
            }
        }
        return super.dispatchTouchEvent(ev);
    }

回调的接口

    private DisPathTouchEvenTable dispatchTouchEventTable;

    public void setDispatchTouchEventTable(DisPathTouchEvenTable dispatchTouchEvenTable) {
        this.dispatchTouchEventTable = dispatchTouchEvenTable;
    }

    private DispatchTouchEventLoadSuccess dispatchTouchEventLoadSuccess;

    public void setDispatchTouchEventLoadSuccess(DispatchTouchEventLoadSuccess dispatchTouchEventLoadSuccess) {
        this.dispatchTouchEventLoadSuccess = dispatchTouchEventLoadSuccess;
    }

    public interface DispatchTouchEventLoadSuccess {
        void loadSuccess();
    }

    public interface DisPathTouchEvenTable {
        void point(int x, int y);
    }

添加需要埋点的id到数组里面:

 /** * 埋点记录 * 需要埋点的统计的添加到数组里面 */
    private void init() {
        Arrays = new Integer[]{R.id.login};
    }

如果说Activity里面没有获取到我们监听的控件,那么这时候我们会把我们触摸的坐标回调到fragment中,在fragment中我们进行判断
fragment的oncreate()中初始化我们需要埋点的控件id:

fragmentCollectIds = new Integer[]{ R.id.login, };

然后在onActivityCreated()中进行初始化:

 ((BaseActivity) getActivity()).setDispatchTouchEventTable(newBaseFragmentDisPathTouchEvenTable());

进行触摸埋点统计:

 private class BaseFragmentDisPathTouchEvenTable implements BaseActivity.DisPathTouchEvenTable { 
   
        @Override
        public void point(int x, int y) {
            for (int i = 0; i < bigDataFragmentCollectIds.length; i++) {
                if (mView != null && bigDataFragmentCollectIds.length != 0) {
                    View viewById = mView.findViewById(bigDataFragmentCollectIds[i]);
                    if (viewById == null) {
                        continue;
                    }
                    Rect rect = new Rect();
                    viewById.getGlobalVisibleRect(rect);
                    if (rect.contains(x, y)) {
                        switch (bigDataFragmentCollectIds[i]) {
                            case R.id.login:
                            //登录
                              break;
                        }
                    }
                }
            }
        }
    }

到这一步我们的触摸埋点就全部监听到了,这样我们就可以吧除了RecyclerView中的控件的触摸全部监听的到。
接着我们讲解fragment中控件的触摸怎么收集,按照我们上面的思路我们通过反射加上代理的方式进行统计,这样贴一下封装好的工具类:

/** * 点击事件收集类 * * @author wangxd * @date 2017/12/30 @time 20:09 */
public class ClickCollectionManager { 
   
    private static final String VIEW_CLASS = "android.view.View";
    private static final String ANDROID_VIEW_CLASS = "android.view.View$ListenerInfo";
    private static final String LISTENER_INFO = "mListenerInfo";
    private static final String ON_CLICK_LISTENER = "mOnClickListener";

    /** * @param activity * @param onClickListener */
    public static void hookListener(Activity activity, OnClickListener onClickListener) {
        if (activity != null) {
            View decorView = activity.getWindow().getDecorView();
            getView(decorView, onClickListener);
        }
    }

    /** * 递归遍历 * * @param view * @param onClickListener */
    private static void getView(View view, OnClickListener onClickListener) {
        //递归遍历,判断当前view是不是ViewGroup,如果是继续遍历,直到不是为止
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                getView(((ViewGroup) view).getChildAt(i), onClickListener);
            }
        }
        viewHook(view, onClickListener);
    }

    /** * 通过反射将我们的代理类替换原来的OnClickListener * * @param view * @param onClickListener */
    private static void viewHook(View view, OnClickListener onClickListener) {
        try {
            Class viewClass = Class.forName(VIEW_CLASS); //创建反射View
            Field listenerInfoField = viewClass.getDeclaredField(LISTENER_INFO);//获得View属性mListenerInfo
            listenerInfoField.setAccessible(true);
            Object mListenerInfo = listenerInfoField.get(view);//view对象中的mListenerInfo
            if (mListenerInfo != null) {
                Class listenerInfoClass = Class.forName(ANDROID_VIEW_CLASS);//反射创建ListenerInfo
                Field onClickListenerField = listenerInfoClass.getDeclaredField(ON_CLICK_LISTENER);//获得ListenerInfo属性Onclick
                onClickListenerField.setAccessible(true);
                View.OnClickListener onListener = (View.OnClickListener) onClickListenerField.get(mListenerInfo);
                if (onListener != null) {
                    View.OnClickListener onClickListenerProxy = new OnClickListenerProxy(onListener, onClickListener);
                    onClickListenerField.set(mListenerInfo, onClickListenerProxy);//设置ListenerInfo属性,mOnClickListener为我们的代理listener
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public interface OnClickListener { 
   
        void beforeInListener(View view);

        void afterInListener(View view);
    }

    private static class OnClickListenerProxy implements View.OnClickListener { 
   
        private View.OnClickListener object;
        private ClickCollectionManager.OnClickListener onclickListener;

        public OnClickListenerProxy(View.OnClickListener object, ClickCollectionManager.OnClickListener onclickListener) {
            this.object = object;
            this.onclickListener = onclickListener;
        }

        @Override
        public void onClick(View view) {
            if (onclickListener != null) {
                onclickListener.beforeInListener(view);
            }
            if (object != null) {
                object.onClick(view);
            }
            if (onclickListener != null) {
                onclickListener.afterInListener(view);
            }
        }
    }
}

通过封装好的工具类,我们只需要把需要判断的条目设置TAG,就可以了,其实如果不考虑到性能的话我们,我们只通过这一个封装,只设置TAG就可以解决问题,但是需要注意,这里面有一个坑,fragment埋点的时候第一次点击没有效果,这就需要我们注意初始化问题了。
如果想在基类中做判断,只需要我们在基类进行封装,然后实现我们封装好的接口,这样我们不仅仅处理了点击事件,同时我们通过静态代理的方式,我们可以在点击前后进行数据的处理,这个基类封装就自行进行封装,或者直接参考demo进行封装即可。

四、挂起的监听思路以及实现
一般挂起我们指的是APP被挂载到了后台,也就是不可见不可用的状态,一般当APP进入到后台的时候,我们需要给一个友情的提示,所以对HOME键的监听就很很重要,下面看一下代码的具体封装,只需要我们集成到项目中就可以直接使用:

/** * Home键监听 * * @author wangxd * @date 2017/12/30 @time 20:50 */
public class HomeWatcher { 
   
    private Context mContext;
    private IntentFilter mIntentFilter;
    private OnHomePressedListener mListener;
    private InnerReceiver mReceiver;

    /** * 接口回调 */
    public interface OnHomePressedListener { 
   
        /** * 短按 */
        void onHomePressed();

        /** * 长按 */
        void onHomeLongPressed();
    }

    public HomeWatcher(Context context) {
        mContext = context;
        mIntentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    }

    /** * 设置监听 * * @param listener */
    public void setOnHomePressedListener(OnHomePressedListener listener) {
        mListener = listener;
        mReceiver = new InnerReceiver();
    }

    /** * 开始监听,注册广播 */
    public void startWatch() {
        if (mReceiver != null) {
            mContext.registerReceiver(mReceiver, mIntentFilter);
        }
    }

    /** * 停止监听,注销广播 */
    public void stopWatch() {
        if (mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
        }
    }

    /** * 广播接收者 */
    class InnerReceiver extends BroadcastReceiver {
        final String SYSTEM_DIALOG_REASON_KEY = "reason";
        final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
        final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
                String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
                if (reason != null) {
                    if (mListener != null) {
                        if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
                            mListener.onHomePressed();  // 短按home键
                        } else if (reason
                                .equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
                            mListener.onHomeLongPressed();// 长按home键
                        }
                    }
                }
            }
        }
    }
}

在Activity基类中进行初始化:

  /** * home键的监听 */
    private void initHomeKeyBoardListen() {
        mHomeWatcher = new HomeWatcher(this);
        mHomeWatcher.setOnHomePressedListener(new HomeWatcher.OnHomePressedListener() {
            @Override
            public void onHomePressed() {
                ToastUtils.showShortToast(getString(R.string.your_app_running_in_background));
            }

            @Override
            public void onHomeLongPressed() {
                ToastUtils.showShortToast(getString(R.string.your_app_running_in_background));
            }
        });
    }

在生命周期中进行判断,对Home键的监听状态进行改变:

@Override
    protected void onPause() {
        super.onPause();
        mHomeWatcher.stopWatch();
    }

@Override
protected void onResume() {
super.onResume();
mHomeWatcher.startWatch()
}

五、唤醒的监听思路以及实现
唤醒一般指的是我们进行APP从后到前台的过程,目前的项目中,今日头条、条目等都进行了监听处理,一般当我们从后后台唤醒的时候,都会首先显示广告,然后在进行内容的显示,怎么显示呢,看封装好的代码:

/** * 唤醒、首次启动 * * @author wangxd * @date 2017/12/30 @time 21:44 */
public class BaseTaskSwitch i { 
   
    private BaseTaskSwitchListener taskSwitchListener;
    private static BaseTaskSwitch mBaseTaskSwitch;
    private static boolean mIsFirstStart = true;
    public int mCount = 0;

    public static BaseTaskSwitch init(Application application) {
        if (null == mBaseTaskSwitch) {
            mBaseTaskSwitch = new BaseTaskSwitch();
            application.registerActivityLifecycleCallbacks(mBaseTaskSwitch);
        }
        return mBaseTaskSwitch;
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle bundle) {

    }

    @Override
    public void onActivityStarted(Activity activity) {
        if (mCount++ == 0) {
            taskSwitchListener.onTaskSwitchToForeground();
        }
    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (--mCount == 0) {
            taskSwitchListener.onTaskSwitchToBackground();
        }
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

    }

    @Override
    public void onActivityDestroyed(Activity activity) {

    }

    public interface BaseTaskSwitchListener { 
   
        void onTaskSwitchToForeground();

        void onTaskSwitchToBackground();
    }

    public void setOnTaskSwitchListener(BaseTaskSwitchListener onTaskSwitchListener) {
        this.taskSwitchListener = onTaskSwitchListener;
    }
}

我们只需要在Application中进行接口的初始化即可,这样我们不仅监听到了唤醒,同时我们还监听到了第一次的启动。剩下的几种数据收集,就交给大家了。详细的代码请看github有具体的代码封装。
五、github代码demo地址
github地址稍后上传附上,先把原理代码讲解一下,可以直接解决项目需求。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/145011.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • jvm的垃圾回收算法_jvm默认的垃圾回收器

    jvm的垃圾回收算法_jvm默认的垃圾回收器前言相比C语言,JVM虚拟机一个优势体现在对对象的垃圾回收上,JVM有一套完整的垃圾回收算法,可以对程序运行时产生的垃圾对象进行及时的回收,以便释放JVM相应区域的内存空间,确保程序稳定高效的运行,但在真正了解垃圾回收算法之前,有必要对JVM的对象的引用做一个简单的铺垫JVM对象可达性分析算法Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象扫描堆中的对象,看是否能够沿着GCRoot对象为起点的引用链找到该对象,找不到表示可以被回收想象一下,对象在什么情况下会被认为是垃圾对象呢?

    2022年10月25日
  • 游戏行业,室内设计,哪个3d建模师更有前景?工资是不是很高啊

    游戏行业,室内设计,哪个3d建模师更有前景?工资是不是很高啊游戏建模职业分类及发展:进入游戏建模行业你可以选择不同的发展方向,比如:(1)手绘3D美术设计师:制作纯手绘风格游戏的所有3D物品如:角色、道具、建筑、山体;(2)次世代3D美术设计师:制作写实次世代风格游戏的所有3D物品,如:角色、道具、建筑。(3)关卡设计师:根据游戏风格要求,使用模型资源,搭建3D游戏世界(4)模型师:制作3D打印、影视动画中的所有模型。如:角色、道具、建筑、山体。次世代美术设计师做什么?次世代游戏:“次世代游戏”指代和同类游戏相比下更加先进的游戏.

  • 杭州电信域名解析服务器,浙江电信的DNS是多少?

    杭州电信域名解析服务器,浙江电信的DNS是多少?1、湖州、嘉兴、绍兴、舟山202.101.172.362、金华、衢州、丽水、台州202.101.172.373、温州202.96.104.164、宁波202.96.104.155、杭州202.101.172.35行政区域名行政区域名是按照我国的各个行政区划分而成的,其划分标准依照国家技术监督局发布的国家标准而定,包括“行政区域名”34个,适用于我国的各省、自治区、直辖市,分别为:BJ-北京市;SH…

  • linux详解sudoers

    linux详解sudoers文章目录sudo使用sudo命令执行过程赋予用户sudo操作的权限/etc/sudoers内容详解编辑/etc/sudoers命令作用域通配符以及取消命令输入密码时有反馈修改sudo会话时间实践sudoers文件详解sudo使用Linux是多用户多任务的操作系统,共享该系统的用户往往不只一个。出于安全性考虑,有必要通过useradd创建一些非root用户,只让它们拥有不完全的权限;如有…

  • 30套JSP网站源代码合集「建议收藏」

    30套JSP网站源代码合集「建议收藏」JSP技术是以Java语言作为脚本语言的,JSP网页为整个服务器端的Java库单元提供了一个接口来服务于HTTP的应用程序。我收集了一些JSP开发的网站源代码,从实践中学习,希望对大家有用。资料名称下载地址网上购物系统(jsp+mysql+tomcat) http://down.51cto.com/data/54179jsp网

  • 使用java发邮件,附jar包

    使用java发邮件,附jar包本人小白,很多都是转载资料,只是学习研究一下!需要用到发邮件的朋友可以看一下,我们需要用到三个包,分别是commos-email.jar,javax.activation-1.1.0.jar,mail.jar,下面我已经给打家打包好了。点我进百度云下载,我们以qq邮箱为例子我们先去qq邮箱的设置里面给自己开通SMTP服务,然后记好你的授权码,下面会要用到,好了下面上代码。importj…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号