实现激励视频广告适配器

本指南适用于希望使用 AdMob 中介平台展示第三方广告联盟提供的激励视频广告的发布商。

AdMob 支持的广告联盟和自定义事件

AdMob 中介平台直接支持的广告联盟实现的是标准中介适配器。而 AdMob 间接支持的广告联盟实现的是自定义事件适配器,但仍可用于请求和展示激励视频广告。下文概述了标准中介适配器与自定义事件适配器之间的区别。

定义服务器参数

对于通过 AdMob 中介平台进行中介的广告联盟,可能需要使用一个或多个标识符才能识别发布商。这些标识符以服务器参数的形式表示,当您在 AdMob 界面中配置用于中介的第三方广告联盟时,可以定义这些标识符。

如果您要构建自定义事件适配器,请按照下文有关创建自定义事件的后续步骤操作。如果构建适配器,您的广告联盟和相应的服务器参数将作为广告联盟初始配置流程的一部分在 AdMob 中进行配置。

创建自定义事件

要定义自定义事件,必须先在 AdMob 界面中创建自定义事件。有关创建自定义事件的说明,请参阅添加自定义事件。定义自定义事件后,它会指向您应用中某个通过实现 MediationRewardedVideoAdAdapter 来投放激励视频广告的类。此外,自定义事件还会列出传递给激励视频广告适配器的服务器参数。

以下屏幕截图显示的是自定义事件示例的部分设置:

该屏幕截图包含以下条目:

类名称
实现自定义事件的类的完全限定名称。
标签
事件的唯一名称。
参数
传递给自定义事件的可选参数。

实现中介适配器

本部分介绍了实现适配器所需执行的步骤。为了显示实践中的适配器代码可能是什么样子,我们创建了一个示例广告联盟 SDK,并会为该 SDK 构建适配器。

初始化适配器

在应用发出初始激励视频广告请求后,Google 移动广告 SDK 会调用适配器的 initialize() 方法。

适配器会负责实现此方法,以初始化第三方广告联盟。此方法会提供完成初始化所需的一组 serverParameters。根据您要开发的是中介适配器还是自定义事件适配器,这些参数的获取方式略有不同。

对于中介适配器,服务器参数的密钥已预先配置,您可以逐个提取服务器参数。对于自定义事件,有一个参数可通过 MediationRewardedVideoAdAdapter.CUSTOM_EVENT_SERVER_PARAMETER_FIELD 密钥获取:

中介适配器

String parameter1 = serverParameters.getString("SERVER_PARAMETER_KEY1");
    String parameter2 = serverParameters.getString("SERVER_PARAMETER_KEY2");
    

自定义事件

String parameter = serverParameters.getString(MediationRewardedVideoAdAdapter.CUSTOM_EVENT_SERVER_PARAMETER_FIELD);
    

适配器应保留对 MediationRewardedVideoAdListener 实例的引用,以便将广告事件转发给 Google 移动广告 SDK。如果初始化成功,适配器应调用 MediationRewardedVideoAdListener.onInitializationSucceeded()

如果初始化失败,则适配器应调用 MediationRewardedVideoAdListener.onInitializationFailed()。在初始化失败的情况下,每当有激励视频广告请求发出时,Google 移动广告 SDK 都会重新尝试初始化适配器。下面展示了 initialize() 方法的实现示例:

@Override
    public void initialize(Context context,
        MediationAdRequest mediationAdRequest,
        String unused,
        MediationRewardedVideoAdListener listener,
        Bundle serverParameters,
        Bundle mediationExtras) {

        // In this method you should initialize your SDK.

        // The sample SDK requires activity context to initialize, so check
        // that the context provided by the app is an activity context before
        // initializing.
        if (!(context instanceof Activity)) {
            // Context not an Activity context, log the reason for failure and
            // fail the initialization.
            Log.d(TAG, "Sample SDK requires an Activity context to initialize");
            listener.onInitializationFailed(
                SampleAdapter.this, AdRequest.ERROR_CODE_INVALID_REQUEST);
            return;
        }

        // Get the Ad Unit ID for the Sample SDK from serverParameters bundle.
        String adUnit = serverParameters.getString(
            MediationRewardedVideoAdAdapter.CUSTOM_EVENT_SERVER_PARAMETER_FIELD);

        if (TextUtils.isEmpty(adUnit)) {
            listener.onAdFailedToLoad(this, AdRequest.ERROR_CODE_INVALID_REQUEST);
            return;
        }

        // Create a rewarded video event forwarder to forward the events from
        // the Sample SDK to the Google Mobile Ads SDK.
        mRewardedVideoEventForwarder =
            new SampleMediationRewardedVideoEventForwarder(listener, SampleAdapter.this);

        // Initialize the Sample SDK.
        SampleRewardedVideo.initialize((Activity) context, adUnit, mRewardedVideoEventForwarder);
    }
    
