앱 오프닝 광고

이 가이드는 Google 모바일 광고 Android SDK를 사용하여 앱 오프닝 광고를 통합하려는 게시자를 대상으로 작성되었습니다.

앱 오프닝 광고는 앱 로드 화면으로 수익을 올리려는 게시자를 위한 특별한 광고 형식입니다. 앱 오프닝 광고는 언제든 닫을 수 있으며 사용자가 앱을 포그라운드로 가져올 때 표시되도록 설계되었습니다.

앱 오프닝 광고는 사용자가 앱을 사용 중임을 알 수 있도록 작은 브랜딩 영역을 자동으로 표시합니다. 앱 오프닝 광고는 다음과 같이 게재됩니다.

크게 다음과 같은 주요 단계가 있습니다.

  1. Application 클래스를 확장하여 GMA SDK를 초기화합니다.
  2. 광고를 표시하기 전에 광고를 로드하는 유틸리티 클래스를 생성합니다.
  3. 광고를 로드합니다.
  4. ActivityLifecycleCallbacks.를 수신합니다.
  5. 광고를 표시하고 콜백을 처리합니다.
  6. 포그라운드 이벤트 도중 광고를 표시할 수 있도록 LifecycleObserver 인터페이스를 구현하고 등록합니다.

기본 요건

  • Google 모바일 광고 SDK 19.4.0 이상
  • 시작 가이드의 설정 안내를 따릅니다.

항상 테스트 광고로 테스트

앱을 제작하고 테스트할 때 운영 중인 실제 광고가 아닌 테스트 광고를 사용하세요. 이렇게 하지 않으면 계정이 정지될 수 있습니다.

테스트 광고를 로드하는 가장 쉬운 방법은 앱 오프닝 광고 전용 테스트 광고 단위 ID를 사용하는 것입니다.

ca-app-pub-3940256099942544/3419835294

이 ID는 모든 요청에 대해 테스트 광고를 반환하도록 특별히 구성되었으며, 코딩, 테스트, 디버깅 중에 앱에서 자유롭게 사용할 수 있습니다. 앱을 게시하기 전에 이 ID를 자체 광고 단위 ID로 바꿔야 합니다.

모바일 광고 SDK의 테스트 광고가 작동하는 방식을 자세히 알아보려면 테스트 광고를 참고하세요.

Application 클래스 확장

MyApplication이라는 새 클래스를 만들고 다음 코드를 추가합니다.

package com.google.android.gms.example.appopendemo;

import android.app.Application;
import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.initialization.InitializationStatus;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;

/** The Application class that manages AppOpenManager. */
public class MyApplication extends Application {

  @Override
  public void onCreate() {
    super.onCreate();
    MobileAds.initialize(
        this,
        new OnInitializationCompleteListener() {
          @Override
          public void onInitializationComplete(InitializationStatus initializationStatus) {}
        });
  }
}

이렇게 하면 SDK가 초기화되고 나중에 앱 포그라운드 이벤트에 등록할 수 있는 스켈레톤이 제공됩니다.

다음으로 AndroidManifest.xml에 아래의 코드를 추가합니다.

<application
    android:name="com.google.android.gms.example.appopendemo.MyApplication" ...>
...
</application>

반드시 실제 패키지 이름을 참조하세요.

유틸리티 클래스 구현

광고는 빠르게 게재되어야 하므로 광고를 게재하기 전에 미리 로드하는 것이 좋습니다. 이렇게 하면 사용자가 앱을 실행하자마자 바로 광고를 게재할 수 있습니다. 광고를 표시해야 하는 시점에 앞서 미리 광고 요청을 할 수 있도록 유틸리티 클래스를 구현합니다.

AppOpenManager라는 새 클래스를 만들고 다음과 같이 작성합니다.

import static androidx.lifecycle.Lifecycle.Event.ON_START;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.appopen.AppOpenAd;
import java.util.Date;

/** Prefetches App Open Ads. */
public class AppOpenManager {
  private static final String LOG_TAG = "AppOpenManager";
  private static final String AD_UNIT_ID = "ca-app-pub-3940256099942544/3419835294";
  private AppOpenAd appOpenAd = null;

