Optimizing eCPM for Interstitial Ads in Mobile Applications

Written by akk0rd87 | Published 2023/02/06
Tech Story Tags: mobile-ads | c++ | android | ios | ecpm | web-monetization | optimizing-ecpm | web-development

TLDRA cross-platform mobile app published on Google Play and Apple App Store. For monetization, I use fullscreen advertisement blocks from AdMob and Yandex. At the app level, I control how often I want to show the advertisement. Each interstitial ad block can be given a minimum eCPM threshold, or rely on optimization from the advertising network itself. Let's try to optimize it!via the TL;DR App

I have a cross-platform mobile app published on Google Play and Apple App Store. For monetization, I use fullscreen advertisement blocks (interstitials) from AdMob and Yandex advertising providers. At the app level, I control how often I want to show the advertisement.

For example, I show it when transitioning from one screen to another but not more than once every N (e.g. 3-6) minutes. So, when transitioning from one screen to another, we keep track of how many minutes/seconds have passed since the last display, and if it exceeds N minutes, I show the advertisement if it's currently loaded and I have something to display.

Integration with advertising providers

I will not discuss technical integration issues, you can read instructions on official websites: Admob, Yandex.

You should understand that for advertising display your app needs to send a request to the provider, and after some time the provider will notify you of the success or failure of its receipt in your app. In the second case, you can send repeated requests until the result is successful. Each request is associated with the identifier of a specific advertisement block. In this way, within N minutes we periodically send requests to providers (for example, by timer or some other event), until we receive a response about the success of advertising loading. And when the right moment comes, we show the advertisement.

It is important to emphasize that we will send requests to load advertisements simultaneously to all providers and only show one of the loaded advertisements according to the priority of the providers we set (in my case, this is determined by the order of their sequence in the provider array: the lower the provider index, the higher its priority).

Each interstitial ad block can be given a minimum eCPM threshold, or rely on optimization from the advertising network itself. What is eCPM can be read on support.google.com.

Below are screenshots from the control panel demonstrating the appearance of the eCPM setting.

We will set up several interstitial advertising blocks with different eCPM for each ad provider within our app: we will leave one of them (we will call it Default) with optimization at the provider level, and set different values for the rest and give them meaningful names.

It can't be seen from the Admob console screenshot, but the eCPM blocks are set up in this manner:

Ad unit

eCPM

JCross-droid-default

Google optimized (All prices)

JCross-droid-medium

Global floor of $1.00

JCross-droid-high

Global floor of $2.50

Solution

Thus, our optimization task is to try to load an ad with the highest possible eCPM within N minutes (i.e. from the previous display time to the desired next display time).

Split the time interval of N minutes into 3 unequal segments: High, Medium, Default. Formulate the algorithm for generating ad requests for one provider:

  1. In the first segment of time, we send ad requests for High Ad unit (Green). If we were able to load the ad during this time, proceed to step 5.

  2. If we were unable to load the ad during the High segment, change the Ad Unit identifier to Medium and form requests for it (Yellow). If we were able to load the ad during this time, proceed to step 5.

  3. If we were unable to load the ad during the Medium segment, change the Ad Unit identifier to Default and form requests for it (Blue). If we were able to load the ad during this time, proceed to step 5. If not, proceed to step 4.

  4. If we were unable to load the ad at moment N, continue requesting for the Default Ad Unit until we get a successful result.

  5. Stop forming requests and wait for moment N to show the ad.

Once an advertisement has been displayed at a certain moment, we repeat our algorithm from step one to obtain an advertisement for the next display.

Performance analysis

I will generate reports for the last 30 days and compare eCPM metrics. It is clear that the resulting eCPM (marked as green) significantly exceeds the default eCPM (marked as orange).

Implementation

My application is written in C++ using my open-source SDK, which in turn uses SDL2 under the hood. The directory containing the logic for working with advertisements is located at framework/ads.

If you want to delve deeper into the implementation, the simplified diagrams below describe the main entities and their relationships:

  1. The abstract class Provider serves to describe the interface of an advertisement provider.

  2. AdmobAds and YandexAds implement this interface.

  3. We need the ProviderCallback class, which is intended for the transfer of events from the provider to the Manager. Each provider has a reference to an object of the ProviderCallback class.

  4. Manager implements the interface of the ProviderCallback class and contains a list of providers (pointers to Provider).

During app initialization, it is necessary to create providers and bind them to the Manager:

adsMgr->SetIntersitialShowDelay(5 * 60); // N = 5 min between ad show
std::string InterstitialBlockID;

if (auto adMob = ads::AdMob::createProvider(adsMgr, ads::Format::Interstitial)) {
    GetAdMobAdIdentifier(InterstitialBlockID, 0);
    adMob->addInterstitialUnit(InterstitialBlockID, 20); // default

    GetAdMobAdIdentifier(InterstitialBlockID, 1);
    adMob->addInterstitialUnit(InterstitialBlockID, 60); // medium

    GetAdMobAdIdentifier(InterstitialBlockID, 2);
    adMob->addInterstitialUnit(InterstitialBlockID, 120); // high

    adsMgr->addProvider(adMob);
}

if (auto yandex = ads::Yandex::createProvider(adsMgr, ads::Format::Interstitial)) {
    GetYandexAdIdentifier(InterstitialBlockID, 0);
    yandex->addInterstitialUnit(InterstitialBlockID, 20); // default

    GetYandexAdIdentifier(InterstitialBlockID, 1);
    yandex->addInterstitialUnit(InterstitialBlockID, 60); // medium

    GetYandexAdIdentifier(InterstitialBlockID, 2);
    yandex->addInterstitialUnit(InterstitialBlockID, 120); // high

    adsMgr->addProvider(yandex);
}

The second parameter of the addInterstitialUnit function means the timestamp (in seconds) of the start of the segment relative to moment N. For Highest Ad unit, this parameter is ignored and the Highest Ad unit Ad unit is always set immediately regardless of the parameter value.

At some event (e.g. a timer), the function tryLoadInterstitial must be called, which initiates a request to load an advertisement if it is not loaded yet.:

adsMgr->tryLoadInterstitial();

Next, when the time is right, the show ad method will need to be called:

adsMgr->showInterstitialIfAvailable();

Thank you for your attention!


Written by akk0rd87 | C++ and Oracle PL/SQL Developer
Published by HackerNoon on 2023/02/06