만족

[Android] admob 전면 광고의 적절한 로딩 시점과 표시 시점 본문

[Android] admob 전면 광고의 적절한 로딩 시점과 표시 시점

FrontEnd/Android Satisfaction 2022. 12. 12. 02:13

이번에 전면 광고의 로딩 시점과 표시 시점을 변경하여,

그렇게 변경한 이유와 장점에 대해 소개한다.

 

기존

기존에는 스플래시 화면이 초기화될 때(onCreate)에서 로딩을 시작하고,

3초를 기다리거나 그 전에 광고가 로딩된 경우 표시했다.

 

그러나 admob의 응답 속도는 그다지 빠르지 않으며,

네트워크가 느린 환경에 있는 유저도 꽤나 많았기 때문에 노출률이 그다지 높지 않았다.

 

https://satisfactoryplace.tistory.com/133

 

[Android] Google Play - 앱, 타사 광고, 기기 기능 방해 정책 위반

갑자기 다음과 같은 메일이 날라오며 앱 업데이트가 거부되었다. 의아한 것은 해당 업데이트 내용은 버그 수정 및 편의성 강화로써 광고 관련 코드는 수정된 바가 전혀 없다는 것이다. 이의 신

satisfactoryplace.tistory.com

 

광고 가이드도 의외로 빡세기 때문에 로딩이 끝났다고 냅다 띄울수도 없어서 고민이 많았다.

 

그래서 전면 광고와 네이티브 광고(이 경우 로딩이 좀 더 빠르다)를 동시에 로딩하고

표시 시점에 전면 광고가 없을 경우에 네이티브 광고라도 다이얼로그에 띄웠다.

 

하지만 이 행위는 전면 광고의 로딩 성공 가능성을 떨어뜨리게 되어

전면 광고의 노출률을 오히려 더 떨어뜨렸다.

 

플로우를 요약하자면 이렇다.

 

1. 스플래시 화면 표시

2. 전면/네이티브 광고 로딩 시작 + 초기 필수 데이터 로딩 시작

3. 초기 필수 데이터 로딩이 완료된 후 최대 3초간 전면 광고 로딩 대기

4. 전면 광고가 로딩되지 않은 경우 네이티브 광고 표시

5. 네이티브 광고도 표시되지 않은 경우 그냥 홈 화면으로 이동

 

무조건 광고는 스플래시에서 표시까지 끝내야 한다는 강박에 사로잡혀 

어떻게 하면 광고가 로딩되는 동안 불쾌감을 최소화할 수 있을까? 에만 집중했었다.

 

무조건 스플래시에서 로딩, 표시까지 끝내야 할까?

생각해보면 아니다.

 

간단한 답이지만 몇년동안 이 생각에 갇혀 있었다.

 

Admob에서는 로딩 화면(스플래시) 또는 홈 화면에서 사용자가 인터렉션을 시작한 후 전면 광고를 표시하는 것을 강제한다.

 

그렇다면 로딩 시작을 스플래시에서 하고,

홈 화면에서 유저가 다른 창으로 이동할 때나 다른 창에서 돌아왔을 때 표시하면 어떨까?

 

좋은 방법이라고 생각한다.

 

기존에는 광고 때문에 최대 3초를 스플래시 화면에서 허비해야 했지만,

이제는 필수 데이터 로딩만 완료되면 홈으로 바로 진입할 수 있기 때문이다.

 

모든 버튼이 다른 액티비티를 여는 동작을 한다

내 앱의 홈 화면은 네이게이션 역할을 하고 있다.

 

따라서 스플래시에서 미리 로딩을 걸어 놨다가,

다른 액티비티가 열릴 때 로딩이 완료된 경우 광고를 표시한 다음 그 액티비티를 열게 변경했다.

 

수정전
수정후

 

어떤가?

 

수정 후에서는 앱의 홈에 진입하기까지 훨씬 적은 시간이 요구되어 훨씬 쾌적해졌다.

 

로딩/표시 시점 미루기

https://developers.google.com/admob/android/interstitial#expandable-1
 

전면 광고  |  Android  |  Google Developers

전면 광고 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Interstitial ads are full-screen ads that cover the interface of their host app. They're typically displayed at natural transition

developers.google.com