  private AppOpenAd.AppOpenAdLoadCallback loadCallback;

  private final MyApplication myApplication;

  /** Constructor */
  public AppOpenManager(MyApplication myApplication) {
    this.myApplication = myApplication;
  }

  /** Request an ad */
  public void fetchAd() {
    // We will implement this below.
  }

  /** Creates and returns ad request. */
  private AdRequest getAdRequest() {
    return new AdRequest.Builder().build();
  }

  /** Utility method that checks if ad exists and can be shown. */
  public boolean isAdAvailable() {
    return appOpenAd != null;
  }
}

이 클래스 셸은 로드된 광고, 광고 단위 ID, AppOpenAdLoadCallback을 추적하는 인스턴스 변수를 관리합니다.

이제 유틸리티 클래스가 있으므로 MyApplication 클래스에서 이 클래스를 인스턴스화할 수 있습니다.

/** The Application class that manages AppOpenManager. */
public class MyApplication extends Application {

  private static AppOpenManager appOpenManager;

  @Override
  public void onCreate() {
    super.onCreate();
    MobileAds.initialize(
        this,
        new OnInitializationCompleteListener() {
          @Override
          public void onInitializationComplete(InitializationStatus initializationStatus) {}
        });

    appOpenManager = new AppOpenManager(this);

  }
}

광고 로드

다음 단계에서는 fetchAd() 메서드를 작성합니다. AppOpenManager 클래스에 다음 코드를 추가합니다.

/** Prefetches App Open Ads and handles lifecycle detection. */
public class AppOpenManager {
  ...
  /** Request an ad */
  public void fetchAd() {
    // Have unused ad, no need to fetch another.
    if (isAdAvailable()) {
      return;
    }

    loadCallback =
        new AppOpenAd.AppOpenAdLoadCallback() {
          /**
           * Called when an app open ad has loaded.
           *
           * @param ad the loaded app open ad.
           */
          @Override
          public void onAppOpenAdLoaded(AppOpenAd ad) {
            AppOpenManager.this.appOpenAd = ad;
          }

          /**
           * Called when an app open ad has failed to load.
           *
           * @param loadAdError the error.
           */
          @Override
          public void onAppOpenAdFailedToLoad(LoadAdError loadAdError) {
            // Handle the error.
          }

        };
    AdRequest request = getAdRequest();
    AppOpenAd.load(
        myApplication, AD_UNIT_ID, request,
        AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT, loadCallback);
  }
  ...
}

AppOpenAdLoadCallback에는 AppOpenAd 로드가 완료되면 호출되는 메서드가 있습니다.

현재 활동 추적

광고를 표시하려면 Activity 컨텍스트가 필요합니다. 사용자가 이용한 최신 Activity 컨텍스트를 추적하려면 AppOpenManager 클래스에서 Application.ActivityLifecycleCallbacks 인터페이스를 구현하도록 합니다.

...
/** Prefetches App Open Ads and handles lifecycle detection. */
public class AppOpenManager implements Application.ActivityLifecycleCallbacks {

  private Activity currentActivity;

  ...

  /** ActivityLifecycleCallback methods */
  @Override
  public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

  @Override
  public void onActivityStarted(Activity activity) {
    currentActivity = activity;
  }

  @Override
  public void onActivityResumed(Activity activity) {
    currentActivity = activity;
  }

  @Override
  public void onActivityStopped(Activity activity) {}

  @Override
  public void onActivityPaused(Activity activity) {}

  @Override
  public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}

  @Override
  public void onActivityDestroyed(Activity activity) {
    currentActivity = null;
  }
}

현재 활동을 추적하여 광고를 표시하는 데 사용할 수 있는 컨텍스트를 확보합니다. 이제 AppOpenManager 생성자의 registerActivityLifecycleCallbacks Application 메서드를 사용하여 이 인터페이스를 등록해야 합니다.

/** Constructor */
public AppOpenManager(MyApplication myApplication) {
  this.myApplication = myApplication;
  this.myApplication.registerActivityLifecycleCallbacks(this);
}

