神策源码
(链接为镜像,如果不想使用镜像,请把域名改回github.com)
核心接口 ISensorsDataAPI
这里从自定义事件的入口开始
/**
* 调用 track 接口,追踪一个带有属性的事件
*
* @param eventName 事件的名称
* @param properties 事件的属性
*/
void track(String eventName, JSONObject properties);
实现类 SensorDataAPI
@Override
public void track(final String eventName, final JSONObject properties) {
try {
final JSONObject dynamicProperty = getDynamicProperty();
mTrackTaskManager.addTrackEventTask(new Runnable() {
@Override
public void run() {
JSONObject _properties = ChannelUtils.checkOrSetChannelCallbackEvent(getConfigOptions().isAutoAddChannelCallbackEvent, eventName, properties, mContext);
trackEvent(EventType.TRACK, eventName, _properties, dynamicProperty, null);
}
});
} catch (Exception e) {
SALog.printStackTrace(e);
}
}
mTrackTaskManager.addTrackEventTask
就是把任务塞进 LinkedBlockingQueue
然后有 TrackTaskManagerThread
在子线程循环抓取
public class TrackTaskManagerThread implements Runnable {
@Override
public void run() {
try {
while (!isStop) {
// take 阻塞获取,停止时往下走
Runnable downloadTask = mTrackTaskManager.takeTrackEventTask();
mPool.execute(downloadTask);
}
while (true) {
// poll 把停止后还剩下的数据全部取完,取完时为空
Runnable downloadTask = mTrackTaskManager.pollTrackEventTask();
if (downloadTask == null) {
break;
}
mPool.execute(downloadTask);
}
mPool.shutdown();
} catch (Exception e) {
SALog.printStackTrace(e);
}
}
}
然后我们进入 track 任务的执行方法 trackEvent
protected void trackEvent(final EventType eventType, String eventName, final JSONObject properties, JSONObject dynamicProperty, final String
originalDistinctId) {
// ... 省略一大部分处理数据的代码
try {
// 禁用采集事件时,先计算基本信息存储到缓存中
if (!mSAConfigOptions.isDataCollectEnable) {
if (SALog.isLogEnabled()) {
SALog.i(TAG, "track event, isDataCollectEnable = false, eventName = " + eventName + ",property = " + JSONUtils.formatJson(sendProperties.toString()));
}
// 塞进任务队列,等待可以采集事件的时机
transformEventTaskQueue(eventType, eventName, properties, sendProperties, originalDistinctId, getDistinctId(), getLoginId(), eventTimer);
return;
}
// 实际的上报方法
trackEventInternal(eventType, eventName, properties, sendProperties, originalDistinctId, getDistinctId(), getLoginId(), eventTimer);
} catch (JSONException e) {
throw new InvalidDataException("Unexpected property");
}
} catch (Exception e) {
SALog.printStackTrace(e);
}
}
进入 trackEventInternal
private void trackEventInternal(final EventType eventType, final String eventName, final JSONObject properties, final JSONObject sendProperties,
final String originalDistinctId, String distinctId, String loginId, final EventTimer eventTimer) throws JSONException {
// ...上面处理和校验数据的方法全部省略
mMessages.enqueueEventMessage(eventType.getEventType(), dataObj);
if (SALog.isLogEnabled()) {
SALog.i(TAG, "track event:\n" + JSONUtils.formatJson(dataObj.toString()));
}
}
进入 AnalyticsMessages.enqueueEventMessage
void enqueueEventMessage(final String type, final JSONObject eventJson) {
try {
synchronized (mDbAdapter) {
int ret = mDbAdapter.addJSON(eventJson);
// ...省略异常状况
final Message m = Message.obtain();
m.what = FLUSH_QUEUE;
if (mSensorsDataAPI.isDebugMode() || ret ==
DbParams.DB_OUT_OF_MEMORY_ERROR) {
// debug或者db溢出立刻上报
mWorker.runMessage(m);
} else {
// track_signup 立即发送
if (type.equals("track_signup") || ret > mSensorsDataAPI
.getFlushBulkSize()) {
// track_signup 以及存储数量超过50条立刻上报
mWorker.runMessage(m);
} else {
// 触发5秒延时上报
final int interval = mSensorsDataAPI.getFlushInterval();
mWorker.runMessageOnce(m, interval);
}
}
}
} catch (Exception e) {
SALog.i(TAG, "enqueueEventMessage error:" + e);
}
}
这里发的Message最后会走到AnalyticsMessageHandler
private class AnalyticsMessageHandler extends Handler {
AnalyticsMessageHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
try {
// 清理数据队列
if (msg.what == FLUSH_QUEUE) {
sendData();
}
// ...
} catch (final RuntimeException e) {
SALog.i(TAG, "Worker threw an unhandled exception", e);
}
}
}
sendData
里面的核心逻辑:
synchronized (mDbAdapter) {
if (mSensorsDataAPI.isDebugMode()) {
/* debug 模式下服务器只允许接收 1 条数据 */
eventsData = mDbAdapter.generateDataString(DbParams.TABLE_EVENTS, 1);
} else {
// 非debug,按照旧到新拿50条数据
eventsData = mDbAdapter.generateDataString(DbParams.TABLE_EVENTS, 50);
}
}
// 最后经过一番数据处理走到Http请求,就不用多说了
if (!TextUtils.isEmpty(data)) {
sendHttpRequest(mSensorsDataAPI.getServerUrl(), data, gzip, rawMessage, false);
}
DbAdapter
留到最后说,
@Override
int insertData(Uri uri, ContentValues contentValues) {
// 插入数据
try {
if (deleteDataLowMemory(uri) != 0) {
return DbParams.DB_OUT_OF_MEMORY_ERROR;
}
contentResolver.insert(uri, contentValues);
} catch (Exception e) {
SALog.printStackTrace(e);
}
return 0;
}
// 读取数据
contentResolver.query(uri, null, null, null, DbParams.KEY_CREATED_AT + " ASC LIMIT " + limit);
简单总结:
所有的埋点默认会塞进ContentProvider,debug模式下保存完了会实时继续一条一条读出来发到服务器,release模式下会延时5秒:5秒内如果ContentProvider里面的数据缓存超过50条,会立刻读最早的50条上传;不够50条的话,进来的新消息会继续触发5秒延迟,等5秒后再进行从ContentProvider里面拿50条上传的逻辑;注册消息无论如何会立刻上传