关于前端:Android-SDK-之用户路径采集

33次阅读

共计 9563 个字符,预计需要花费 24 分钟才能阅读完成。

一、用户门路

用户路径分析为八大重要分析模型之一,能够追踪用户从某个开始行为事件直到完结事件的行为门路,是一种监测用户流向,从而统计产品应用深度的分析方法,帮忙业务人员理解用户行为散布状况,对海量用户的行为习惯造成宏观理解。

用户门路的作用

用户门路能够帮忙使用者洞察用户看似平时的行为背地真正的思维,从而解脱“海底捞针”式的用户行为数据查问。

使用者既能够对症下药,验证本身假如,有针对性地解决问题;也能够日常监测用户的行为门路,及时发现用户的外围关注点及烦扰选项,疏导用户继续开掘产品及服务的价值。

二、用户门路的采集

一个用户的残缺行为门路会蕴含多个行为事件,以电商为例,用户从关上 App 到领取胜利要通过首页浏览、搜寻商品、退出购物车、提交订单、领取订单等过程。而在用户实在的选购过程是一个交缠重复的过程,例如提交订单后,用户可能会返回首页持续搜寻商品,也可能去勾销订单,每一个门路背地都有不同的动机。

通常一个残缺的用户门路蕴含 App 启动、若干个页面浏览和 App 退出等事件。通过 Application.ActivityLifecycleCallbacks 监听,神策 Android SDK 实现 App 启动、页面浏览和 App 退出三个行为事件。

(一)根底概念

要想理解神策的用户门路采集原理,首先咱们要理解下 session 和补发机制。

session 机制

在 Android App 中,因为用户会很频繁的切换利用,就会造成利用的启动和退出事件过于频繁,且会打断用户失常浏览序列。

例如在上图这种状况,就会有 2 个残缺的用户门路:

关上利用 → 浏览页面 1 → 敞开 / 切换利用 → 关上利用 → 浏览页面 2 → 敞开 / 切换利用

而页面浏览 1 和页面浏览 2 就被切割成 2 个独立的用户门路。

所以神策 Android SDK 为了应答利用切换、多过程和强杀等场景,退出了 session 机制(默认 30 秒,可动静设置)用户关上 App 距上次退出 App 少于设置的 session 工夫,则不会触发利用退出和启动事件,如下图:


这时用户门路就被变成为:

关上利用 → 浏览页面 1 → 浏览页面 2 → 敞开 / 切换利用

补发机制
如果在退出 App 到后盾 30 秒内,过程还没有被杀掉,那么此时会触发退出事件并尝试上报,如果过程被杀掉了,那么退出事件会在下一次启动时补发。

理解完 session 机制和补发机制后,咱们再从代码层面来具体讲讲用户门路事件的采集逻辑。

(二)App 启动事件

当用户首次启动 App 时,如果满足咱们的 session 时长机制则会触发 App 启动事件。SensorsDataActivityLifecycleCallbacks 中的 onActivityStrated 办法的实现如下:

利用启动

@Override

