android--BI统计事件分析



整个发送事件流程结构图



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的同时需要自己做好携带参数。至此,事件埋点分析到这里结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值