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); } }