android中神策sensorsdata的数据存储与上报源码分析

神策的官方文档

在使用神策分析时,会遇到一些问题,譬如调用了 flush() 方法的上报,后台为啥没有查到数据,而在一天甚至几天后又有了?譬如数据怎么去做重试的?在官方文档没有详细介绍到细节逻辑,于是去查看源码,了解了一下数据存储与上报的具体实现。

首先带着一些问题:

  1. 调用 flush() 方法会立即上报吗?会立即上报到我们需要的数据吗?
  2. 当本地数据会越存越多吗?怎么去做管理,删除什么样的数据?
  3. 数据终归网络上报后台,异常后怎么去重试?

神策上报的核心方法如下:

// 主要的核心方法:
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. 分析数据重试上报逻辑

  1. 每次调用 track() 或者 flush() 或者 flushScheduled() 方法时,最终会调用 sendData() 方法,会判断当数据库若还存有数据(当前插入的数据,或者上次未删除的数据),则会继续上报;
  2. 当网络请求后的状态码为(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时,会立即重试一次网络请求;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值