内容简介:埋点模块是一个完整的系统不可获取的一部分,无论是移动端,Web端还是后端(后端可能倾向于叫日志系统)。当然,现在也有很多第三方的埋点SDK,如友盟,接入也很简单,只需要几行代码即可使用。但大多都是侵入式,也就是说,在每个需要埋点的地方手动添加代码,这样耦合性太大,虽然可通过二次封装的方式,降低对这些SDK的依赖,但埋点统计模块耦合性仍然很大,为了解决这个问题,我们可通过无埋点方案来实现数据的收集过程。目前的埋点系统,主要分为2种:侵入式和无埋点。还有一种可视化的埋点方案,可认为是无埋点的一种,只是将设置埋
埋点模块是一个完整的系统不可获取的一部分,无论是移动端,Web端还是后端(后端可能倾向于叫日志系统)。当然,现在也有很多第三方的埋点SDK,如友盟,接入也很简单,只需要几行代码即可使用。但大多都是侵入式,也就是说,在每个需要埋点的地方手动添加代码,这样耦合性太大,虽然可通过二次封装的方式,降低对这些SDK的依赖,但埋点统计模块耦合性仍然很大,为了解决这个问题,我们可通过无埋点方案来实现数据的收集过程。
埋点系统类型
目前的埋点系统,主要分为2种:侵入式和无埋点。还有一种可视化的埋点方案,可认为是无埋点的一种,只是将设置埋点配置信息的过程做成了可视化而已。
侵入式埋点方案
在每个需要埋点的地方手动添加代码,优点是埋点准确,缺点也很明显,代码耦合度高,后期难以维护,不需要的埋点需要手动删除。
无埋点方案
无埋点方式是通过全局监听或AOP技术添加埋点的一种实现方案,开发者不需要在每个需要埋点的地方添加代码,只需要根据服务器分发的配置,获取相应的埋点数据即可。一方面代码耦合度低,同时灵活度也高,埋点数据直接由服务器控制。缺点就是没有侵入式埋点精准。
需要收集的数据
埋点的主要作用就是用于统计,对于埋点系统而言,最起码需要收集以下数据:
- 首次使用APP的新设备信息(精确控制还需要后端的配合);
- 页面的停留时长;
- View的交互事件(点击,滑动等);
- 辅助运营的各种数据(渠道号,地理位置,设备信息等)
埋点系统介绍
一个完整的埋点系统,应该至少包含以下三个模块:
网络模块
负责从服务器获取配置信息,上传埋点数据;
存储模块
缓存埋点配置信息,保存产生的埋点数据;
核心处理模块
负责收集埋点数据,并保存在存储模块中,根据配置在指定的时间上传数据。
无埋点系统的工作原理
在APP启动时,对无埋点SDK进行初始化,初始化的时候系统会先从配置中设置的URL请求埋点配置信息,然后对Activity,Fragment,View进行全局监听,当有相应的事件产生时,通过与配置信息比对,将需要收集的事件先将其保存在数据库中,到上传时机时,从数据库中获取数据,然后上传到服务器,上传成功后删除数据库的已上传的内容。
无埋点系统的实现
无埋点系统的主要目标是降低开发人员对埋点过程的参与度,其核心在于如何对事件进行全局监听以及如何生成埋点配置列表。
页面停留时长的监听
Android应用中的页面,也就Activity,Fragment两种。对于Activity,系统了全局的生命周期监听的方法,只需要在onResume中记录页面显示时的时间,在onPause中计算显示的时长,在onDestroy中将停留时长事件添加到数据库即可:
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
private Map<Context, Long> durationMap = new WeakHashMap<>();
private Map<Context, Long> resumeTimeMap = new WeakHashMap<>();
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
durationMap.put(activity, 0L);
}
@Override
public void onActivityResumed(Activity activity) {
resumeTimeMap.put(activity, System.currentTimeMillis());
}
@Override
public void onActivityPaused(Activity activity) {
durationMap.put(activity, durationMap.get(activity)
+ (System.currentTimeMillis() - resumeTimeMap.get(activity)));
}
@Override
public void onActivityDestroyed(Activity activity) {
long duration = durationMap.get(activity);
if (duration > 0) {
// 将事件添加到数据库
}
resumeTimeMap.remove(activity);
durationMap.remove(activity);
}
// 其他生命周期方法
});
复制代码
而对于Fragment,虽然com.app包中的Fragment没有提供生命周期的全局监听,但25.1.0之后的v4包中提供了全局监听,考虑到通常情况下都使用v4包中的Fragment,所以这里就直接使用了v4包中提供的方法来实现页面停留时长的监听。
FragmentManager fm = getSupportFragmentManager();
fm.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
private Map<Fragment, Long> resumeTimeMap = new WeakHashMap<>();
private Map<Fragment, Long> durationMap = new WeakHashMap<>();
@Override
public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) {
super.onFragmentAttached(fm, f, context);
resumeTimeMap.put(f, 0L);
}
@Override
public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {
super.onFragmentResumed(fm, f);
resumeTimeMap.put(f, System.currentTimeMillis());
}
@Override
public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
super.onFragmentPaused(fm, f);
durationMap.put(f, durationMap.get(f) + System.currentTimeMillis() - resumeTimeMap.get(f));
}
@Override
public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {
super.onFragmentDetached(fm, f);
long duration = durationMap.get(f);
if (duration > 0) {
// 将事件添加到数据库
}
resumeTimeMap.remove(f);
durationMap.remove(f);
}
}, true);
复制代码
上面的代码只是对Fragment生命周期的监听,但Fragment的可见性与生命周期并不总是一一对应的,如:Fragment show/hide或者ViewPager中的Fragment在切换时生命周期中的方法并不总是执行的,所以还需要监听与这两种情况对应的onHiddenChanged和setUserVisibleHint,但这两个方v4包中提供的全局监听中并没有,所以还需要特殊处理一下。这里提供两种解决方案:
- 提供一个LifycycleFragment, 对onHiddenChanged和setUserVisibleHint方法进行监听,业务层的Fragment继承此Fragment;
- 使用AOP,监听onHiddenChanged和setUserVisibleHint;
其中的处理逻辑与onResume和onPause中一致,具体参考后面的源码。
如果要对com.app包中的Fragment实现生命周期的全局监听,可采用以下两种方式:
- 写一个LifycycleFragment, 在其中实现生命周期的监听,业务层的Fragment实现时继承此Fragment;
- 使用透明的Fragment,透明的Fragment由于没有UI,其生命周期会与当前Fragment生命周期一致;
由于Fragment总是依赖于Activity存在的,所以其监听范围也是Activity级别的。在Activity的onCreate中对Fragment设置监听即可。
监听View的点击事件
View点击事件的监听可通过两种方式来实现:
基于AOP监听onClick方法;
这里以Aspect为例,实现onClick的全局监听:
@Aspect
public class ViewClickedEventAspect {
@After("execution(* android.view.View.OnClickListener.onClick(android.view.View))")
public void viewClicked(final ProceedingJoinPoint joinPoint) {
/**
* 保存点击事件
*/
}
}
复制代码
通过setAccessibilityDelegate实现:
关于setAccessibilityDelegate我们可先看一下View点击事件被执行的源码:
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
复制代码
从代码中可以看出,View的onClick被执行时,有个sendAccessibilityEvent被执行,我们再看一下sendAccessibilityEvent方法的代码:
public void sendAccessibilityEvent(int eventType) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
} else {
sendAccessibilityEventInternal(eventType);
}
}
复制代码
从代码可以看出,只需要为View设置了mAccessibilityDelegate,我们就可以监听View的onClick事件了。而设置View mAccessibilityDelegate的方法刚好是公开的,所以我们可使用此方式对View的点击事件进行监听,核心代码如下:
public class ViewClickedEventListener extends View.AccessibilityDelegate {
/**
* 设置Activity页面中View的事件监听
* @param activity
*/
public void setActivityTracker(Activity activity) {
View contentView = activity.findViewById(android.R.id.content);
if (contentView != null) {
setViewClickedTracker(contentView, null);
}
}
/**
* 设置Fragment页面中View的事件监听
* @param fragment
*/
public void setFragmentTracker(Fragment fragment) {
View contentView = fragment.getView();
if (contentView != null) {
setViewClickedTracker(contentView, fragment);
}
}
private void setViewClickedTracker(View view, Fragment fragment) {
if (needTracker(view)) {
if (fragment != null) {
view.setTag(FRAGMENT_TAG_KEY, fragment);
}
view.setAccessibilityDelegate(this);
}
if (view instanceof ViewGroup) {
int childCount = ((ViewGroup) view).getChildCount();
for (int i = 0; i < childCount; i++) {
setViewClickedTracker(((ViewGroup) view).getChildAt(i), fragment);
}
}
}
@Override
public void sendAccessibilityEvent(View host, int eventType) {
super.sendAccessibilityEvent(host, eventType);
if (AccessibilityEvent.TYPE_VIEW_CLICKED == eventType && host != null) {
// 添加事件到数据库
}
}
}
复制代码
然后在Activity和Fragment的onResume中添加View的监听即可。
生成埋点配置信息
事件的全局监听已经实现了,理论上APP开发人员不需要参与埋点的过程,但后台的统计并不需要所有的数据,所以这里还需要添加埋点配置信息的收集。这里提供了埋点数据实时上传的功能,在APP上线前,将数据上传策略修改成实时上传,即可将所有的事件信息通过Socket发送给后台,然后将需要的数据导入到埋点配置信息列表中,APP上线后,会从服务器获取埋点配置信息,在产生数据后,根据获取的配置信息,保存需要的数据,到指定上传时间时,将数据提交给服务器。
使用
在Application的onCreate中进行初始化即可:
TrackerConfiguration configuration = new TrackerConfiguration()
.openLog(true)
.setUploadCategory(Constants.UPLOAD_CATEGORY.REAL_TIME.getValue())
.setConfigUrl("http://m.baidu.com") // 埋点配置信息的URL
.setHostName("127.0.0.1") // 接收实时埋点数据的IP和端口
.setHostPort(10001)
.setNewDeviceUrl("http://m.baidu.com") // 保存新设备信息的URL
.setUploadUrl("http://m.baidu.com"); // 保存埋点数据的URL
Tracker.getInstance().init(this, configuration);
复制代码
在发布版本之前,将上传策略设置成Constants.UPLOAD_CATEGORY.REAL_TIME收集埋点配置信息,APP上线时务必将数据上传策略改成其他的,避免耗电。
对于埋点数据的上传,提供了以下策略:
REAL_TIME(0), // 实时传输,用于收集配置信息 NEXT_LAUNCH(-1), // 下次启动时上传 NEXT_15_MINUTER(15), // 每15分钟上传一次 NEXT_30_MINUTER(30), // 每30分钟上传一次 NEXT_KNOWN_MINUTER(-1); // 使用服务器下发的上传策略(间隔时间由服务器决定) 复制代码
以上所述就是小编给大家介绍的《无埋点统计SDK实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 前端iOS打点统计的AOP技术实践
- 微信小程序数据统计和错误统计的实现
- 机器学习数学基础:数理统计与描述性统计
- Java8中使用stream进行分组统计和普通实现的分组统计的性能对比
- RecyclerView 的曝光统计
- MongoDB 分组统计
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python Algorithms
Magnus Lie Hetland / Apress / 2010-11-24 / USD 49.99
Python Algorithms explains the Python approach to algorithm analysis and design. Written by Magnus Lie Hetland, author of Beginning Python, this book is sharply focused on classical algorithms, but it......一起来看看 《Python Algorithms》 这本书的介绍吧!