애드몹 문서의 예제 코드에서는 액티비티 초기화 시점에 로딩을 시작하고, 로딩이 완료되면 표시한다.

 

그러나 우리가 하고 싶은 것은 로딩은 스플래시에서, 

로딩이 완료되었다면 홈 화면에서 다른 화면으로 이동할 때(콜백을 붙일 수 있다면 굳이 이동이 아니더라도 상관없다) 표시하는 것이다.

 

따라서, 로딩과 표시 로직을 분리해 별도의 클래스를 만든다.

package kr.co.dothome.whenever.cyphersapp.ui.util;

import android.content.Context;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.InterstitialAd;

public class AdmobUtil {
  @NonNull
  private InterstitialAd mInterstitialAd;
  
  private boolean showedFullsizeAd = false;
  
  private AdmobUtil(Context context) {
    load(context);
  }
  
  private static AdmobUtil instance;
  
  public static AdmobUtil getInstance(Context context) {
    if (instance == null) {
      instance = new AdmobUtil(context);
    }
    return instance;
  }
  
  /**
   * load method is automatically called whe creating instance
   * if re-call load(), reload is running
   *
   * @param context
   */
  public void load(Context context) {
    mInterstitialAd = new InterstitialAd(context);
    mInterstitialAd.setAdUnitId("AD_UNIT_ID");
    mInterstitialAd.loadAd(new AdRequest.Builder().build());
    mInterstitialAd.setAdListener(new AdListener() {
      @Override
      public void onAdFailedToLoad(int i) {
        super.onAdFailedToLoad(i);
        //retry
        load(context);
      }
    });
    
    showedFullsizeAd = false;
  }
  
  // 광고가 로딩되었는가(표시 가능한가)?
  public boolean isLoadedFullsizeAd() {
    return mInterstitialAd.isLoaded();
  }
  
  // 로딩된 광고가 노출이 된 적이 있는가?
  public boolean isShowedFullsizeAd() {
    return showedFullsizeAd;
  }
  
  /**
   * require checking activity is top(foreground) state
   *
   * @param onClose onClose callback
   */
  public void showFullsizeAd(@Nullable Runnable onClose) {
    if (!isLoadedFullsizeAd()) {
      Log.d("AdmobUtil", "fullsize ad is not loaded but show() is called");
      return;
    }
    if(showedFullsizeAd){
      //already showed
      if (onClose != null) {
        onClose.run();
      }
      return;
    }
    
    mInterstitialAd.setAdListener(new AdListener() {
      @Override
      public void onAdOpened() {
        super.onAdOpened();
      }
      
      @Override
      public void onAdClosed() {
        super.onAdClosed();
        if (onClose != null) {
          onClose.run();
        }
      }
    });
    showedFullsizeAd = true;
    mInterstitialAd.show();
  }
  
}

AdmobUtil은 Singleton 패턴으로 작성되어,

AdmobUtil.getInstance(context)로 어디서든 활용할 수 있다.

 

이제 스플래시 액티비티에서 로딩을 시작해 준다.

 

public class SplashActivity{
	//...
    @Override
    protected void onCreate(Bundle savedInstance){
      //...
      
      // AdmobUtil 초기화
      AdmobUtil.getInstance(this);
    }
    //...
}

AdmobUtil의 생성자에서 load(context)를 호출하게 해 두었기 때문에 여기에서는 그냥 getInstance만 해줘도 된다.

 

스플래시에서 홈 화면(이하 메인 액티비티)으로 이동하고 나면,

이제 위에서 설명한 '적절한 시점'에 광고를 표시할 것이다.

 

public class MainActivity{
  //...
  
  private void checkAdShowed(Runnable callback) {
    AdmobUtil admobUtil = AdmobUtil.getInstance(this);
    
    if (!admobUtil.isShowedFullsizeAd() && admobUtil.isLoadedFullsizeAd()) {
      // loaded but not showed
      // start show
      
      Toast.makeText(this, "앱을 열고 나서 한 번 전면 광고가 표시됩니다\n닫기(Close) 버튼을 눌러 닫을 수 있습니다", Toast.LENGTH_LONG).show();
      admobUtil.showFullsizeAd(callback);
    } else {
      // not loaded or already showed
      
      if (callback != null)
        callback.run();
    }
  }
  
  //...
}

 