registerActivityLifecycleCallbacks를 사용하면 모든 Activity 이벤트를 수신할 수 있습니다. 활동이 시작되고 소멸될 때를 수신하여 현재 Activity에 대한 참조를 추적할 수 있고, 이 참조는 앱 오프닝 광고를 표시하는 데 사용됩니다.

광고 표시 및 전체 화면 콜백 이벤트 처리

AppOpenManager 클래스에 다음 메서드를 추가합니다.

/** Prefetches App Open Ads and handles lifecycle detection. */
public class AppOpenManager implements Application.ActivityLifecycleCallbacks {
  ...
  private static boolean isShowingAd = false;

  /** Shows the ad if one isn't already showing. */
  public void showAdIfAvailable() {
    // Only show ad if there is not already an app open ad currently showing
    // and an ad is available.
    if (!isShowingAd && isAdAvailable()) {
      Log.d(LOG_TAG, "Will show ad.");

      FullScreenContentCallback fullScreenContentCallback =
          new FullScreenContentCallback() {
            @Override
            public void onAdDismissedFullScreenContent() {
              // Set the reference to null so isAdAvailable() returns false.
              AppOpenManager.this.appOpenAd = null;
              isShowingAd = false;
              fetchAd();
            }

            @Override
            public void onAdFailedToShowFullScreenContent(AdError adError) {}

            @Override
            public void onAdShowedFullScreenContent() {
              isShowingAd = true;
            }
          };

      appOpenAd.show(currentActivity, fullScreenContentCallback);

    } else {
      Log.d(LOG_TAG, "Can not show ad.");
      fetchAd();
    }
  }
  ...
}

이 메서드는 광고를 표시하며, 이때 광고가 표시되거나 광고 표시가 실패하거나 광고가 닫히는 등의 이벤트를 처리하기 위해 FullScreenContentCallback 익명 클래스를 전달합니다. 사용자가 앱 오프닝 광고를 클릭하여 앱을 떠났다가 돌아올 경우 이 메서드는 해당 사용자에게 다른 앱 오프닝 광고가 표시되지 않도록 합니다.

앱 포그라운드 이벤트 수신

Gradle 파일에 라이브러리 추가

앱 포그라운드 이벤트에 대한 알림을 받으려면 LifecycleObserver를 등록해야 합니다. 먼저 LifecycleObserver 라이브러리를 포함하도록 애플리케이션 수준의 build.gradle 파일을 수정합니다.

apply plugin: 'com.android.application'

android {
   compileSdkVersion 28
   buildToolsVersion "29.0.0"
   defaultConfig {
       applicationId "com.google.android.gms.example.appopendemo"
       minSdkVersion 18
       targetSdkVersion 28
       versionCode 1
       versionName "1.0"
       testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
       release {
           minifyEnabled false
           proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       }
   }
}

dependencies {
   implementation 'androidx.appcompat:appcompat:1.0.2'
   implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

   implementation 'com.google.android.gms:play-services-ads:19.4.0'

   def lifecycle_version = "2.0.0"
   implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
   implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
   annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
}

LifecycleObserver 인터페이스 구현

LifecycleObserver 인터페이스를 구현하여 AppOpenManager 클래스에서 포그라운드 이벤트를 수신할 수 있습니다. 다음을 추가하여 클래스를 수정합니다.

package com.google.android.gms.example.appopendemo;

import static androidx.lifecycle.Lifecycle.Event.ON_START;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.appopen.AppOpenAd;
import java.util.Date;

/** Prefetches App Open Ads and handles lifecycle detection. */
public class AppOpenManager implements LifecycleObserver, Application.ActivityLifecycleCallbacks {
  ...
  /** Constructor */
  public AppOpenManager(MyApplication myApplication) {
    this.myApplication = myApplication;
    this.myApplication.registerActivityLifecycleCallbacks(this);
    ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
  }

  /** LifecycleObserver methods */
  @OnLifecycleEvent(ON_START)
  public void onStart() {
    showAdIfAvailable();
    Log.d(LOG_TAG, "onStart");
  }
  ...
}

LifecycleObserver를 등록하면 앱 실행 및 포그라운드 이벤트가 앱에 전달되며 적절한 시점에 광고를 표시할 수 있습니다.

광고 만료 고려

