在使用神策分析时,会遇到一些问题,譬如调用了 flush() 方法的上报,后台为啥没有查到数据,而在一天甚至几天后又有了?譬如数据怎么去做重试的?在官方文档没有详细介绍到细节逻辑,于是去查看源码,了解了一下数据存储与上报的具体实现。
首先带着一些问题:
- 调用 flush() 方法会立即上报吗?会立即上报到我们需要的数据吗?
- 当本地数据会越存越多吗?怎么去做管理,删除什么样的数据?
- 数据终归网络上报后台,异常后怎么去重试?
神策上报的核心方法如下:
// 主要的核心方法:
private void sendData() {
try {
if (!mSensorsDataAPI.isNetworkRequestEnable()) {
SALog.i(TAG, "NetworkRequest is disabled");
return;
}
if (TextUtils.isEmpty(mSensorsDataAPI.getServerUrl())) {
SALog.i(TAG, "Server url is null or empty.");
return;
}
//无网络
if (!NetworkUtils.isNetworkAvailable(mContext)) {
return;
}
//不符合同步数据的网络策略
String networkType = NetworkUtils.networkType(mContext);
if (!NetworkUtils.isShouldFlush(networkType, mSensorsDataAPI.getSAContextManager().getInternalConfigs().saConfigOptions.mNetworkTypePolicy)) {
SALog.i(TAG, String.format("Invalid NetworkType = %s", networkType));
return;
}
// 如果开启多进程上报
if (mInternalConfigs.saConfigOptions.isMultiProcessFlush()) {
// 已经有进程在上报
if (DbAdapter.getInstance().isSubProcessFlushing()) {
return;
}
DbAdapter.getInstance().commitSubProcessFlushState(true);
} else if (!mInternalConfigs.isMainProcess) {//不是主进程
return;
}
} catch (Exception e) {
SALog.printStackTrace(e);
return;
}
int count = 100;
while (count > 0) {
boolean deleteEvents = true;
String[] eventsData;
synchronized (mDbAdapter) {
if (mSensorsDataAPI.isDebugMode()) {
/* debug 模式下服务器只允许接收 1 条数据 */
eventsData = mDbAdapter.generateDataString(DbParams.TABLE_EVENTS, 1);
} else {
eventsData = mDbAdapter.generateDataString(DbParams.TABLE_EVENTS, 50);
}
}
if (eventsData == null) {
DbAdapter.getInstance().commitSubProcessFlushState(false);
return;
}
final String lastId = eventsData[0];
final String rawMessage = eventsData[1];
final String gzip = eventsData[2];
String errorMessage = null;
try {
String data = rawMessage;
if (DbParams.GZIP_DATA_EVENT.equals(gzip)) {
data = encodeData(rawMessage);
}
if (!TextUtils.isEmpty(data)) {
sendHttpRequest(mSensorsDataAPI.getServerUrl(), data, gzip, rawMessage, false);
}
} catch (ConnectErrorException e) {
deleteEvents = false;
errorMessage = "Connection error: " + e.getMessage();
} catch (InvalidDataException e) {
errorMessage = "Invalid data: " + e.getMessage();
} catch (ResponseErrorException e) {
deleteEvents = isDeleteEventsByCode(e.getHttpCode());
errorMessage = "ResponseErrorException: " + e.getMessage();
} catch (Exception e) {
deleteEvents = false;
errorMessage = "Exception: " + e.getMessage();
} finally {
boolean isDebugMode = mSensorsDataAPI.isDebugMode();
if (!TextUtils.isEmpty(errorMessage)) {
if (isDebugMode || SALog.isLogEnabled()) {
SALog.i(TAG, errorMessage);
if (isDebugMode && mInternalConfigs.isShowDebugView) {
SensorsDataDialogUtils.showHttpErrorDialog(AppStateTools.getInstance().getForegroundActivity(), errorMessage);
}
}
}
if (deleteEvents || isDebugMode) {
count = mDbAdapter.cleanupEvents(lastId);
SALog.i(TAG, String.format(TimeUtils.SDK_LOCALE, "Events flushed. [left = %d]", count));
} else {
count = 0;
}
}
}
if (mInternalConfigs.saConfigOptions.isMultiProcessFlush()) {
DbAdapter.getInstance().commitSubProcessFlushState(false);
}
}
1. 分析一次上报的数据量
最多50条,详见 AnalyticsMessages.sendData()
2. 分析数据上报顺序
每次上报,从数据库是按照数据的创建时间的升序取的数据,也就是每次取最老的数据上报
3. 分析上报策略
3.1 一般上报
// 调用方式
SensorsDataAPI.sharedInstance().track("event", properties)
该上报的核心方法主要是 EventProcessor.process(InputData input)
(1)其中 storeData() 的code返回数据库的数据条数,当数据库添加失败时code<0
(2)其中 sendData() 就是调用 flushEventMessage(boolean isSendImmediately) 方法,传参isSendImmediately的值根据以下条件来确定:
- code<0(数据库插入失败);或者
- code>设置的缓存条数(默认缓存条数是100条,可最小设置的条数是50条);或者
- 设置的debug模式;或者
- 调用的login()事件
一般上报最终就是调用 flushEventMessage(boolean isSendImmediately) 方法,根据传参的值来决定是立即上报还是定时上报:
- 当isSendImmediately为true时,就去立即上报;
- 当isSendImmediately为false时,就去定时上报;
3.2 定时上报
SensorsDataAPI.sharedInstance().flushScheduled()
调用 flushScheduled()方法 (只调用一次即可),默认15s报一次
为啥只需调用一次,是因为延时消息到点后,会自动再次发送延时消息
3.3 立即上报
SensorsDataAPI.sharedInstance().flush()
调用 flush() 方法,可以立即上报一次
3.4 上报条件
上述三种上报方法,除了满足自身条件外,还需满足:
- 是否设置网络请求开关为开,默认开;
- 是否配置api服务;
- 是否网络可用;
- 是否符合网络条件,默认为WIFI/3G/4G/5G 网络条件;
- 是否开启多进程上报,或者是否在主进程上报;
- 上述条件查询没有异常
通过上面3部分的分析,解决开头提出的第一个问题:
1. 调用 flush() 方法并不会立即上报,需要满足3.4的约束条件;
2. 当我们调用 track() 上报某事件后,再调用 flush(),该事件不一定立马上报,因为每次上报上限为50条数据,并且上报数据库存储的最老的数据。假设数据库已经存储了50条,插入一条数据后,调用 flush(),会先上报前50条数据,新插入的数据会等下次满足条件后再上报
4. 分析数据删除逻辑
4.1 snedData() 中的网络请求后的删除/不删除逻辑
主要的删除/不删除逻辑如下
(1)删除数据 -> 数据上报成功
(2)不删除数据 -> 自定义的ConnectErrorException异常,也就是网络请求时抛出的IOException异常
(3)删除数据 -> 自定义的InvalidDataException异常,也就是数据读写时抛出的IOException异常
(4)不删除数据 -> 其他异常
(5)自定义删除 -> 自定义的ResponseErrorException异常
- 当网络请求的状态码小于200或者大于等于300的时候,抛出ResponseErrorException;
- 抛出ResponseErrorException异常后,判断状态码为 (>= 500 && < 600) || 404 || 403 的时候,不删除数据,否则其他的状态码就删除数据;
总结网络上报后的删除数据的情况:
- 数据上报成功;
- 数据在文件流读写操作时出现的异常;
- 状态码 ((<200 || >= 300) && !((>= 500 && < 600) || 404 || 403));
4.2 超过设置的本地缓存值时的删除逻辑
数据库默认缓存数据的上限值为 32MB
(1)当每次插入数据时,会校验数据库数据是否达到上限值;
(2)若达到上限值,就会删除最老的100条数据;
通过这一部分的分析,解决开头提出的第二个问题:
1. 根据网络上报的情况制定删除策略;
2. 设置本地数据库的存储上限,删除最老的100条数据;
5. 分析数据重试上报逻辑
- 每次调用 track() 或者 flush() 或者 flushScheduled() 方法时,最终会调用 sendData() 方法,会判断当数据库若还存有数据(当前插入的数据,或者上次未删除的数据),则会继续上报;
- 当网络请求后的状态码为(301 || 302 || 307)时,会立即重试一次网络请求上报;
通过这一部分的分析,解决开头提出的第三个问题:
1. 未上报成功的,继续存储在数据库,等待下次取到这些数据重试;
2. 301/302/307时立即重试一次网络请求
6. 总结
1. 每次上报的数据量最多50条;
2. 每次取最老的数据进行上报;
3. 调用 track() 方法后,是立即上报还是定时上报取决于这几个条件:
- code<0(数据库插入失败);或者
- code>设置的缓存条数(默认缓存条数是100条,可最小设置的条数是50条);或者
- 设置的debug模式;或者
- 调用的login()事件
4. 上报时还会检查是否满足下面这些条件:
- 是否设置网络请求开关为开,默认开;
- 是否配置api服务;
- 是否网络可用;
- 是否符合网络条件,默认为WIFI/3G/4G/5G 网络条件;
- 是否开启多进程上报,或者是否在主进程上报;
- 上述条件查询没有异常;
5. 数据删除逻辑分两种:
网络上报后的数据删除:
- 数据上报成功;
- 数据在文件流读写操作时出现的异常;
- 状态码 ((<200 || >= 300) && !((>= 500 && < 600) || 404 || 403));
本地数据库存储的数据删除:
- 判断本地数据存储是否达到上限值32MB,若达到,就会删除最老的100条数据;
6. 数据上报重试机制:
- 本次未上报成功的数据,会继续存储在数据库,等待下次取到这些数据重试;
- 本次未上报出现301/302/307时,会立即重试一次网络请求;