Android 全埋点解决方案(2)

简介: Android 全埋点解决方案

Fragment

上面是Activity的埋点,关于fragment书中并没有讲解,不过我们也可以按照生命周期的方式来处理,比如在BaseFragment中进行统一埋点,又或者单独处理,正好演示一下手动埋点的操作。


示例:


 

private var mBeginTime = 0L
    override fun onResume() {
        super.onResume()
        mBeginTime = System.currentTimeMillis()
    }

首先在onResume中记录一下开始时间。

    override fun onHiddenChanged(hidden: Boolean) {
        super.onHiddenChanged(hidden)
        val blankFragment = this
        if (hidden) {
            val activity = activity as SecondActivity
            val jsonObject = JSONObject()
            jsonObject.put("useActivity", true)
            jsonObject.put("fragment", activity.javaClass.canonicalName + blankFragment.javaClass.canonicalName + "-custom"
            )
            SensorsDataAPI.getInstance().track("AppViewScreen", jsonObject, mBeginTime)
        }
    }

然后在onHiddenChanged中判断显示与否进行埋点,自定义数据,然后调用track方法进行埋点。


唯一标示的key用fragment表示,value用当前引用的activity全路径,加上fragment的全路径,最后加上自定义的参数,即可作为唯一标示。


以上即为页面埋点的主要代码,以及一些关键的代码细节,最后附Demo地址。


别忘了在Application中初始化埋点:


class App : Application() {
    override fun onCreate() {
        super.onCreate()
        //初始化埋点
        SensorsDataAPI.init(this)
    }
}

事件

一般来说就是点击事件,书中的解决方案挺多的,今天现在说说比较简单的,即代理模式

原理

拦截系统的点击事件,然后替换成我们自己的点击事件,然后在自己的点击事件中进行埋点操作。

通过获取页面的根布局,然后递归遍历出所有的view,并代理它们的click事件。

    public static void registerActivityLifecycleCallbacks(Application application) {
        application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;
            @Override
            public void onActivityCreated(final Activity activity, android.os.Bundle bundle) {
                final ViewGroup rootView = getRootViewFromActivity(activity, true);
                onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        delegateViewsOnClickListener(activity, rootView);
                    }
                };
            }
            @Override
            public void onActivityStarted(Activity activity) {
            }
            @Override
            public void onActivityResumed(Activity activity) {
                mBeginTime = System.currentTimeMillis();
                mCurrentActivity = activity;
                //trackAppViewScreen(activity);
                //添加视图树监听器
                final ViewGroup rootView = getRootViewFromActivity(activity, true);
                rootView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
            }
            @Override
            public void onActivityPaused(Activity activity) {
                trackAppViewScreen(activity);
            }
            @Override
            public void onActivityStopped(Activity activity) {
                //移除
                final ViewGroup rootView = getRootViewFromActivity(activity, true);
                rootView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
            }
            @Override
            public void onActivitySaveInstanceState(Activity activity, android.os.Bundle bundle) {
            }
            @Override
            public void onActivityDestroyed(Activity activity) {
            }
        });
    }
  • onActivityCreated中初始化代理方法,
  • onActivityResumed中添加代理事件,
  • onActivityStopped中移除代理事件。

我们在看看这个代理事件是怎么代理的:

    protected static void delegateViewsOnClickListener(final Context context, final android.view.View view) {
        if (context == null || view == null) {
            return;
        }
        //获取当前 view 设置的 OnClickListener
         final android.view.View.OnClickListener listener = getOnClickListener(view);
         //判断已设置的 OnClickListener 类型,如果是自定义的 WrapperOnClickListener,说明已经被 hook 过,防止重复 hook
         if (listener != null && !(listener instanceof WrapperOnClickListener)) {
             //替换成自定义的 WrapperOnClickListener
             view.setOnClickListener(new WrapperOnClickListener(listener));
         } else if (view instanceof CompoundButton) {
             final CompoundButton.OnCheckedChangeListener onCheckedChangeListener = getOnCheckedChangeListener(view);
             if (onCheckedChangeListener != null &&
                     !(onCheckedChangeListener instanceof WrapperOnCheckedChangeListener)) {
                 ((CompoundButton) view).setOnCheckedChangeListener(
                         new WrapperOnCheckedChangeListener(onCheckedChangeListener));
             }
         } else if (view instanceof RadioGroup) {
             final RadioGroup.OnCheckedChangeListener radioOnCheckedChangeListener =
                     getRadioGroupOnCheckedChangeListener(view);
             if (radioOnCheckedChangeListener != null &&
                     !(radioOnCheckedChangeListener instanceof WrapperRadioGroupOnCheckedChangeListener)) {
                 ((RadioGroup) view).setOnCheckedChangeListener(
                         new WrapperRadioGroupOnCheckedChangeListener(radioOnCheckedChangeListener));
             }
         } else if (view instanceof RatingBar) {
             final RatingBar.OnRatingBarChangeListener onRatingBarChangeListener =
                     ((RatingBar) view).getOnRatingBarChangeListener();
             if (onRatingBarChangeListener != null &&
                     !(onRatingBarChangeListener instanceof WrapperOnRatingBarChangeListener)) {
                 ((RatingBar) view).setOnRatingBarChangeListener(
                         new WrapperOnRatingBarChangeListener(onRatingBarChangeListener));
             }
         } else if (view instanceof android.widget.SeekBar) {
             final android.widget.SeekBar.OnSeekBarChangeListener onSeekBarChangeListener =
                     getOnSeekBarChangeListener(view);
             if (onSeekBarChangeListener != null &&
                     !(onSeekBarChangeListener instanceof WrapperOnSeekBarChangeListener)) {
                 ((android.widget.SeekBar) view).setOnSeekBarChangeListener(
                         new WrapperOnSeekBarChangeListener(onSeekBarChangeListener));
             }
         }
        //如果 view 是 ViewGroup,需要递归遍历子 View 并 hook
        if (view instanceof ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) view;
            int childCount = viewGroup.getChildCount();
            if (childCount > 0) {
                for (int i = 0; i < childCount; i++) {
                    android.view.View childView = viewGroup.getChildAt(i);
                    //递归
                    delegateViewsOnClickListener(context, childView);
                }
            }
        }
    }

