精细化曝光策略

阅读数:36 2019 年 9 月 23 日 10:09

目前贝壳找房 APP 端的曝光时机是写死的, 触发条件:卡片必须要完整展示在界面上; 在列表界面上下 / 左右滑动时单次 / 多次曝光同一个卡片。

现有方案的不足:
1. 门限条件应改为 API 下发的;
2. 缺少卡片在界面上显示的时长;

反例:
1. 比如说列表有 1000 条记录,快速滑动列表到最后一条;用户并没有看清中间的 900 多条记录,这时要不要为这些记录做曝光埋点?
2. 例如一个卡片高度为 100px,实际上只显示了 80px,是否要做一次曝光埋点。

精细化曝光策略

当前问题:

如果只滑动这个程度,目前 app 不会为“附近地图”做曝光埋点,但该卡片的主要信息都已经展示了

行业对标:
今日头条、手机百度的曝光埋点策略做的很细, 比如卡片划入、划出时间,卡片显示多少比例可以算曝光等等。

解决方案

参考今日头条、手机百度的做法,实现类似的曝光策略。

1. 为每种卡片设置不同的曝光策略;
2.APP 根据 API 下发的门限条件触发埋点;
3. 记录卡片移入、移出屏幕的时间, 统计每个卡片真正显示的时长;
4. 界面销毁、显示 / 隐藏是否触发曝光埋点。例如按 home 键时是否触发曝光埋点,再次进入是否触发埋点。 这些场景由 API 下发配置开关。

精细化曝光策略

双向队列缓存当前 RecyclerView 显示的所有 ViewHolder, 用于执行卡片的曝光埋点函数。

在监听 RecyclerView 滑动事件时得到第一个可见位置、最后一个可见位置,根据参数判断是上滑或下滑,通过判断 ViewHolder 的 itemView top、bottom 参数值得出刚刚移入屏幕的卡片显示比例, 并根据 API 下发的门限值 (最低显示比例) 记录开始时间,在卡片即将划出屏幕时 (API 下发的门限值) 触发曝光埋点。 从而得出卡片的显示周期。

精细化曝光策略

参考代码

滑动回调

复制代码
public class CardExposureHelper extends RecyclerView.OnScrollListener {
// 缓存卡片的双向队列
private Deque<BaseHomeCard> deque;
// 队列顶部 Card 的 position
private int preFirstExposure;
// 队列底部 Card 的 position
private int preLastExposure;
/**
* 处理垂直方向卡片曝光
* @param manager
* @param isUp 是否向上滑动
*/
private void onVerticalExposure(LinearLayoutManager manager,boolean isUp) {
int firstVisiblePosition = manager.findFirstVisibleItemPosition();
int lastVisiblePosition = manager.findLastVisibleItemPosition();
// 根据曝光比例判断第一个可见卡片是否需要曝光
firstVisiblePosition = isVerticalExposure(firstVisiblePosition)?firstVisiblePosition:firstVisiblePosition+1;
// 根据曝光比例判断最后一个可见卡片是否需要曝光
lastVisiblePosition = isVerticalExposure(lastVisiblePosition)?lastVisiblePosition:lastVisiblePosition-1;
// 第一次曝光,曝光所有符合曝光比例的 Card
if (preFirstExposure==0&&preLastExposure==0){
offerVerticalVisibleQueue(firstVisiblePosition,lastVisiblePosition,true);
}else if (isUp){
// 向上滑动,把顶部不可见 Card 从顶部出队,底部进入可曝光的卡片入队
popVerticalVisibleQueue(preFirstExposure,firstVisiblePosition-1,true);
offerVerticalVisibleQueue(preLastExposure+1,lastVisiblePosition,false);
}else {
// 对应向下滑动的策略
popVerticalVisibleQueue(lastVisiblePosition+1,preLastExposure,false);
offerVerticalVisibleQueue(firstVisiblePosition,preFirstExposure-1,true);
}
// 更新队列的顶部 position 和底部 position
preFirstExposure = firstVisiblePosition;
preLastExposure = lastVisiblePosition;
}
/**
* 入队操作
* @param start
* @param end
* @param isFirst 是否从顶部入队
*/
private void offerVerticalVisibleQueue(int start,int end,boolean isFirst){
if (start>=0 && end<recyclerView.getAdapter().getItemCount() && start<=end){
if (isFirst){
for (int i=end;i>=start;i--){
onVerticalItemSlideInto(i,true);
}
}else {
for (int i=start;i<=end;i++){
onVerticalItemSlideInto(i,false);
}
}
}
}
/**
* 出队操作
* @param start
* @param end
* @param isFirst 是否从顶部出队
*/
private void popVerticalVisibleQueue(int start,int end,boolean isFirst){
if (start>=0 && end<recyclerView.getAdapter().getItemCount() && start<=end){
if (isFirst){
for (int i=start;i<=end;i++){
onVerticalItemSlideOut(i,isFirst);
}
}else {
for (int i=end;i>=start;i--){
onVerticalItemSlideOut(i,isFirst);
}
}
}
}
/**
* 处理滑入(入队)可曝光的卡片
* @param position
* @param isFirst 是否从顶部滑入(入队)
*/
private void onVerticalItemSlideInto(int position,boolean isFirst){
BaseHomeCard card = getBaseHomeCard(position);
if (isFirst){
deque.offerFirst(card);
}else {
deque.offerLast(card);
}
// 回调卡片开始曝光事件
callItemExposure(card,position);
}
/**
* 处理滑出(出队)停止曝光的卡片
* @param position
* @param isFirst 是否从顶部滑出(出队)
*/
private void onVerticalItemSlideOut(int position,boolean isFirst){
BaseHomeCard card;
if (isFirst){
card = deque.removeFirst();
}else {
card = deque.removeLast();
}
// 回调卡片结束曝光事件
callItemEndExposure(card,position,isFirst);
}

展望

目前这是 APP 端做的技术储备, 如需上线仍需要产品经理做更细致的产品规划。

作者介绍:
高瑞 、贺宇成,Android 工程师,负责贝壳找房 app 安卓端研发工作。

本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。

原文链接:

https://mp.weixin.qq.com/s/TKgFlupncu-Fol-mH8OEYA

评论

发布