关于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)


相关推荐

  • mac navicat 激活码【永久激活】

    (mac navicat 激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • Vue中,methods中调用filters里的过滤器

    Vue中,methods中调用filters里的过滤器需求:vue中,除了在模板中使用过滤器,有时候,methods中也需要使用filters中的过滤器! this.$options.filters[filter](…args)//这种方法很简单,也很实用打印 this.$options.filters报错,打印this.$options.filters.myFilter正常,显示为一个function原文地址:https:/…

  • navicat15永久注册激活码(最新序列号破解)

    navicat15永久注册激活码(最新序列号破解),https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • IT技术开发人员获得成功的六大步骤

    IT技术开发人士成功的6大步骤一个前辈在移民加拿大后写的文章,写得不错,值得借鉴,转来给大家看看,也给自己序言:经过001多年的洗礼,认识了这里这么多的JJMMGGDD,前几天刚得到签证,无限感慨

    2021年12月25日
  • 什么是线程? [通俗易懂]

    什么是线程? [通俗易懂]所有重要的操作系统都支持进程的概念–独立运行的程序,在某种程度上相互隔离。线程有时称为轻量级进程。与进程一样,它们拥有通过程序运行的独立的并发路径,并且每个线程都有自己的程序计数器,称为堆栈

  • 介绍一下redis_redis sortedset

    介绍一下redis_redis sortedset想要操作redis,就需要与redis建立连接。就像操作MySQL一样,需要首先拿到数据库链接。进而,类似于MySQL的DataSource,ActiveMQ的pool,redis也提供了自己的pool–JedisPool。这些”池”理念是相通的,把你从繁琐的手动获取释放链接解放出来,减少了资源消耗,提高了性能。【1】先看源码源码如下:packageredis.clien…

发表回复

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

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