App Open Ads
Overview
App open ads are full-screen ads designed to appear during app launch moments. They are similar to interstitial ads but are specifically tailored for two key scenarios:
Cold start — When the user opens the app fresh (the app was not previously in memory). A splash or loading screen is typically shown before the ad.
Soft launch — When the user returns to the app from the background. The app is still in memory but was paused.
In both cases, the ad is displayed before the user reaches the main content. Users can dismiss the ad at any time.
Implementation Steps
At a high level, integrating app open ads involves the following:
- Build a manager class that preloads an ad so it's ready when needed.
- Display the ad when the app comes to the foreground.
- React to ad lifecycle and presentation callbacks.
Best Practices
App open ads are a great way to monetize your app's loading screen, but it's important to follow best practices so that your users continue to enjoy using your app:
- Show ads only during natural waiting moments. App open ads work best when users are already expecting a brief pause, such as during app launch or when returning from the background. Avoid surprising users with ads at unexpected times.
- Always show a splash or loading screen first. The user should never see actual app content before the ad appears. The expected flow is: splash screen → app open ad → app content.
- Initialize the SDK before loading ads. Make sure the BlueStack SDK has fully initialized before you attempt to load an app open ad. Loading ads before initialization may result in failed requests.
- Preload the ad early. Load the ad as soon as possible so there is no delay when it's time to show it. Avoid loading other ad formats in parallel, as this can strain device resources and reduce fill rates.
- Respect user experience with frequency controls. Avoid showing an ad on every single app open. Consider
strategies such as:
- Showing an ad on every second or third opportunity instead of every time.
- Requiring a minimum background duration (e.g., 30 seconds or 2 minutes) before showing an ad on soft launch.
- Skipping soft launch ads for a period after showing a cold start ad.
- Be mindful of new users. Hold off on showing app open ads until users have opened and used your app a few times. This helps build a positive first impression before introducing ads.
- Handle ad expiration. Preloaded ads expire after a certain period (typically 4 hours). Always verify that the ad is still valid before attempting to show it, and reload if it has expired.
- Coordinate your loading screen with the ad. If you have a loading screen running behind the app open ad and
it finishes before the user dismisses the ad, dismiss the loading screen in the
onAdDismissedcallback to ensure a smooth transition to your app content.
Create an App Open Ad
Implement a Manager Class
App open ads should appear immediately when the user opens or returns to your app, so it's important to have the ad loaded and ready before it's needed. The best approach is to create a manager class that takes care of loading ads ahead of time, checking whether a loaded ad is still valid, and displaying it at the right moment.
Create a class called AppOpenAdManager:
- Java
- Kotlin
import android.app.Activity;
import com.azerion.bluestack.appopen.AppOpenAd;
import com.azerion.bluestack.appopen.AppOpenAdListener;
public class AppOpenAdManager implements AppOpenAdListener {
private static final String TAG = "AppOpenAdManager";
private ActivityContextProvider activityContextProvider;
private AppOpenAd appOpenAd;
private OnShowAdCompleteListener onShowAdCompleteListener;
public boolean isShowingAd = false;
public AppOpenAdManager(ActivityContextProvider activityContextProvider) {
this.activityContextProvider = activityContextProvider;
}
public interface OnShowAdCompleteListener {
void onShowAdComplete();
}
public interface ActivityContextProvider {
Activity getActivity();
}
private void initializeAppOpenAd() {
if (appOpenAd == null) {
appOpenAd = new AppOpenAd("APP_OPEN_PLACEMENT_ID");
appOpenAd.setAppOpenAdListener(this);
}
}
}
import android.app.Activity
import com.azerion.bluestack.appopen.AppOpenAd
import com.azerion.bluestack.appopen.AppOpenAdListener
class AppOpenAdManager(private val activityContextProvider: ActivityContextProvider) :
AppOpenAdListener {
companion object {
private const val TAG = "AppOpenAdManager"
}
private var appOpenAd: AppOpenAd? = null
private var onShowAdCompleteListener: OnShowAdCompleteListener? = null
var isShowingAd = false
interface OnShowAdCompleteListener {
fun onShowAdComplete()
}
interface ActivityContextProvider {
fun getActivity(): Activity?
}
private fun initializeAppOpenAd() {
if (appOpenAd == null) {
appOpenAd = AppOpenAd("APP_OPEN_PLACEMENT_ID")
appOpenAd?.setAppOpenAdListener(this)
}
}
}
Standalone Creation
To create an app open ad directly, instantiate an AppOpenAd with a placement ID.
- Java
- Kotlin
import com.azerion.bluestack.appopen.AppOpenAd;
public class MainActivity extends AppCompatActivity {
private AppOpenAd appOpenAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appOpenAd = new AppOpenAd("APP_OPEN_PLACEMENT_ID");
}
}
import com.azerion.bluestack.appopen.AppOpenAd
class MainActivity : AppCompatActivity() {
private lateinit var appOpenAd: AppOpenAd
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appOpenAd = AppOpenAd("APP_OPEN_PLACEMENT_ID")
}
}
Load an App Open Ad
With AppOpenAdManager
The recommended way to load an app open ad is through the AppOpenAdManager class. Add the loadAd() method to
the manager class:
- Java
- Kotlin
public void loadAd(Activity activity) {
initializeAppOpenAd();
appOpenAd.load(activity);
}
fun loadAd(activity: Activity) {
initializeAppOpenAd()
appOpenAd?.load(activity)
}
Standalone Loading
App open ads can be loaded by calling the load(activity) method:
- Java
- Kotlin
appOpenAd.load(activity);
appOpenAd.load(activity)
Ad load call will preload an ad before you show it so that ads can be shown with zero latency when needed. This
preloaded ad will expire after a certain period. If you try to show an expired ad, you will get
AdError.AD_EXPIRED error in the onAdFailedToDisplay callback. Once the ad expires, you can call load again
using the existing instance to preload a new ad.
Show an Ad
With AppOpenAdManager
Before showing the ad, the manager checks whether an ad is already on screen. Add these methods to the
AppOpenAdManager class:
- Java
- Kotlin
public void showAdIfAvailable(Activity activity) {
showAdIfAvailable(activity, new OnShowAdCompleteListener() {
@Override
public void onShowAdComplete() {
// Empty because the user will go back to the activity that shows the ad.
}
});
}
public void showAdIfAvailable(Activity activity, OnShowAdCompleteListener onShowAdCompleteListener) {
initializeAppOpenAd();
if (isShowingAd) {
Log.d(TAG, "The app open ad is already showing.");
return;
}
if (appOpenAd == null || !appOpenAd.isReady()) {
onShowAdCompleteListener.onShowAdComplete();
loadAd(activity);
return;
}
this.onShowAdCompleteListener = onShowAdCompleteListener;
isShowingAd = true;
appOpenAd.show(activity);
}
fun showAdIfAvailable(activity: Activity) {
showAdIfAvailable(activity, object : OnShowAdCompleteListener {
override fun onShowAdComplete() {
// Empty because the user will go back to the activity that shows the ad.
}
})
}
fun showAdIfAvailable(activity: Activity, onShowAdCompleteListener: OnShowAdCompleteListener) {
initializeAppOpenAd()
if (isShowingAd) {
Log.d(TAG, "The app open ad is already showing.")
return
}
if (appOpenAd?.isReady() == false) {
onShowAdCompleteListener.onShowAdComplete()
loadAd(activity)
return
}
this.onShowAdCompleteListener = onShowAdCompleteListener
isShowingAd = true
appOpenAd?.show(activity)
}
Standalone Showing
To show an app open ad directly, check isReady() on the AppOpenAd instance and call show(activity).
- Java
- Kotlin
if (appOpenAd != null && appOpenAd.isReady()) {
appOpenAd.show(activity);
}
if (appOpenAd?.isReady() == true) {
appOpenAd?.show(activity)
}
Show the Ad During App Foregrounding
To display the ad whenever the user returns to your app, implement a lifecycle observer in your Application class. Create a custom Application class that integrates with Android's lifecycle callbacks:
- Java
- Kotlin
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.azerion.bluestack.MobileAds;
public class MyApplication extends Application
implements Application.ActivityLifecycleCallbacks,
DefaultLifecycleObserver,
AppOpenAdManager.ActivityContextProvider {
private AppOpenAdManager appOpenAdManager;
private Activity currentActivity;
@Override
public void onCreate() {
super.onCreate();
appOpenAdManager = new AppOpenAdManager(this);
registerActivityLifecycleCallbacks(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@Override
public void onActivityStarted(Activity activity) {
if (!appOpenAdManager.isShowingAd) {
currentActivity = activity;
}
}
@Override
public void onStart(LifecycleOwner owner) {
if (MobileAds.isInitialized() && currentActivity != null) {
// Don't show app open ad on launcher/splash activities
// as they manage their own app open ad flow
boolean shouldShowAd = !(currentActivity instanceof CustomLauncherActivity) &&
!(currentActivity instanceof SplashScreenActivity);
if (shouldShowAd) {
appOpenAdManager.showAdIfAvailable(currentActivity);
}
}
}
public void loadAd(Activity activity) {
appOpenAdManager.loadAd(activity);
}
public void showAdIfAvailable(Activity activity,
AppOpenAdManager.OnShowAdCompleteListener listener) {
appOpenAdManager.showAdIfAvailable(activity, listener);
}
@Override
public Activity getActivity() {
return currentActivity;
}
// Other ActivityLifecycleCallbacks methods...
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
@Override
public void onActivityResumed(Activity activity) {}
@Override
public void onActivityPaused(Activity activity) {}
@Override
public void onActivityStopped(Activity activity) {}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override
public void onActivityDestroyed(Activity activity) {}
}
import android.app.Activity
import android.app.Application
import android.os.Bundle
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.azerion.bluestack.MobileAds
class MyApplication : Application(),
Application.ActivityLifecycleCallbacks,
DefaultLifecycleObserver,
AppOpenAdManager.ActivityContextProvider {
private val appOpenAdManager = AppOpenAdManager(this)
private var currentActivity: Activity? = null
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
override fun onActivityStarted(activity: Activity) {
if (!appOpenAdManager.isShowingAd) {
currentActivity = activity
}
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
if (MobileAds.isInitialized()) {
currentActivity?.let { activity ->
// Don't show app open ad on launcher/splash activities
// as they manage their own app open ad flow
val shouldShowAd = activity !is CustomLauncherActivity &&
activity !is SplashScreenActivity
if (shouldShowAd) {
appOpenAdManager.showAdIfAvailable(activity)
}
}
}
}
fun loadAd(activity: Activity) {
appOpenAdManager.loadAd(activity)
}
fun showAdIfAvailable(
activity: Activity,
onShowAdCompleteListener: AppOpenAdManager.OnShowAdCompleteListener
) {
appOpenAdManager.showAdIfAvailable(activity, onShowAdCompleteListener)
}
override fun getActivity() = currentActivity
// Other ActivityLifecycleCallbacks methods...
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
}
Don't forget to register your custom Application class in the AndroidManifest.xml:
<manifest>
<application
android:name=".MyApplication"
... >
...
</application>
</manifest>
Destroying an App Open Ad
When you have finished displaying an app open ad, call destroy() to free resources.
- Java
- Kotlin
@Override
protected void onDestroy() {
if (appOpenAd != null) {
appOpenAd.destroy();
appOpenAd = null;
}
super.onDestroy();
}
override fun onDestroy() {
appOpenAd?.destroy()
appOpenAd = null
super.onDestroy()
}
Ad events
You can register an AppOpenAdListener on your AppOpenAd instance to receive notifications about ad lifecycle
events. The AppOpenAdListener extends FullScreenAdListener and provides the following callbacks:
Load Events
onAdLoaded()— Called when an ad is successfully loaded and ready to displayonAdFailedToLoad(Exception exception)— Called when an ad fails to load, with an exception describing the cause
Display Events (from FullScreenAdListener)
onAdDisplayed()— Called when the ad successfully displays full-screen contentonAdFailedToDisplay(Exception exception)— Called when the ad fails to display, with an exception describing the causeonAdClicked()— Called when a click is recorded for the adonAdDismissed()— Called when the ad is dismissed and the user returns to the app
Register for app open events
AppOpenAd delivers lifecycle and presentation events through the AppOpenAdListener interface. Use the
setAppOpenAdListener() method to register your listener:
- Java
- Kotlin
appOpenAd.setAppOpenAdListener(this);
appOpenAd.setAppOpenAdListener(this)
App Open ad lifecycle and display events
Implement the AppOpenAdListener interface in your AppOpenAdManager class to handle all ad events:
- Java
- Kotlin
@Override
public void onAdLoaded() {
Log.i(TAG, "App open ad loaded");
}
@Override
public void onAdFailedToLoad(Exception exception) {
Log.e(TAG, "App open ad failed to load", exception);
}
@Override
public void onAdDisplayed() {
Log.i(TAG, "App open ad displayed");
}
@Override
public void onAdFailedToDisplay(Exception exception) {
Log.e(TAG, "App open ad failed to display", exception);
isShowingAd = false;
if (onShowAdCompleteListener != null) {
onShowAdCompleteListener.onShowAdComplete();
}
Activity activity = activityContextProvider.getActivity();
if (activity != null) {
loadAd(activity);
}
}
@Override
public void onAdClicked() {
Log.i(TAG, "App open ad clicked");
}
@Override
public void onAdDismissed() {
Log.i(TAG, "App open ad dismissed");
isShowingAd = false;
if (onShowAdCompleteListener != null) {
onShowAdCompleteListener.onShowAdComplete();
}
Activity activity = activityContextProvider.getActivity();
if (activity != null) {
loadAd(activity);
}
}
override fun onAdLoaded() {
Log.i(TAG, "App open ad loaded")
}
override fun onAdFailedToLoad(exception: Exception) {
Log.e(TAG, "App open ad failed to load", exception)
}
override fun onAdDisplayed() {
Log.i(TAG, "App open ad displayed")
}
override fun onAdFailedToDisplay(exception: Exception) {
Log.e(TAG, "App open ad failed to display", exception)
isShowingAd = false
onShowAdCompleteListener?.onShowAdComplete()
activityContextProvider.getActivity()?.let { loadAd(it) }
}
override fun onAdClicked() {
Log.i(TAG, "App open ad clicked")
}
override fun onAdDismissed() {
Log.i(TAG, "App open ad dismissed")
isShowingAd = false
onShowAdCompleteListener?.onShowAdComplete()
activityContextProvider.getActivity()?.let { loadAd(it) }
}
Handling Cold Starts with Loading Screens
The examples above focus on showing app open ads when users return to an app that is already in memory (soft launch). Cold starts — when the app is launched fresh and was not previously in memory — require additional consideration.
During a cold start, there is no previously loaded ad ready to show immediately. The delay between requesting an ad and receiving one can create a situation where the user briefly sees app content before an ad unexpectedly appears. This is a poor user experience and should be avoided.
The recommended approach is to use a loading or splash screen during the app's startup sequence and only show the app open ad while that screen is still visible. Here's an example implementation:
- Java
- Kotlin
import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
import androidx.appcompat.app.AppCompatActivity;
import com.azerion.bluestack.MobileAds;
import com.azerion.bluestack.initialization.InitializationListener;
import com.azerion.bluestack.initialization.SDKInitializationStatus;
import java.util.concurrent.TimeUnit;
public class CustomLauncherActivity extends AppCompatActivity {
private static final String TAG = "CustomLauncherActivity";
// Simulate app loading time (5 seconds)
private static final long COUNTER_TIME_MILLISECONDS = 5000L;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
initializeCMP();
}
private void initializeCMP() {
// Initialize your Consent Management Platform here
// Once consent is obtained, initialize BlueStack SDK
initializeBlueStackSDK();
}
private void initializeBlueStackSDK() {
MobileAds.setDebugModeEnabled(true);
MobileAds.initialize(this, "YOUR_APP_ID", new InitializationListener() {
@Override
public void onInitialized(SDKInitializationStatus status) {
// Load the app open ad after initialization
((MyApplication) getApplication()).loadAd(CustomLauncherActivity.this);
}
});
createTimer();
}
private void navigateToMainActivity() {
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
private void createTimer() {
new CountDownTimer(COUNTER_TIME_MILLISECONDS, 1000) {
@Override
public void onTick(long millisUntilFinished) {
Log.d(TAG, "App is done loading in: " +
(TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) + 1));
}
@Override
public void onFinish() {
// Show the app open ad when the splash screen is ready to dismiss
((MyApplication) getApplication()).showAdIfAvailable(
CustomLauncherActivity.this,
new AppOpenAdManager.OnShowAdCompleteListener() {
@Override
public void onShowAdComplete() {
navigateToMainActivity();
}
}
);
}
}.start();
}
}
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.azerion.bluestack.MobileAds
import com.azerion.bluestack.initialization.InitializationListener
import com.azerion.bluestack.initialization.SDKInitializationStatus
import java.util.concurrent.TimeUnit
class CustomLauncherActivity : AppCompatActivity() {
private val TAG = "CustomLauncherActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
initializeCMP()
}
private fun initializeCMP() {
// Initialize your Consent Management Platform here
// Once consent is obtained, initialize BlueStack SDK
initializeBlueStackSDK()
}
private fun initializeBlueStackSDK() {
MobileAds.setDebugModeEnabled(true)
MobileAds.initialize(this, "YOUR_APP_ID", object : InitializationListener {
override fun onInitialized(status: SDKInitializationStatus) {
// Load the app open ad after initialization
(application as MyApplication).loadAd(this@CustomLauncherActivity)
}
})
createTimer()
}
private fun navigateToMainActivity() {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
startActivity(intent)
finish()
}
private fun createTimer() {
object : CountDownTimer(COUNTER_TIME_MILLISECONDS, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG, "App is done loading in: ${
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) + 1
}")
}
override fun onFinish() {
// Show the app open ad when the splash screen is ready to dismiss
(application as MyApplication).showAdIfAvailable(
this@CustomLauncherActivity,
object : AppOpenAdManager.OnShowAdCompleteListener {
override fun onShowAdComplete() {
navigateToMainActivity()
}
}
)
}
}.start()
}
companion object {
// Simulate app loading time (5 seconds)
private const val COUNTER_TIME_MILLISECONDS = 5000L
}
}
Follow these guidelines:
- Keep the loading screen visible until the ad is ready. Do not dismiss it or transition to app content before the ad has been shown.
- Show the ad from the loading screen only. If your app finishes loading and has already moved the user to the main content, do not show the ad — the moment has passed.
- Dismiss the loading screen in
onShowAdComplete. Wait for the callback before transitioning to app content. This ensures a smooth flow from splash screen → ad → app content with no flicker or content flash in between.
Consider Ad Expiration
A preloaded ad can become stale if too much time passes between loading and displaying. The BlueStack SDK handles
ad expiration internally. If you attempt to show an expired ad, you will receive an AdError.AD_EXPIRED error in
the onAdFailedToDisplay() callback.
When this occurs, the best practice is to:
- Clean up the expired ad reference
- Immediately load a fresh ad for the next opportunity
The AppOpenAdManager implementation above automatically handles this by reloading an ad in the
onAdFailedToDisplay() callback.
Control Ad Frequency
To maintain a positive user experience, avoid showing an app open ad on every single foreground event. Consider implementing frequency controls such as:
- Skip opportunities — Show an ad on every second or third app open instead of every time.
- Minimum background duration — Only show an ad if the user was away from the app for a certain amount of time (e.g., 30 seconds, 2 minutes, or 15 minutes).
- Cooldown after cold start — If you showed an ad during a cold start, skip soft launch ads for a set period afterward.
- Frequency caps — Limit the total number of app open ads shown per session or per day. Where possible, tailor caps based on user cohorts or engagement levels.