만료된 광고가 표시되지 않도록 하려면 AppOpenManager에 메서드를 추가해 광고 참조가 로드된 후 경과한 시간을 확인합니다. 그런 다음 이 메서드를 사용해 광고가 여전히 유효한지 확인합니다. 다음과 같이 AppOpenManager 메서드를 업데이트합니다.

/** Prefetches App Open Ads and handles lifecycle detection. */
public class AppOpenManager implements LifecycleObserver, Application.ActivityLifecycleCallbacks {
  ...
  private long loadTime = 0;

  /** Request an ad */
  public void fetchAd() {
    // Have unused ad, no need to fetch another.
    if (isAdAvailable()) {
      return;
    }

    loadCallback =
        new AppOpenAd.AppOpenAdLoadCallback() {
          /**
           * Called when an app open ad has loaded.
           *
           * @param ad the loaded app open ad.
           */
          @Override
          public void onAppOpenAdLoaded(AppOpenAd ad) {
            AppOpenManager.this.appOpenAd = ad;
            AppOpenManager.this.loadTime = (new Date()).getTime();
          }

          /**
           * Called when an app open ad has failed to load.
           *
           * @param loadAdError the error.
           */
          @Override
          public void onAppOpenAdFailedToLoad(LoadAdError loadAdError) {
            // Handle the error.
          }

        };
    AdRequest request = getAdRequest();
    AppOpenAd.load(
        myApplication, AD_UNIT_ID, request, AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT, loadCallback);
  }
  ...

  /** Utility method to check if ad was loaded more than n hours ago. */
  private boolean wasLoadTimeLessThanNHoursAgo(long numHours) {
    long dateDifference = (new Date()).getTime() - this.loadTime;
    long numMilliSecondsPerHour = 3600000;
    return (dateDifference < (numMilliSecondsPerHour * numHours));
  }

  /** Utility method that checks if ad exists and can be shown. */
  public boolean isAdAvailable() {
    return appOpenAd != null && wasLoadTimeLessThanNHoursAgo(4);
  }
}

콜드 스타트 및 로드 화면

지금까지 이 문서에서는 메모리에 일시중지된 상태로 있는 앱을 사용자가 포그라운드로 가져올 때만 앱 오프닝 광고가 게재된다고 가정했습니다. '콜드 스타트'는 앱이 실행되었지만 실행되기 전에 앱이 일시중지 상태로 메모리에 있지 않은 경우 발생합니다.

콜드 스타트의 한 예는 사용자가 앱을 처음으로 열 때입니다. 콜드 스타트 상황에는 미리 로드되어 즉시 표시할 수 있는 앱 오프닝 광고가 없습니다. 광고를 요청하고 광고를 수신하는 시점 사이에 지연 시간이 발생하면 사용자가 잠시 앱을 사용하다가 난데없이 게재되는 광고로 인해 놀라는 상황이 발생할 수 있습니다. 이는 사용자 경험에 악영향을 미치므로 피해야 합니다.

콜드 스타트가 발생할 때 앱 오프닝 광고를 사용하는 바람직한 방법은 게임 또는 앱 애셋을 로드할 때의 로드 화면을 사용하고 해당 로드 화면에서만 광고를 표시하는 것입니다. 앱에서 로드가 완료되고 사용자에게 앱의 주요 콘텐츠를 전송한 후에는 광고를 표시하지 않아야 합니다.

권장사항

앱 오프닝 광고를 사용하면 앱을 처음 실행할 때와 앱을 전환하는 동안 앱의 로드 화면으로 수익을 창출할 수 있지만, 사용자가 앱을 사용하는 데 불편하지 않도록 권장사항을 고려하는 것이 중요합니다. 이러한 권장사항은 다음과 같습니다.

  • 사용자가 앱을 몇 번 사용한 후에 처음으로 앱 오프닝 광고를 표시합니다.
  • 사용자가 앱이 로드되기를 기다리는 동안 앱 오프닝 광고를 표시합니다.
  • 앱 오프닝 광고 아래에 로드 화면이 있고 광고가 사라지기 전에 화면에서 로드가 완료된다면 onAdDismissedFullScreenContent() 메서드로 로드 화면을 닫는 것이 좋습니다.