public void onActivityStarted(Activity activity) {

try {

//step 1 读取 Activity 启动的个数

if (isMultiProcess) {// 是否多过程

startActivityCount = mDbAdapter.getActivityCount();

mDbAdapter.commitActivityCount(++startActivityCount);

} else {

++startActivityCount;

}

// 如果是第一个页面

if (startActivityCount == 1) {

boolean sessionTimeOut = isSessionTimeOut();

//step 2 是否满足 session 时长距离

if (sessionTimeOut) {

try {

if (mSensorsDataInstance.isAutoTrackEnabled() && !mSensorsDataInstance.isAutoTrackEventTypeIgnored(SensorsDataAPI.AutoTrackEventType.APP_START)) {

if (firstStart) {

mFirstStart.commit(false);

}

JSONObject properties = new JSONObject(); properties.put(“$resume_from_background”, resumeFromBackground); properties.put(“$is_first_time”, firstStart); SensorsDataUtils.mergeJSONObject(activityProperty, properties);

//step 3 发送 $AppStart 事件

mSensorsDataInstance.track(“$AppStart”, properties);

}

} catch (Exception e) {

SALog.i(TAG, e);

}

}

}

}

在 onActivityStarted 办法中的关键步骤如下:

step 1. 将 startActivityCount 自增(startActivityCount++),不同的是当多过程时通过数据库来存取 startActivityCount;

step 2. 判断本次启动是否满足 session 时长距离筛选,如果满足则判断用户是否开启 App 启动事件采集;

step 3. 采集 App 启动事件。

(三)App 退出事件

当用户退出 App 或将 App 退到后盾超过 session 工夫时则为采集 App 退出事件。

神策 Android SDK 进行 App 退出事件的采集时,须要在 ActivityLifecycleCallbacks 中的 onActivityStopped(Activity activity) 回调中施行。

$AppEnd

@Override

public void onActivityStopped(Activity activity){

startTimerCount–;

/*

  • 如果以后是最初一个页面

*/

if (startActivityCount <= 0) {

generateAppEndData();

// 发送一个提早 session 工夫的音讯,用于采集 App 退出事件

handler.sendMessageDelayed(generateMessage(true), sessionTime);

}

}

/**

  • 构建 Message 对象

*

  • @param resetState 是否重置状态
  • @return Message

*/

private Message generateMessage(boolean resetState) {

Message message = Message.obtain(handler);

message.what = MESSAGE_END;

Bundle bundle = new Bundle();

bundle.putLong(APP_END_TIME, DbAdapter.getInstance().getAppPausedTime());

bundle.putString(APP_END_DATA, DbAdapter.getInstance().getAppEndData());

bundle.putBoolean(APP_RESET_STATE, resetState);

message.setData(bundle);

return message;

}

handler = new Handler(handlerThread.getLooper()) {

@Override

public void handleMessage(Message msg) {

if (msg != null) {

Bundle bundle = msg.getData();

long endTime = bundle.getLong(APP_END_TIME);

String endData = bundle.getString(APP_END_DATA);

boolean resetState = bundle.getBoolean(APP_RESET_STATE);

// 如果是失常的退到后盾,须要重置标记位

if (resetState) {

resetState();

} else {// 如果是补发则须要增加打点距离,避免 $AppEnd 在 AppCrash 事件序列之前

endTime = endTime + TIME_INTERVAL;

}

// 采集 App 退出事件

trackAppEnd(endTime, endData);

}

}

}

在 onActivityStopped(Activity activity) 回调中判断以后是否是最初一个页面,如果以后是最初一个页面,则触发提早 session 时长的 App 退出事件的音讯。

如果用户在 session 时长内从新进入到前台,则会在 onActivityStarted(Activity activity) 回调中勾销 Handler 的音讯,即本次没有触发 App 退出事件。如果在后盾 session 时长距离内把 App 过程杀死,则会在下次启动时进行补发。

@Override

public void onActivityStarted(Activity activity) {

try {

activityProperty = AopUtil.buildTitleAndScreenName(activity);

SensorsDataUtils.mergeJSONObject(activityProperty, endDataProperty);

if (isMultiProcess) {

startActivityCount = mDbAdapter.getActivityCount();

mDbAdapter.commitActivityCount(++startActivityCount);

} else {

++startActivityCount;

}

// 如果是第一个页面

if (startActivityCount == 1) {

handler.removeMessages(MESSAGE_END);

boolean sessionTimeOut = isSessionTimeOut();

if (sessionTimeOut) {

// 超时尝试补发 $AppEnd

handler.sendMessage(generateMessage(false));

}

}

} catch (Exception e) {

SALog.printStackTrace(e);

}

}

这样,当用户强杀利用后,下次启动时会通过补发 $AppEnd 事件,从而使整个用户门路残缺。在 App 退出事件的采集会遇到很多非凡的因素,更多的细节解决能够参照咱们开源我的项目的残缺代码。

(四)页面浏览($AppViewScreen)

1. Activity 页面浏览事件采集

神策 Android SDK 中页面浏览事件蕴含 Activity 和 Fragment 的页面浏览事件,通过 ActivityLifecycleCallbacks 中的 onActivityResumed(Activity activity) 回调监听 Activity 页面,具体实现如下:

@Override

public void onActivityResumed(final Activity activity) {

try {

if (mSensorsDataInstance.isAutoTrackEnabled() && !mSensorsDataInstance.isActivityAutoTrackAppViewScreenIgnored(activity.getClass())

&& !mSensorsDataInstance.isAutoTrackEventTypeIgnored(SensorsDataAPI.AutoTrackEventType.APP_VIEW_SCREEN)) {

JSONObject properties = new JSONObject();

SensorsDataUtils.mergeJSONObject(activityProperty, properties);

if (activity instanceof ScreenAutoTracker) {

ScreenAutoTracker screenAutoTracker = (ScreenAutoTracker) activity;

JSONObject otherProperties = screenAutoTracker.getTrackProperties();

if (otherProperties != null) {

SensorsDataUtils.mergeJSONObject(otherProperties, properties);

}

}

mSensorsDataInstance.trackViewScreen(SensorsDataUtils.getScreenUrl(activity), properties);

}

} catch (Exception e) {

SALog.printStackTrace(e);

}

}

在 onActivityResumed 中调用 trackViewScreen 上报 $AppViewScreen 事件,其中 ScreenAutoTracker 接口是用来让用户自定义页面浏览的 url 和一些自定义属性的,这里就不细讲了。

2. Fragment 页面浏览事件采集

Fragment 自身没有监听生命周期,前期 FragmentLifecycleCallbacks 监听 Fragment 的生命周期,是 Andorid Support 25.1.0 和 AndroidX 中减少 FragmentManager 起到的重要作用。

神策 SDK 不依赖 Support 库和 AndroidX 库,无奈用 FragmentLifecycleCallbacks 监听 Fragment 的生命周期,为此神策通过全埋点插件在编译期间插入代码来实现 Fragment 的页面浏览采集,上面来看看具体的采集原理:

下图是反编译后的源码,这里应用 ASM 在编译期间通过在 Fragment 零碎生命周期办法中别离插入了对应的神策 SDK 办法。

从中能够看到神策全埋点插件 Hook 的 Fragment 生命周期办法有:

onViewCreated()
onResume()
setUserVisibleHint()
onHiddenChanged()
上面别离介绍对每个生命周期插入的代码:

在 Fragment 的 onViewCreated 生命周期办法中插入方法 onFragmentViewCreated(Object object, View rootView, Bundle bundle),onFragmentViewCreated 办法中次要是遍历 View 并给 View 设置 TAG 标记,这里次要是为了点击事件所做的解决,跟页面浏览事件关系不大。

public static void onFragmentViewCreated(Object object, View rootView, Bundle bundle) {

try {

if (!isFragment(object)) {

return;

}

//Fragment 名称

String fragmentName = object.getClass().getName();

rootView.setTag(R.id.sensors_analytics_tag_view_fragment_name, fragmentName);

if (rootView instanceof ViewGroup) {

traverseView(fragmentName, (ViewGroup) rootView);

}

} catch (Exception e) {

SALog.printStackTrace(e);

}

}

在 Fragment 的 onResume 办法中通过插入 trackFragmentResume(Object object) 办法实现 Fragment 的页面浏览事件的采集。

public static void trackFragmentResume(Object object) {

if (SensorsDataAPI.sharedInstance().isAutoTrackEventTypeIgnored(SensorsDataAPI.AutoTrackEventType.APP_VIEW_SCREEN)) {

return;

}

if (!SensorsDataAPI.sharedInstance().isTrackFragmentAppViewScreenEnabled()) {

return;

}

if (!isFragment(object)) {

return;

}

try {

Method getParentFragmentMethod = object.getClass().getMethod(“getParentFragment”);

if (getParentFragmentMethod != null) {

Object parentFragment = getParentFragmentMethod.invoke(object);

if (parentFragment == null) {

if (!fragmentIsHidden(object) && fragmentGetUserVisibleHint(object)) {

trackFragmentAppViewScreen(object);

}

} else {

if (!fragmentIsHidden(object) && fragmentGetUserVisibleHint(object) && !fragmentIsHidden(parentFragment) && fragmentGetUserVisibleHint(parentFragment)) {

trackFragmentAppViewScreen(object);

}

}

}

} catch (Exception e) {

//ignored

}

}

在 Fragment 的 onHiddenChanged 办法中通过插入 trackOnHiddenChanged(Object object, boolean hidden) 办法实现 Fragment 的页面浏览事件的采集。

public static void trackOnHiddenChanged(Object object, boolean hidden) {

if (SensorsDataAPI.sharedInstance().isAutoTrackEventTypeIgnored(SensorsDataAPI.AutoTrackEventType.APP_VIEW_SCREEN)) {

return;

}

if (!SensorsDataAPI.sharedInstance().isTrackFragmentAppViewScreenEnabled()) {

return;

}

if (!isFragment(object)) {

return;

}

Object parentFragment = null;

try {

Method getParentFragmentMethod = object.getClass().getMethod(“getParentFragment”);

if (getParentFragmentMethod != null) {

parentFragment = getParentFragmentMethod.invoke(object);

}

} catch (Exception e) {

//ignored

}

if (parentFragment == null) {

if (!hidden) {

if (fragmentIsResumed(object)) {

if (fragmentGetUserVisibleHint(object)) {

trackFragmentAppViewScreen(object);

}

}

}

} else {

if (!hidden && !fragmentIsHidden(parentFragment)) {

if (fragmentIsResumed(object) && fragmentIsResumed(parentFragment)) {

if (fragmentGetUserVisibleHint(object) && fragmentGetUserVisibleHint(parentFragment)) {

trackFragmentAppViewScreen(object);

}

}

}

}

}

在 Fragment 的 setUserVisibleHint 办法中通过插入 trackFragmentSetUserVisibleHint(Object object, boolean isVisibleToUser) 办法实现 Fragment 的页面浏览事件的采集。

public static void trackFragmentSetUserVisibleHint(Object object, boolean isVisibleToUser) {

if (SensorsDataAPI.sharedInstance().isAutoTrackEventTypeIgnored(SensorsDataAPI.AutoTrackEventType.APP_VIEW_SCREEN)) {

return;

}

if (!SensorsDataAPI.sharedInstance().isTrackFragmentAppViewScreenEnabled()) {

return;

}

if (!isFragment(object)) {

return;

}

Object parentFragment = null;

try {

Method getParentFragmentMethod = object.getClass().getMethod(“getParentFragment”);

if (getParentFragmentMethod != null) {

parentFragment = getParentFragmentMethod.invoke(object);

}

} catch (Exception e) {

//ignored

}

if (parentFragment == null) {

if (isVisibleToUser) {

if (fragmentIsResumed(object)) {

if (!fragmentIsHidden(object)) {

trackFragmentAppViewScreen(object);

}

}

}

} else {

if (isVisibleToUser && fragmentGetUserVisibleHint(parentFragment)) {

if (fragmentIsResumed(object) && fragmentIsResumed(parentFragment)) {

if (!fragmentIsHidden(object) && !fragmentIsHidden(parentFragment)) {

trackFragmentAppViewScreen(object);

}

}

}

}

}

依据 Fragment 不同状态和一系列判断,调用 trackFragmentAppViewScreen 办法来采集页面浏览事件。

以上,就对 Android 中常见的页面浏览的形式实现了采集。

三、总结

这篇文章次要是为了可能让大家对于 Sensors Data Android SDK 在用户门路采集方面有大抵的理解,大家如果有什么好的想法,或者发现咱们我的项目的 bug,欢送大家到 GitHub 上提 issues 或间接 Pull Requests,咱们会第一工夫解决,也心愿咱们的 SDK 能在大家的一起致力下,做得更加欠缺。

文章起源:公众号神策技术社区

正文完
 0