整个发送事件流程结构图
dearly项目中基于Countly统计分析系统。目前项目中统计事件分为四类:
(一)PV事件:既PathView。用于记录用户在app中访问所经过的页面路径,同时也携带了一些其他统计参数,例如uuid,spm,viewname等。
以基类ActivityAnalyticsBase为例
@Override
public final void onCreate(@Nullable Bundle savedInstanceState) {
onCreateBefore(savedInstanceState);
super.onCreate(savedInstanceState);
mViewUniqueId = ToolApp.getUniqueCode();
......
}
@Override
protected void onStart() {
super.onStart();
if (!isAppRestarting()) {
getAnaly().sendStartPv(this, getIntent().getExtras());
}
}
@Override
public IViewAnalytics getAnaly() {
if (mViewAnalytics == null) {
mViewAnalytics = new ActivityAnalytics(super.getViewTraceModel(), this);
}
return mViewAnalytics;
}
这里在onCreate里生成了一个UUID,也表示每生成一个activity都会初始化一个唯一的UUID(这个之后会介绍哪里用到)。然后初始化了接口,调用了sendStartPv(this, getIntent().getExtras())方法
@Override
public void sendStartPv(@NonNull IBaseView baseView, Bundle bundle) {
if (baseView.isNeedAnalyseView()) {
if (isViewDataOk(baseView)) {
ViewTraceModel vtm = baseView.getViewTraceModel();
// 发送GA
ToolGA.sendScreen(vtm.getViewName());
// 发送Countly
CountlySenderManager.getInstance()
.sendStartPv(baseView.getViewTraceModel(), ICountlySender.SENDER_FLAG_ACTIVITY_START);
} else {
ToolException.throwIllegalAccessError("sendStartPv()", "参数不正确,需要解决!");
}
}
}
baseView.getViewTraceModel()中取出带过来的pageViewId(UUID)、previousViewName(上个页面路径viewname)、spm等,然后发送一个当前屏幕事件(GA),带着所有参数走了 CountlySenderManager.getInstance().sendStartPv方法。
public void sendStartPv(ViewTraceModel viewTraceModel, String senderFlag) {
if (checkViewTraceModel(viewTraceModel, senderFlag)) {
viewTraceModel.setViewStartTime(Countly.currentTimestamp());
Map<String, String> segments = getDataCollectorMap(senderFlag);
// 追加PV与Evt公共参数
putCommonSegments(segments, viewTraceModel);
// 追加Start Pv参数
segments.put("ref", viewTraceModel.getPreviousViewName());// 前一个页面名称
segments.put("spm", viewTraceModel.getRootSpm());// spm
// 追加自定义参数
putCustomSegments(segments, viewTraceModel.getSegments());
// 发送数据
addJob(segments);
}
}
private void putCommonSegments(@NonNull Map<String, String> segments, @NonNull ViewTraceModel viewTraceModel) {
// 当前屏幕名称
segments.put(CountlyCodeConstBase.PARAM_PAGE_NAME, viewTraceModel.getViewName());
// PvId
segments.put(CountlyCodeConstBase.PARAM_PV_ID_2, viewTraceModel.getPageViewId());
// 用户Id
putSegment(segments, CountlyCodeConstBase.PARAM_USER_ID, UserSecurityManager.getInstance().getUserId());
}
这里解释下spm,就是超级位置模型,主要用来定位当前精确位置。所以主要这次事件发送,携带了参数有当前页面UUId、userId、当前页面viewname、上个页面viewname以及从viewTraceModel里取出来的其他自定义参数。到这里一个页面初始化需要发送的pv事件就走完了。
接下去讲sendStopPv。
@Override
public void sendStopPv(@NonNull IBaseView baseView) {
if (baseView.isNeedAnalyseView()) {
if (isViewDataOk(baseView)) {
ViewTraceModel vtm = baseView.getViewTraceModel();
// 发送GA
ToolGA.sendScreen(vtm.getViewName());
// 发送Countly
CountlySenderManager.getInstance()
.sendStopPv(baseView.getViewTraceModel(), ICountlySender.SENDER_FLAG_ACTIVITY_STOP);
} else {
ToolException.throwIllegalAccessError("sendStopPv()", "参数不正确,需要解决!");
}
}
}
与onStart如出一辙。也是先发送了一个当前页面的GA,之后带着参数走sendStopPv。
public void sendStopPv(@NonNull ViewTraceModel viewTraceModel, String senderFlag) {
if (checkViewTraceModel(viewTraceModel, senderFlag)) {
Map<String, String> segments = getDataCollectorMap(senderFlag);
// 追加PV与Evt公共参数
putCommonSegments(segments, viewTraceModel);
// 追加Stop PV参数
segments.put(ICountlySender.KEY_COUNTLY_VIEW_START_TIME, viewTraceModel.getViewStartTime() + "");
// 发送数据
addJob(segments);
}
}
也是一样,往里面传入了当前页面viewname、uuid、userId,不过stop里追加的参数是当前页面生成的时间,然后发送事件结束。
看到这里有几个疑问点spm到底什么时候赋值和生成的?getViewStartTime()时间什么时候生成?我们跟着setRootSpm代码再看看
@NonNull
static ViewTraceModel createNextViewTraceModel(@NonNull ViewTraceModel currentViewVtm) {
/* 获取下个页面的Root spm */
String nextViewRootSpm = TextUtils.isEmpty(currentViewVtm.getNonhereditarySpm())
? BusinessSpm.assembleSpmValue(currentViewVtm.getRootSpm(), currentViewVtm.getItemSpm())
: currentViewVtm.getNonhereditarySpm();
// 清除当前页面 相关spm
currentViewVtm.setNonhereditarySpm(null).setItemSpm(null);
// 下个页面跟踪参数
ViewTraceModel nextTraceModel = new ViewTraceModel();
nextTraceModel.setLauncherCode(currentViewVtm.getViewCode())
.setPreviousViewCode(currentViewVtm.getViewCode())
.setPreviousViewName(currentViewVtm.getViewName())
.setRootSpm(nextViewRootSpm);
......
return nextTraceModel;
}
/**
* 拼装最终的 Spm值
*
* @return spm
*/
@Nullable
public static String assembleSpmValue(@Nullable String rootSpm, @Nullable String itemSpm) {
boolean isRootSpmEmpty = TextUtils.isEmpty(rootSpm);
boolean isItemStmEmpty = TextUtils.isEmpty(itemSpm);
if (!isRootSpmEmpty && !isItemStmEmpty) {
final String splitFlagStr = "_";
String[] array = rootSpm.split(splitFlagStr);
return (array.length >= SPM_ITEM_APPEND_MAX_COUNT) ? rootSpm : (rootSpm + splitFlagStr + itemSpm);
} else if (!isRootSpmEmpty) {
return rootSpm;
} else {
return itemSpm;
}
}
在这里有个判断,如果currentViewVtm.getNonhereditarySpm()等于null拼接spm,空的话取nonhereditarySpm。
/**
* 设置单个Spm值到Value中
*
* @param view view控件
* @param spmItemValue 单个spm值,格式:a.b.c
*/
public static void setSpmItemValue2View(View view, String spmItemValue) {
if ((view != null) && (!TextUtils.isEmpty(spmItemValue))) {
view.setTag(R.id.m_base_v_tag_spm_value, spmItemValue);
} else {
ToolException.throwIllegalAccessError(BusinessSpm.class.getSimpleName()
, "setSpmItemValue2View() 有值为null,view:" + view + ", spm:" + spmItemValue);
}
}
在AdapterCouponRecommendGoods业务类中找到定好的spm值,可以猜测所有的值都是定好写入的
......
BusinessSpm.setSpmItemValue2View(viewHolder.ivRecommend, SpmConst.COUPON_RECOMMEND_GOOD);
}
再回头跟着createNextViewTraceModel一直往上走
@Interceptor(priority = ActivityParamsInterceptor.INTERCEPTOR_LEVEL, name = "ViewParamsInterceptor")
public class ActivityParamsInterceptor implements IInterceptor {
......
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
DefaultParamsModel previousVpm = postcard.getExtras().getParcelable(DefaultParamsModel.KEY_VIEW_PARAMS_MODEL);
boolean isViewParamsError = isIllegalViewParams(previousVpm);
/* 测试环境下,公共参数有问题,进入提示页 */
if (isViewParamsError && (ToolApp.getEnvHome().isSandBoxEnvironment())) {
callback.onInterrupt(new Throwable("公共参数错误!拦截路径:" + postcard.getPath()));
ActivityNavigator.buildTarget(ViewPathConstBase.ACTIVITY_PATH_PARAMS_ERROR)
.with(postcard.getExtras())
.greenChannel()
.navigation();
} else {
putNextViewParams(postcard, previousVpm, isViewParamsError);
callback.onContinue(postcard);
}
.......
}
到这真相大白,spm的值是Arouter的拦截器里设置着(手动调用除外)。
而getViewStartTime()比较简单,跟着setViewStartTime可以跑到
/**
* 初始化页面跟踪数据
*
* @param baseView View
* @param viewParamsModel 页面公共参数
*/
static void initViewTraceModel(@NonNull IBaseView baseView, DefaultParamsModel viewParamsModel) {
if (hasViewTraceModel(viewParamsModel)) {
viewParamsModel.getViewTraceModel()
// 当前页面名称
.setViewName(baseView.getTagGAScreenName())
// 当前页面Code
.setViewCode(baseView.getViewCode())
// PvId
.setPageViewId(ToolApp.getUniqueCode())
//页面启动时间
.setViewStartTime(BusinessActivityBase.getCurrentSeconds());
}
}
/**
* 获取当前秒数
*/
static long getCurrentSeconds() {
return System.currentTimeMillis() / 1000L;
}
@Override
public final void initData(@NonNull Bundle bundle) {
super.initData(bundle);
// 初始化页面跟踪数据
BusinessAnalytics.initViewTraceModel(this, mViewParamsModel);
}
在initdata中生成,而再往上就是onCreate方法了,整个事件发送经过结束。
这里延伸出一个问题目前都是基于activity间路由设值触发事件,如果fragment与fragment呢??举个例子容器之间的PV事件,我们回到MainHelper类
/**
* 切换Tab时,替换页面数据
*
* @param currentFragment 当前页面
* @param nextFragment 下一个需要显示的页面
*/
public void changeViewParams(@NonNull FragmentAnalyticsBase currentFragment, @NonNull FragmentAnalyticsBase nextFragment) {
DefaultParamsModel nextViewParamsModel = nextFragment.getArguments().getParcelable(DefaultParamsModel.KEY_VIEW_PARAMS_MODEL);
ViewTraceModel nextVtmModel;
int nextViewLauncherCode = 0;
/* 获取下一个页面的跟踪数据对象 */
if (nextViewParamsModel == null) {
/* View 首次打开,初始化 */
nextViewParamsModel = new DefaultParamsModel();
nextFragment.getArguments().putParcelable(DefaultParamsModel.KEY_VIEW_PARAMS_MODEL, nextViewParamsModel);
nextVtmModel = new ViewTraceModel();
nextViewParamsModel.setViewTraceModel(nextVtmModel);
nextViewLauncherCode = currentFragment.getViewTraceModel().getViewCode();
} else {
nextVtmModel = nextViewParamsModel.getViewTraceModel();
}
/* 修改跟踪数据 */
if ((nextVtmModel == null) || (currentFragment.getViewParams().getViewTraceModel() == null)) {
ToolException.throwIllegalAccessError("changeViewParams()"
, ("重要值为null,vtmModel:" + nextVtmModel + ", params:" + currentFragment.getViewParams()));
} else {
int launcherCode = (nextViewLauncherCode > 0 ? nextViewLauncherCode : nextVtmModel.getViewCode());
nextVtmModel.setLauncherCode(launcherCode)
.setPreviousViewCode(currentFragment.getViewTraceModel().getViewCode())
.setPreviousViewName(currentFragment.getViewTraceModel().getViewName())
.setRootSpm(getRootSpm(nextFragment.getViewCode()))
.setPageViewId(ToolApp.getUniqueCode());
}
}
这里可以看到,当fragmentTab进行切换的时候,手动进行传参。也就是说如果我们要写容器类,发送事件这块要手动进行设值。
(二)点击事件:
目前点击事件的常量类都在CountlyCodeConst中
/**
* checkOut下单结果事件
*/
public static final String EVENT_NAME_CHECKOUT_RESULT = "key_checkout_result";
/**
* checkOutx下单风控
*/
public static final String EVENT_NAME_CHECKOUT_ORDERCONTROL_ALERT = "checkout_ordercontrol_alert_ok_click";
/**
* 结算页顶部提示点击关闭
*/
public static final String EVENT_NAME_CHECKOUT_NOTICE_CLOSE_CLICK = "checkout_notice_close_click";
跟着随便一个常量我们可以跑到如下方法
private void doReload() {
if (ToolNetWork.isNetConnected(this)) {
getAnaly().sendEvent(CountlyCodeConstBase.EVENT_NAME_NETWORK_ERROR_RELOAD_CLICK);
String path = getIntent().getStringExtra(BundleConstBase.KEY_ACTIVITY_INTERCEPTOR_VIEW_PATH);
if (ToolText.isNotEmpty(path)) {
getNavi().navigation(path, postcard -> {
postcard.with(getIntent().getExtras());
});
finish();
} else {
getNavi().goBackWithResult();
}
} else {
getMsgBox().showLoading();
MainHandler.getInstance().postDelayed(() -> {
if (isActive()) {
getMsgBox().hideLoading();
}
}, 1000);
}
}
@Override
public void onViewClick(View v) {
if (v.getId() == R.id.m_base_rl_title_left) {
getNavi().goBackWithResult();
} else if (v.getId() == R.id.m_base_tv_reloading) {
doReload();
}
}
好了,可以看到点击事件就是当按钮触发后需要发送的事件,我们这里可以看到发送了一个getAnaly().sendEvent(CountlyCodeConstBase.EVENT_NAME_NETWORK_ERROR_RELOAD_CLICK)事件,跟进去
@Override
public void sendEvent(@NonNull String eventName) {
this.sendEvent(eventName, new HashMap<>(4));
}
@Override
public void sendEvent(@NonNull String eventName, @NonNull String key, @NonNull Object value) {
this.sendEvent(eventName, ToolCountly.createParamsMap(key, value));
}
......
@Override
public void sendEvent(@Nullable EventTraceModel eventModel) {
if ((eventModel != null)) {
sendEventByGoogle(eventModel);
CountlySenderManager.getInstance().sendEvent(eventModel);
}
}
/**
* 发送Google Analytics Event事件
*/
private void sendEventByGoogle(@NonNull EventTraceModel eventModel) {
Map<String, String> segments = (eventModel.getSegments() == null) ? new HashMap<>(1) : eventModel.getSegments();
if (eventModel.getViewTraceModel() != null) {
segments.put("view_name", eventModel.getViewTraceModel().getViewName());
}
ToolGA.sendEvent(eventModel.getEventName(), getEventAction(segments), segments.get(EVT_LABEL), segments);
}
首先发了Analytics事件,然后常规走sendevent
/**
* 发送普通事件
*/
public void sendEvent(EventTraceModel eventTraceModel) {
if (checkEventTraceModel(eventTraceModel)) {
Map<String, String> segments = getDataCollectorMap(ICountlySender.SENDER_TYPE_EVENT);
// 事件名称
segments.put(ICountlySender.KEY_COUNTLY_EVENT_NAME, eventTraceModel.getEventName());
// 追加PV与Evt公共参数
putCommonSegments(segments, eventTraceModel.getViewTraceModel());
// 追加自定义参数
putCustomSegments(segments, eventTraceModel.getSegments());
// 发送数据
addJob(segments);
}
}
到这结束,又回到了普通事件分发(没看懂再回头看一整个事件流程)。
(三)其他事件:
这里有一点还没讲就是sendEvent可以在发送事件的同时携带其他参数例如
/**
* 搜索行为标识符
*/
public static final String PARAM_SEARCH_ID = "srid";
/**
* 搜索优化算法(服务端返回)
*/
public static final String PARAM_FLAG = "flag";
/**
* 地址Id
*/
public static final String PARAM_ADD_ID = "addid";
/**
* 仓库编号
*/
public static final String PARAM_WARE = "ware";
举个例子我搜索了PARAM_SEARCH_ID字段,我们看看调用
/**
* 商品点击埋点
*/
private void sendGoodClickCountlyEvent(int position, GoodsGeneralModel goodModel) {
getAnaly().sendEvent(CountlyCodeConst.EVENT_NAME_GOODS_CLICK
, new String[]{CountlyCodeConstBase.PARAM_GOODS_ID, CountlyCodeConstBase.PARAM_POSITION
, CountlyCodeConstBase.PARAM_ALGORITHM, CountlyCodeConstBase.PARAM_PRICE
, CountlyCodeConstBase.PARAM_KEY_WORD, CountlyCodeConst.PARAM_SEARCH_ID
, CountlyCodeConst.PARAM_FLAG}
, new String[]{goodModel != null ? String.valueOf(goodModel.getGoodsId()) : ""
, String.valueOf(position)
, goodModel != null ? goodModel.getUrlKey() : ""
, BusinessCommon.getPriceForCountly(goodModel != null
? goodModel.getShopPrice() : 0
, goodModel != null ? goodModel.getPromotePrice() : 0)
, mSearchValue
, String.valueOf(mSearchStamp4Countly)
, goodModel != null ? goodModel.getBiFlag() : ""});
}
可以看到sendEvent后两个参数是数组,第一个数组是key,第二个是value,这两个最后会同时一一对应放置到map中。
/**
* 统一创建事件参数集合 <br/>
* 作用:用于判断参数或者参数值为null或者空指针的情况
*
* @param keysArr 参数名称数组
* @param valuesArr 参数值数组
* @return 参数Map集合
*/
@Nullable
static Map<String, String> createParamsMap(String[] keysArr, String[] valuesArr) {
if (keysArr == null || valuesArr == null || keysArr.length != valuesArr.length) {
ToolException.throwIllegalAccessError(ToolCountly.class.getSimpleName(),
"createParamsMap() 键值对不统一");
return null;
}
Map<String, String> paramsMap = new HashMap<>();
for (int i = 0; i < keysArr.length; i++) {
if (!TextUtils.isEmpty(keysArr[i]) && !TextUtils.isEmpty(valuesArr[i])) {
paramsMap.put(keysArr[i], valuesArr[i]);
}
}
return paramsMap;
}
最后我们说的其他事件发送就是指,例如一个dialog在进入或者退出之后要发送的事件,这种特殊事件都被我列入其他类中
/**
* 联系客服弹框弹出
*/
public static final String EVENT_NAME_CONTACT_HOTLINE_POPUP_RESULT = "contacthotline_popup_result";
/**
* 显示客服辅助弹框
*/
private void showCustomerAidDialog() {
......
dialogVHelper.setCancelAble(false);
dialogVHelper.showVerticalBtnDialog("",
codConfigModel.getAidMessage(), codConfigModel.getAidNumberInfo(),
R.string.cancel, false, new BaseDialogFragmentForPopUp.OnDialogClickListener() {
@Override
public void onClick(BaseDialogFragmentForPopUp dialog, int which) {
mHasClickedAidCall = true;
BusinessCommon.jumpToPhonePanel(ActivityPayCodConfirm.this, getViewTraceModel().getPageViewId(),
codConfigModel.getAidNumber());
getAnaly().sendEvent(CountlyCodeConst.EVENT_NAME_CONTACT_HOTLINE_CLICK,
new String[]{CountlyCodeConstBase.PARAM_ORDER_ID},
new String[]{getPre().getSub().getOrderId() + ""});
}
}, new BaseDialogFragmentForPopUp.OnDialogClickListener() {
@Override
public void onClick(BaseDialogFragmentForPopUp dialog, int which) {
getAnaly().sendEvent(CountlyCodeConst.EVENT_NAME_CONTACT_HOTLINE_CANCEL_CLICK,
new String[]{CountlyCodeConstBase.PARAM_ORDER_ID},
new String[]{getPre().getSub().getOrderId() + ""});
}
});
getAnaly().sendEvent(CountlyCodeConst.EVENT_NAME_CONTACT_HOTLINE_POPUP_RESULT,
new String[]{CountlyCodeConstBase.PARAM_ORDER_ID},
new String[]{getPre().getSub().getOrderId() + ""});
}
这里就是一个dialog弹出的其他事件,点击之后退出的时候发送了一个senEvent.
(四)曝光事件。当前前台页面正在展现的页面.CountlyCodeConst需要曝光的常量在这个类中
/曝光/
/**
* 专题曝光
*/
public static final String EVENT_NAME_FEATURES_IMPRESSION = "features_impression";
/**
* 导航类目曝光
*/
public static final String EVENT_NAME_CATEGORIES_IMPRESSION = "categories_impression";
/**
* 推荐商品曝光
*/
public static final String EVENT_NAME_GOODS_COVER_IMPRESSION = "goods_cover_impression";
/**
* 搜索结果页推荐属性值曝光
*/
public static final String EVENT_NAME_RECOMMEND_WORD_IMPRESSION = "recommendword_impression";
依旧惯例举例专题曝光跟进代码。
private void sendExposureEvent() {
mAnalytics.sendExposureEvent(CountlyCodeConst.EVENT_NAME_FEATURES_IMPRESSION, mImpressModel);
}
@Override
public void onPageSelected(int position, List<HomeAdvertModel> mDataLists) {
if (mCountlyEnabled && mIsAttached) {
HomeAdvertModel advertModel = mDataLists.get(position);
if (advertModel != null) {
setInstantImpressData(mImpressModel, position, advertModel);
sendExposureEvent();
}
}
}
private void setInstantImpressData(ImpressParamsModel impressModel, int position, HomeAdvertModel model) {
impressModel.setPosition(position);
impressModel.setGoodsId(model.getGoodsId());
impressModel.setFeatureCode(model.getAdCode());
}
这里可以看出impressModel取值是从onPageSelected当前轮播图中取到的正在展现的UI图片,set了position、goodsId、Adcode。继续往下走
@Override
public void sendExposureEvent(String eventName, @NonNull ImpressParamsModel paramModel) {
CountlySenderManager.getInstance().sendEvent(eventName, mViewTraceModel, paramModel);
}
/**
* 发送曝光事件
*
* @param eventName 事件名称
* @param viewTraceModel 页面统计数据
* @param paramsModel 曝光参数
*/
public void sendEvent(String eventName, ViewTraceModel viewTraceModel, ImpressParamsModel paramsModel) { //曝光事件
if ((viewTraceModel != null) && (paramsModel != null)) {
Map<String, String> segments = getDataCollectorMap(ICountlySender.SENDER_TYPE_EVENT);
// 事件名称
segments.put(ICountlySender.KEY_COUNTLY_EVENT_NAME, eventName);
// 追加PV与Evt公共参数
putCommonSegments(segments, viewTraceModel);
// 追加曝光参数
putSegment(segments, CountlyCodeConstBase.PARAM_POSITION, paramsModel.getPosition() + "");
putSegment(segments, CountlyCodeConstBase.PARAM_GOODS_ID, (paramsModel.getGoodsId() == 0 ? "" : paramsModel.getGoodsId() + ""));
putSegment(segments, CountlyCodeConstBase.PARAM_FEATUER_CODE, paramsModel.getFeatureCode());
putSegment(segments, CountlyCodeConstBase.PARAM_PAGE_NUM, paramsModel.getTabCode());
putSegment(segments, CountlyCodeConstBase.PARAM_LABEL, paramsModel.getLabel());
putSegment(segments, CountlyCodeConstBase.PARAM_AREA, paramsModel.getArea());
putSegment(segments, CountlyCodeConstBase.PARAM_CATEGORY_ID, paramsModel.getCategoryId());
putSegment(segments, CountlyCodeConstBase.PARAM_ALGORITHM, paramsModel.getAlgorithm());
putSegment(segments, CountlyCodeConstBase.PARAM_PRICE, paramsModel.getPrice());
putSegment(segments, CountlyCodeConstBase.PARAM_KEY_WORD, paramsModel.getKeyword());
putSegment(segments, CountlyCodeConstBase.PARAM_TYPE, paramsModel.getType());
putSegment(segments, CountlyCodeConstBase.PARAM_PAGE_NUM, paramsModel.getPageNum());
putSegment(segments, CountlyCodeConstBase.PARAM_STORE_ID, paramsModel.getStoreId() == 0 ? "" : String.valueOf(paramsModel.getStoreId()));
// 发送数据
addJob(segments);
......
}
目前曝光所要带的所有可选参数和必带带参都在这,一个公共方法。
(五)事件原理。三个发送事件讲完之后,接下去讲底层发送事件实现原理,再回到
/**
* 发送普通事件
*/
public void sendEvent(EventTraceModel eventTraceModel) {
if (checkEventTraceModel(eventTraceModel)) {
Map<String, String> segments = getDataCollectorMap(ICountlySender.SENDER_TYPE_EVENT);
// 事件名称
segments.put(ICountlySender.KEY_COUNTLY_EVENT_NAME, eventTraceModel.getEventName());
// 追加PV与Evt公共参数
putCommonSegments(segments, eventTraceModel.getViewTraceModel());
// 追加自定义参数
putCustomSegments(segments, eventTraceModel.getSegments());
// 发送数据
addJob(segments);
}
}
/**
* 将Pv和Evt的发送任务放入后台线程执行
*
* @param segments 自定义参数
*/
private void addJob(@NonNull Map<String, String> segments) {
//停止发送任务Job
if (isStopSenderJob()) {
stopSenderJob();
}
getSenderJobOrCreateIfNull().addJob(segments);
}
/**
* 获取数据发送任务,如果没有则创建新的后台任务,并启动
*/
private CountlySenderJob getSenderJobOrCreateIfNull() {
if (mSenderJob == null) {
mSenderJob = new CountlySenderJob();
mSenderJob.start();
}
return mSenderJob;
}
这里有个实现了Thread方法的CountlySenderJob,我们直接可以看run方法
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mThreadBeginTime = System.currentTimeMillis();
// ToolLog.v2Console("CountlySenderJob start=====");
while (!mIsActiveQuit) {
try {
Map<String, String> segmentsMap = getSendingQueueOrCreateIfNull().take();
mLastGetDataTimeByThread = System.currentTimeMillis();
if (segmentsMap != null) {
String senderFlag = segmentsMap.remove(ICountlySender.KEY_SENDER_FLAG);
ICountlySender sender = getOrCreateSendersIfNull().get(senderFlag);
if ((sender != null) && (!segmentsMap.isEmpty())) {
sender.sendDataFacade(segmentsMap); // 发送Countly数据
CacheMapProvider.getInstance().recycleMap(segmentsMap);
} else {
saveErrorLogAndThrowException(senderFlag, sender);
}
......
}
这里主要做的一步是把,放入队列的我们要发送的数据map放入sender.sendDataFacade()方法
public class ActivityStartPvSender extends BaseSender {
@Override
public String getSenderFlag() {
return SENDER_FLAG_ACTIVITY_START;
}
@Override
public void sendData(@NonNull Map<String, String> segments) {
String viewName = segments.remove(CountlyCodeConstBase.PARAM_PAGE_NAME);
CountlyManager.getInstance().onActivityStart(viewName, segments);
}
以StartPvSender为例,从中取出当前viewName,走onActivityStart方法
/**
* Activity 显示在屏幕最顶端,发送PV
*
* @param viewName 页面名称
* @param segments 自定义参数
*/
public synchronized void onActivityStart(@NonNull String viewName, Map<String, String> segments) {
setPublicParams4OldUser();
Countly.sharedInstance().onActivityStart(viewName, segments);
}
/**
* 设置老用户公共参数
*/
private void setPublicParams4OldUser() {
if (!mIsOldUser) {
mIsOldUser = CountlyConfig.getInstance().isOldUser(ApplicationBase.getInstance());
// 设置新、老用户标志
Map<String, String> publicParams = new HashMap<>(1);
publicParams.put(CountlyCodeConstBase.PARAM_NEW, mIsOldUser ? "0" : "1");
Countly.sharedInstance().setJollyChicSourceMap(publicParams);
if (!mIsOldUser) {
CountlyConfig.getInstance().setOldUser(ApplicationBase.getInstance());
}
}
}
new了一个map一个为new的key,判断是否之前已经发送过这个PV如果是传0,不是则传1,之后设置到CountlyConfig中
Countly.sharedInstance().onActivityStart(viewName, segments);
之后调用Countly.sharedInstance().onActivityStart发送消息,结束。fragment和stop方法类似这里就不贴代码了,无非就是Countly调用onFragmentStart等等方法不一样,有兴趣的可以看看这里的代码
总结
所有的事件发起调用的方法都是SendEvent方法,下面有多种构造方法可以满足当前需要的发送事件需求。activity除了点击和特殊事件需要我们单独手动处理,PV事件在拦截器中已经做好了传值不需要单独手动去做;而fragment之间的切换,例如容器类我们切换fragment的同时需要自己做好携带参数。至此,事件埋点分析到这里结束。