可以看到除了click之外还有check等事件,其实原理都是想通的,我们来挑一个click来看看。

先获取OnClickListener,怎么获取呢,看getOnClickListener方法:

    private static android.view.View.OnClickListener getOnClickListener(android.view.View view) {
        boolean hasOnClick = view.hasOnClickListeners();
        if (hasOnClick) {
            try {
                Class viewClazz = Class.forName("android.view.View");
                Method listenerInfoMethod = viewClazz.getDeclaredMethod("getListenerInfo");
                if (!listenerInfoMethod.isAccessible()) {
                    listenerInfoMethod.setAccessible(true);
                }
                Object listenerInfoObj = listenerInfoMethod.invoke(view);
                Class listenerInfoClazz = Class.forName("android.view.View$ListenerInfo");
                Field onClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener");
                if (!onClickListenerField.isAccessible()) {
                    onClickListenerField.setAccessible(true);
                }
                return (android.view.View.OnClickListener) onClickListenerField.get(listenerInfoObj);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

通过反射拿到OnClickListener,然后再判断是否被代理,如果没有代理,就换成我们自己的Listener


view.setOnClickListener(new WrapperOnClickListener(listener));

看一下我们自定义的WrapperOnClickListener

/*public*/ class WrapperOnClickListener implements android.view.View.OnClickListener {
    private android.view.View.OnClickListener source;
    WrapperOnClickListener(android.view.View.OnClickListener source) {
        this.source = source;
    }
    @Override
    public void onClick(android.view.View view) {
        //调用原有的 OnClickListener
        try {
            if (source != null) {
                source.onClick(view);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //插入埋点代码
        SensorsDataPrivate.trackViewOnClick(view);
    }
}


目录
相关文章
|
6月前
|
XML Android开发 数据格式
android点击FrameLayout、LinearLayout等父布局没响应的原因以及解决方案
android点击FrameLayout、LinearLayout等父布局没响应的原因以及解决方案
174 2
|
6月前
|
安全 Shell Android开发
Android系统 init.rc sys/class系统节点写不进解决方案和原理分析
Android系统 init.rc sys/class系统节点写不进解决方案和原理分析
372 0
|
15天前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
1月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
77 7
|
2月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台解决方案
【9月更文挑战第27天】在移动应用开发的广阔天地中,安卓和iOS两大操作系统如同双子星座般耀眼。开发者们在这两大平台上追逐着创新的梦想,却也面临着选择的难题。如何在保持高效的同时,实现跨平台的开发?本文将带你探索跨平台开发的魅力所在,揭示其背后的技术原理,并通过实际案例展示其应用场景。无论你是安卓的忠实拥趸,还是iOS的狂热粉丝,这篇文章都将为你打开一扇通往跨平台开发新世界的大门。
|
1月前
|
Android开发
Android开发显示头部Bar的需求解决方案--Android应用实战
Android开发显示头部Bar的需求解决方案--Android应用实战
21 0
|
3月前
|
前端开发 开发工具 Android开发
探索安卓与iOS应用开发:跨平台解决方案的崛起
【8月更文挑战第27天】在移动设备日益普及的今天,安卓和iOS系统占据了市场的主导地位。开发者们面临着一个重要问题:是选择专注于单一平台,还是寻找一种能够同时覆盖两大系统的解决方案?本文将探讨跨平台开发工具的优势,分析它们如何改变了移动应用的开发格局,并分享一些实用的开发技巧。无论你是新手还是资深开发者,这篇文章都将为你提供有价值的见解和建议。
|
3月前
|
Android开发
Android编译出现Warning: Mapping new ns to old ns的解决方案
Android编译出现Warning: Mapping new ns to old ns的解决方案
329 3
|
3月前
|
前端开发 JavaScript Android开发
探索Android和iOS开发中的跨平台解决方案
【8月更文挑战第1天】随着移动应用市场的不断扩张,开发者面临一个共同的挑战——如何高效地为多个平台创建和维护应用程序。本文将深入探讨跨平台开发工具,特别是Flutter和React Native,通过比较它们的优势和限制,并辅以实际代码示例,揭示这些工具如何帮助开发者在保持高性能的同时,实现代码的最大化重用。
|
3月前
|
前端开发 JavaScript Android开发
安卓与iOS开发中的跨平台解决方案
【8月更文挑战第24天】在移动应用开发领域,安卓和iOS两大平台占据了主导地位。然而,为这两个平台分别开发和维护应用会带来额外的时间和成本。本文将探讨跨平台开发的概念、优势以及流行的跨平台框架,如React Native和Flutter,并分析它们如何解决多平台开发的挑战。