[android] Added the ad lifecycle tracker

This commit is contained in:
alexzatsepin 2017-03-24 14:21:08 +03:00 committed by Vladimir Byko-Ianko
parent 2e0077ac23
commit b7650c297c
5 changed files with 164 additions and 28 deletions

View file

@ -2,8 +2,10 @@ package com.mapswithme.maps.ads;
import android.support.annotation.NonNull;
interface AdTracker
public interface AdTracker
{
void start(@NonNull String bannerId);
void stop(@NonNull String bannerId);
void onViewShown(@NonNull String bannerId);
void onViewHidden(@NonNull String bannerId);
void onContentObtained(@NonNull String bannerId);
boolean isImpressionGood(@NonNull String bannerId);
}

View file

@ -0,0 +1,125 @@
package com.mapswithme.maps.ads;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class DefaultAdTracker implements AdTracker, OnAdCacheModifiedListener
{
private final static Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private final static String TAG = DefaultAdTracker.class.getSimpleName();
private final static int GOOD_IMPRESSION_TIME_MS = 2000;
private final Map<String, TrackInfo> mTracks = new HashMap<>();
@Override
public void onViewShown(@NonNull String bannerId)
{
LOGGER.d(TAG, "onViewShown bannerId = " + bannerId);
TrackInfo info = mTracks.get(bannerId);
if (info != null)
info.setVisible(true);
}
@Override
public void onViewHidden(@NonNull String bannerId)
{
LOGGER.d(TAG, "onViewHidden bannerId = " + bannerId);
TrackInfo info = mTracks.get(bannerId);
if (info != null)
info.setVisible(false);
}
@Override
public void onContentObtained(@NonNull String bannerId)
{
LOGGER.d(TAG, "onContentObtained bannerId = " + bannerId);
TrackInfo info = mTracks.get(bannerId);
if (info == null)
throw new AssertionError("A track info must be put in a cache before a content is obtained");
info.fill();
}
@Override
public boolean isImpressionGood(@NonNull String bannerId)
{
TrackInfo info = mTracks.get(bannerId);
return info != null && info.getShowTime() > GOOD_IMPRESSION_TIME_MS;
}
@Override
public void onRemoved(@NonNull String id)
{
LOGGER.d(TAG, "onRemoved id = " + id);
mTracks.remove(id);
}
@Override
public void onPut(@NonNull String id)
{
LOGGER.d(TAG, "onPut id = " + id);
mTracks.put(id, new TrackInfo());
}
private static class TrackInfo
{
private long mTimestamp;
/**
* Accumulates amount of time that ad is already shown.
*/
private long mShowTime;
/**
* Indicates whether the ad view is visible or not.
*/
private boolean mVisible;
/**
* Indicates whether the ad content is obtained or not.
*/
private boolean mFilled;
public void setVisible(boolean visible)
{
// No need tracking if the ad is not filled with a content
if (!mFilled)
return;
// If ad becomes visible, and it's filled with a content the timestamp must be stored.
if (visible && !mVisible)
{
mTimestamp = SystemClock.elapsedRealtime();
}
// If ad is hidden the show time must be accumulated.
else if (!visible && mVisible)
{
mShowTime += SystemClock.elapsedRealtime() - mTimestamp;
mTimestamp = 0;
}
mVisible = visible;
}
public void fill()
{
// If the visible ad is filled with the content the timestamp must be stored
if (mVisible)
{
mTimestamp = SystemClock.elapsedRealtime();
}
mFilled = true;
}
private boolean isRealShow()
{
return mFilled && mVisible;
}
long getShowTime()
{
return mShowTime;
}
}
}

View file