public class SampleMediationRewardedVideoEventForwarder extends SampleRewardedVideoAdListener {
        ...
        @Override
        public void onRewardedVideoInitialized() {
            super.onRewardedVideoInitialized();
            mIsInitialized = true;
            mMediationRewardedVideoAdListener.onInitializationSucceeded(mSampleAdapter);
        }

        @Override
        public void onRewardedVideoInitializationFailed(SampleErrorCode error) {
            super.onRewardedVideoInitializationFailed(error);
            mIsInitialized = false;
            mMediationRewardedVideoAdListener.onInitializationFailed(
                mSampleAdapter, getAdMobErrorCode(error));
        }
    }
    

返回初始化状态

适配器的 isInitialized() 方法应返回 false,直到适配器首次调用 MediationRewardedVideoAdListener.onInitializationSucceeded()。之后,该方法应该始终会返回 true,如下所示:

@Override
    public boolean isInitialized() {
        return mRewardedVideoEventForwarder != null && mRewardedVideoEventForwarder.isInitialized();
    }
    

加载激励视频广告

在适配器调用 MediationRewardedVideoAdListener.onInitializationSucceeded() 方法后,Google 移动广告 SDK 便可以调用适配器的 loadAd() 方法。此方法应请求激励视频广告,并且一旦有要展示的激励视频广告,适配器就会调用 MediationRewardedVideoAdListener.onAdLoaded()。如果没有,则适配器应调用 MediationRewardedVideoAdListener.onAdFailedToLoad()

对于激励视频广告,很多广告联盟会在不打算使用广告请求的情况下采用 API。在此类广告联盟完成初始化后,您可以检查对于当前用户是否有可用的广告资源,以及广告是否已准备就绪。如果条件满足,您就可以向用户展示广告了。下面展示了针对此类广告联盟的 loadAd() 实现示例。

@Override
    public void loadAd(MediationAdRequest mediationAdRequest,
        Bundle serverParameters,
        Bundle mediationExtras) {
        if (SampleRewardedVideo.isAdAvailable()) {
            // Ad already available, use the forwarder to send a success callback
            // to AdMob.
            mRewardedVideoEventForwarder.onAdLoaded();
        } else {
            // No ad available, use the forwarder to send a failure callback.
            mRewardedVideoEventForwarder.onAdFailedToLoad();
        }
    }
    

对于第一个广告请求,SDK 可能会在适配器调用 MediationRewardedVideoAdListener.onInitializationSucceeded() 方法后马上调用 loadAd()。如果此时广告联盟仍在加载广告,立即报告广告无法加载这一情况可能会造成初始激励视频广告请求中出现很大比例的广告空缺情况。在这种情况下,我们建议适配器跟踪广告于何时开始加载,并在确定广告加载成功或失败之后,再响应 Google 移动广告 SDK。为应对适配器出现响应延迟,Google 移动广告 SDK 设置了超时。

展示激励视频广告

在收到适配器发出的广告已成功加载的通知后,Google 移动广告 SDK 可能会随时调用适配器的 showVideo() 方法。对于每个成功完成的 loadAd() 回调,只能调用一次 showVideo() 方法。调用完此方法后,适配器就应该可以播放激励视频广告了。

@Override
    public void showVideo() {
        // Show the rewarded video ad.
        if (SampleRewardedVideo.isAdAvailable()) {
            // Rewarded video ad available, show ad.
            SampleRewardedVideo.showAd();
        } else {
            // Show ad will only be called if the adapter sends back an ad
            // loaded callback in response to a loadAd request. If for any
            // reason the adapter is not ready to show an ad after sending
            // an ad loaded callback, log a warning.
            Log.w(TAG, "No ads to show.");
        }
    }
    

定义奖励

当适配器准备好奖励用户时,必须先创建一个符合 RewardItem 接口的对象。下面展示了 RewardItem 的实现示例:

/**
    * A {@link RewardItem} that maps the sample reward type and reward amount.
    */
    public class SampleRewardItem implements RewardItem {
        private String mRewardType;
        private int mRewardAmount;

        /**
        * Creates a {@link SampleRewardItem}.
        *
        * @param rewardType   the sample reward type.
        * @param rewardAmount the sample reward amount.
        */
        public SampleRewardItem(String rewardType, int rewardAmount) {
            this.mRewardType = rewardType;
            this.mRewardAmount = rewardAmount;
        }

        @Override
        public String getType() {
            return mRewardType;
        }

        @Override
        public int getAmount() {
            return mRewardAmount;
        }
    }
    

要通知 Google 移动广告 SDK 某用户应获得奖励,适配器必须以 RewardItem 的实例为参数来调用 MediationRewardedVideoAdListener.onRewarded() 方法。

向 Google 移动广告 SDK 转发广告事件

当发生以下任一广告事件时,适配器都必须通知 Google 移动广告 SDK:

MediationRewardedVideoAdListener 方法 调用时间
onAdOpened() 当应用内容之上叠加全屏叠加层时
onVideoStarted() 当视频广告开始播放时
onAdClicked() 当用户点击视频广告时
onAdLeftApplication() 当用户因为要观看视频广告而退出应用(例如,转到浏览器)时
onRewarded() 当视频广告奖励用户时
onAdClosed() 当视频广告关闭时

下面展示了向 Google 移动广告 SDK 转发广告的实现示例:

/**
    * A SampleRewardedVideoAdListener that forwards events to AdMob mediation's
    * link.
    */
    public class SampleMediationRewardedVideoEventForwarder extends SampleRewardedVideoAdListener {
        private MediationRewardedVideoAdListener mMediationRewardedVideoAdListener;
        private SampleAdapter mSampleAdapter;
        private boolean mIsInitialized;

        /**
        * Creates a new SampleMediationRewardedVideoEventForwarder.
        *
        * @param listener      An AdMob Mediation MediationRewardedVideoAdListener that should
        *                      receive forwarded events.
        * @param sampleAdapter A SampleAdapter mediation adapter.
        */
        public SampleMediationRewardedVideoEventForwarder(MediationRewardedVideoAdListener listener,
        SampleAdapter sampleAdapter) {
            this.mMediationRewardedVideoAdListener = listener;
            this.mSampleAdapter = sampleAdapter;
        }

        /**
        * @return whether or not the Sample SDK is initialized.
        */
        public boolean isInitialized() {
            return mIsInitialized;
        }

        @Override
        public void onRewardedVideoInitialized() {
            super.onRewardedVideoInitialized();
            mIsInitialized = true;
            mMediationRewardedVideoAdListener.onInitializationSucceeded(mSampleAdapter);
        }

        @Override
        public void onRewardedVideoInitializationFailed(SampleErrorCode error) {
            super.onRewardedVideoInitializationFailed(error);
            mIsInitialized = false;
            mMediationRewardedVideoAdListener.onInitializationFailed(
            mSampleAdapter, getAdMobErrorCode(error));
        }

        @Override
        public void onAdRewarded(final String rewardType, final int amount) {
            super.onAdRewarded(rewardType, amount);

            /*
            * AdMob requires a reward item with a reward type and amount to be
            * sent when sending the rewarded callback. If your SDK does not
            * have a reward amount you need to do the following:
            *
            * 1. AdMob provides an ability to override the reward value in the
            * front end. Document this asking the publisher to override the
            * reward value on AdMob's front end.
            *
            * 2. Send a reward item with default values for the type (an empty
            * string "") and reward amount (1).
            */
            mMediationRewardedVideoAdListener.onRewarded(
                    mSampleAdapter, new SampleRewardItem(rewardType, amount));
        }

        @Override
        public void onAdClicked() {
            super.onAdClicked();
            mMediationRewardedVideoAdListener.onAdClicked(mSampleAdapter);
        }

        @Override
        public void onAdFullScreen() {
            super.onAdFullScreen();
            mMediationRewardedVideoAdListener.onAdOpened(mSampleAdapter);
            // Only send video started here if your SDK starts video
            // immediately after the ad has been opened/is fullscreen.
            mMediationRewardedVideoAdListener.onVideoStarted(mSampleAdapter);
        }

        @Override
        public void onAdClosed() {
            super.onAdClosed();
            mMediationRewardedVideoAdListener.onAdClosed(mSampleAdapter);
        }

        /**
        * Forwards the ad loaded event to AdMob SDK. The Sample SDK does not
        * have an ad loaded callback, the adapter calls this method if an ad
        * is available when loadAd is called.
        */
        protected void onAdLoaded() {
            mMediationRewardedVideoAdListener.onAdLoaded(mSampleAdapter);
        }

        /**
        * Forwards the ad failed event to AdMob SDK. The Sample SDK does not
        * have an ad failed to load callback, the adapter calls this method
        * to forward the failure callback.
        */
        protected void onAdFailedToLoad() {
            mMediationRewardedVideoAdListener.onAdFailedToLoad(
            mSampleAdapter, AdRequest.ERROR_CODE_NO_FILL);
        }

        /**
        * Converts SampleErrorCode into an AdMob SDK's AdRequest error code.
        *
        * @param errorCode a Sample SDK error code.
        * @return an AdMob SDK readable error code.
        */
        private int getAdMobErrorCode(SampleErrorCode errorCode) {
            switch (errorCode) {
                case BAD_REQUEST:
                return AdRequest.ERROR_CODE_INVALID_REQUEST;
            case NETWORK_ERROR:
                return AdRequest.ERROR_CODE_NETWORK_ERROR;
            case NO_INVENTORY:
                return AdRequest.ERROR_CODE_NO_FILL;
            case UNKNOWN:
            default:
                return AdRequest.ERROR_CODE_INTERNAL_ERROR;
            }
        }
    }
    