checkAdShowed는 광고가 로딩되었는지와 이미 표시된적이 있는지를 체크하고,

조건에 맞을 경우 표시하는 코드다.

 

이 메서드를 새로운 액티비티를 여는 메서드 전에 먼저 실행할 것이다.

 

새로운 액티비티를 연다?

 

바로 startActivity, startActivityForResult다.

 

이놈들을 override해서,

광고를 표시할 수 있는 상태면 표시한 다음 super메서드가 실행되게 한다.

 

public class MainActivity{
  //...
  
  private void checkAdShowed(Runnable callback) {
    AdmobUtil admobUtil = AdmobUtil.getInstance(this);
    
    if (!admobUtil.isShowedFullsizeAd() && admobUtil.isLoadedFullsizeAd()) {
      // loaded but not showed
      // start show
      
      Toast.makeText(this, "앱을 열고 나서 한 번 전면 광고가 표시됩니다\n닫기(Close) 버튼을 눌러 닫을 수 있습니다", Toast.LENGTH_LONG).show();
      admobUtil.showFullsizeAd(callback);
    } else {
      // not loaded or already showed
      
      if (callback != null)
        callback.run();
    }
  }
  
  @Override
  public void startActivity(Intent intent) {
    checkAdShowed(() -> {
      super.startActivity(intent);
    });
  }
  
  @Override
  public void startActivityForResult(Intent intent, int requestCode) {
    checkAdShowed(() -> {
      super.startActivityForResult(intent, requestCode);
    });
  }
  
  
  //...
}

이제 다른 액티비티를 열기 전에 전면 광고 표시여부를 판단하여

광고를 표시한 다음 액티비티를 열게 할 수 있다.

 

그런데 만약 사용자가 홈 화면에 진입하자마자 다른 액티비티로 진입하고 (아직 광고는 로드되지 않은 상태)

용무를 끝낸 후 홈 화면으로 돌아온 다음 뒤로가기를 눌러 앱을 종료하는 상황을 상상해 보자.

 

유저는 홈 화면에서 연 액티비티에서 많은 시간을 보냈지만,

홈으로 돌아온 다음에도 광고는 표시되지 않았다.

 

다른 액티비티를 열었다가 닫는 콜백 메서드로 onActivityResult가 있지만,

이것은 startActivityForResult로 액티비티를 열었을 때만 실행된다.

 

따라서 홈 화면에서 onPause(다른 액티비티가 띄워질 때 이 라이프사이클에 진입한다) 상태로 진입한 적이 있다면,

onResume에서 다시 한번 checkAdShowed를 호출해 준다.

 

public class MainActivity{
  //...
  private boolean isPauseHistory= false;
  
  private void checkAdShowed(Runnable callback) {
    AdmobUtil admobUtil = AdmobUtil.getInstance(this);
    
    if (!admobUtil.isShowedFullsizeAd() && admobUtil.isLoadedFullsizeAd()) {
      // loaded but not showed
      // start show
      
      Toast.makeText(this, "앱을 열고 나서 한 번 전면 광고가 표시됩니다\n닫기(Close) 버튼을 눌러 닫을 수 있습니다", Toast.LENGTH_LONG).show();
      admobUtil.showFullsizeAd(callback);
    } else {
      // not loaded or already showed
      
      if (callback != null)
        callback.run();
    }
  }
  
  @Override
  public void startActivity(Intent intent) {
    checkAdShowed(() -> {
      super.startActivity(intent);
    });
  }
  
  @Override
  public void startActivityForResult(Intent intent, int requestCode) {
    checkAdShowed(() -> {
      super.startActivityForResult(intent, requestCode);
    });
  }
    
  @Override
  protected void onResume() {
    super.onResume();
    
    if (isPausedHistory) {
      checkAdShowed(null);
    }
  }
  
  @Override
  protected void onPause() {
    super.onPause();
    isPausedHistory = true;
  }
  
  
  //...
}

isPauseHistory로 체크하지 않으면,

전면 광고가 빠르게 로딩되었을 때, 또는 스플래시에서 초기 데이터 셋팅이 오래 걸렸을 때

홈 화면의 onResume에서 갑자기 광고가 표시될 수도 있다.

 

이는 정책 위반이므로 onPause()에 걸린 적이 있는지 확인한 다음 표시한다.



Comments