@ -35,16 +35,19 @@ public class FacebookAdsLoader implements AdListener
/**
* Loads an ad for a specified placement id. If there is a cached ad, and it's not expired,
* that ad will be returned immediately. Otherwise, this method returns null, and {@link #mAdsListener} will
* be notified when the requested ad is loaded.
* the caller will be notified immediately through {@link FacebookAdsListener#onFacebookAdLoaded(NativeAd)}.
* Otherwise, the caller will be notified once an ad is loaded through the mentioned method.
*
* <br><br><b>Important note: </b> if there is a cached ad for the requested placement id, and that ad
* has a good impression indicator, and there is at least {@link #REQUEST_INTERVAL_MS} between the
* first time that ad was requested and the current time the new ad will be loaded.
*
* @param context An activity context.
* @param placementId A placement id that ad will be loaded for.
* @return A cached banner if it presents, otherwise <code>null</code>
* @param tracker An ad tracker
*/
@Nullable
@UiThread
public NativeAd load(@NonNull Context context, @NonNull String placementId)
public void load(@NonNull Context context, @NonNull String placementId, @NonNull AdTracker tracker)
{
LOGGER.d(TAG, "Load a facebook ad for a placement id '" + placementId + "'");
@ -54,17 +57,18 @@ public class FacebookAdsLoader implements AdListener
{
LOGGER.d(TAG, "There is no an ad in a cache");
loadAdInternal(context, placementId);
return null;
return;
}
if (/** Tracker.checkShowTime(placmenetId) &&**/
SystemClock.elapsedRealtime() - cachedAd.getLoadedTime() >= REQUEST_INTERVAL_MS)
if (tracker.isImpressionGood(placementId)
&& SystemClock.elapsedRealtime() - cachedAd.getLoadedTime() >= REQUEST_INTERVAL_MS)
{
LOGGER.d(TAG, "Ad should be reloaded");
LOGGER.d(TAG, "A new ad will be loaded because the previous one has a good impression");
loadAdInternal(context, placementId);
}
return cachedAd.getAd();
if (mAdsListener != null)
mAdsListener.onFacebookAdLoaded(cachedAd.getAd());
}
/**
@ -168,7 +172,7 @@ public class FacebookAdsLoader implements AdListener
{
mCache.remove(key);
if (mCacheListener != null)
mCacheListener.onRemove(key);
mCacheListener.onRemoved(key);
}
public interface FacebookAdsListener

View file

@ -4,6 +4,6 @@ import android.support.annotation.NonNull;
interface OnAdCacheModifiedListener
{
void onRemove(@NonNull String id);
void onRemoved(@NonNull String id);
void onPut(@NonNull String id);
}

View file

@ -14,6 +14,7 @@ import com.facebook.ads.Ad;
import com.facebook.ads.AdError;
import com.facebook.ads.NativeAd;
import com.mapswithme.maps.R;
import com.mapswithme.maps.ads.DefaultAdTracker;
import com.mapswithme.maps.ads.FacebookAdsLoader;
import com.mapswithme.maps.bookmarks.data.Banner;
import com.mapswithme.util.Config;
@ -74,6 +75,8 @@ final class BannerController
@NonNull
private final FacebookAdsLoader mAdsLoader;
@NonNull
private final DefaultAdTracker mAdTracker;
BannerController(@NonNull View bannerView, @Nullable BannerListener listener)
{
@ -90,6 +93,8 @@ final class BannerController
//TODO: pass as constructor arguments
mAdsLoader = new FacebookAdsLoader();
mAdsLoader.setAdsListener(new NativeAdsListener());
mAdTracker = new DefaultAdTracker();
mAdsLoader.setCacheListener(mAdTracker);
}
private void setErrorStatus(boolean value)
@ -141,19 +146,7 @@ final class BannerController
UiUtils.show(mFrame);
NativeAd data = mAdsLoader.load(mFrame.getContext(), mBanner.getId());
updateVisibility();
if (data != null)
{
LOGGER.d(TAG, "A cached ad '" + mBanner + "' is shown");
fillViews(data);
registerViewsForInteraction(data);
loadIconAndOpenIfNeeded(data, mBanner);
}
if (mOpened && mListener != null)
mListener.onSizeChanged();
mAdsLoader.load(mFrame.getContext(), mBanner.getId(), mAdTracker);
}
boolean isBannerVisible()
@ -213,6 +206,16 @@ final class BannerController
private void onChangedVisibility(@NonNull Banner banner, boolean isVisible)
{
if (TextUtils.isEmpty(banner.getId()))
{
LOGGER.e(TAG, "Banner must have a non-null id!", new Throwable());
return;
}
if (isVisible)
mAdTracker.onViewShown(banner.getId());
else
mAdTracker.onViewHidden(banner.getId());
}
void onChangedVisibility(boolean isVisible)
@ -305,6 +308,8 @@ final class BannerController
loadIconAndOpenIfNeeded(ad, mBanner);
mAdTracker.onContentObtained(ad.getPlacementId());
if (mListener != null && mOpened)
mListener.onSizeChanged();
}