低版本:使用View的onViewAttachedToWindow 和 onViewDetachedFromWindow 高版本:通过onWindowfocusChangedlistener 判断如果view获取focus就开始计时,失去focus就停止计时统计。 已在日活1000万以上的线上产品使用
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;
import androidx.annotation.RequiresApi;
import com.qisi.utils.app.LogUtils;
/**
* 通过监听view的添加删除和focus,判断是够展示
*/
public class ExRelativeLayout extends RelativeLayout implements View.OnAttachStateChangeListener, ExViewContract {
private long comeInTime;
private String pkgName;
private CustomFocusStateChangeListener focusStateChangeListener;
public ExRelativeLayout(Context context) {
super(context);
}
public ExRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* view添加到window
*
* @param v
*/
@Override
public void onViewAttachedToWindow(View v) {
comeInTime = System.currentTimeMillis();
}
/**
* view 从window移除
*
* @param v
*/
@Override
public void onViewDetachedFromWindow(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && focusStateChangeListener != null) {
this.getViewTreeObserver().removeOnWindowFocusChangeListener(focusStateChangeListener);
}
this.removeOnAttachStateChangeListener(this);
collectRootViewData();
}
/**
* 设置要追踪的包名
*
* @param pkgName
*/
@Override
public void setViewData(String pkgName) {
if (!TextUtils.isEmpty(pkgName)) {
this.pkgName = pkgName;
}
initListener();
}
/**
* 高版本监听focus,判断是否在前台
*/
private void initListener() {
this.addOnAttachStateChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
this.getViewTreeObserver().addOnWindowFocusChangeListener(focusStateChangeListener = new CustomFocusStateChangeListener());
}
}
/**
* 计算展示时间,产生展示上报
*/
private void collectRootViewData() {
long curTime = System.currentTimeMillis();
LogUtils.e("trackerlog", pkgName + " hasShownTime " + (curTime - comeInTime));
CustomAdsReporter.Companion.reportThemeShown(pkgName);
}
/**
* 高版本需要判断view是不是激活状态,如果被遮挡就不应该统计到曝光状态
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
private class CustomFocusStateChangeListener implements ViewTreeObserver.OnWindowFocusChangeListener {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus) {
comeInTime = System.currentTimeMillis();
} else {
collectRootViewData();
}
}
}
}
收集统计曝光
class CustomAdsReporter {
companion object {
private const val THEME_ADS_PKGS = "theme_ads_pkgs"
/**
* 展示
*/
fun reportThemeShown(pkgName: String) {
if (!isThemeAds(pkgName)) {
return
}
LogUtils.e("trackerlog", "reported shown $pkgName")
}
fun reportThemeClick(pkgName: String) {
if (!isThemeAds(pkgName)) {
return
}
LogUtils.e("trackerlog", "reported click $pkgName")
}
private fun isThemeAds(pkgName: String): Boolean {
LogUtils.e("trackerlog",RemoteConfigHelper.getInstance().getString(THEME_ADS_PKGS))
return RemoteConfigHelper.getInstance().getString(THEME_ADS_PKGS).contains(pkgName)
}
fun registerThemeTracker(pkgName: String, itemView: View) {
if (itemView is ExViewContract) {
itemView.setViewData(pkgName)
}
}
}
}