第三方广告联盟可能为其广告联盟实现了广告事件监听器。来自第三方 SDK 的广告事件可映射到适配器的 initialize() 方法中提供的 MediationRewardedVideoAdListener 的相应广告事件,并可在适当的时间调用。

上下文更改通知

您的适配器可以选择实现 OnContextChangedListener,以在当前上下文发生更改时获取通知。当 RewardedVideoAd 对象的当前上下文有更新时,系统会调用 onContextChanged() 方法。更新后的 Activity 上下文可以按如下方法访问:

@Override
    public void onContextChanged(Context context) {
        if (context instanceof Activity) {
            SampleRewardedVideo.setCurrentActivity((Activity) context);
        }
    }
    

其他定位参数

MediationAdRequest 包含一些常用的定位信息,可供您在广告定位时使用,例如:

接受适配器的自定义参数

对于适配器通过中介联系的广告联盟而言,可支持 MediationAdRequest 中提供的信息所未涉及的额外定位参数或输入内容。如果您的适配器也属于这种情况,可要求发布商为您的广告联盟提供一个信息 Bundle。该 Bundle 会传递至适配器的 initialize()loadAd() 方法。

例如,发布商可以通过以下方法向 SampleAdapter 适配器提供 Bundle:

Bundle bundle = new Bundle();
    bundle.putBoolean("ShouldAddAwesomeSauce", true);
    AdRequest adRequest = new AdRequest.Builder()
            .addNetworkExtrasBundle(SampleAdapter.class, bundle)
            .build();
    

要让开发者更轻松地为您创建此 Bundle,最佳做法是向您的适配器添加便捷类,帮助发布商为您的适配器构建 Bundle:

public static final class MediationExtrasBundleBuilder {

        // Keys to add and obtain the extra parameters from the bundle.
        private static final String KEY_AWESOME_SAUCE = "awesome_sauce";

        /**
        * An extra value used to populate the "ShouldAddAwesomeSauce" property
        * of the Sample SDK's ad request.
        */
        private boolean mShouldAddAwesomeSauce;

        public MediationExtrasBundleBuilder setShouldAddAwesomeSauce(
                boolean shouldAddAwesomeSauce) {
            this.mShouldAddAwesomeSauce = shouldAddAwesomeSauce;
            return MediationExtrasBundleBuilder.this;
        }

        public MediationExtrasBundleBuilder setIncome(int income) {
            this.mIncome = income;
            return MediationExtrasBundleBuilder.this;
        }

        public Bundle build() {
            Bundle extras = new Bundle();
            extras.putInt(KEY_INCOME, mIncome);
            return extras;
        }
    }
    

此便捷类会为开发者提供一个更简洁的 API,以便为您的广告联盟生成 Bundle:

Bundle sampleAdNetworkBundle =
            new SampleAdapter.MediationExtrasBundleBuilder().setShouldAddAwesomeSauce(true).build();