Google Mobile Ads Next-Gen SDK, 왜 만든건데?

2026. 2. 9. 01:30·Android

안녕하세요, 이번에 광고 SDK를 도입하면서 알게된 소식을 전달하고자 이번 글을 작성합니다. 도움이 되길 바랍니다👍🏻

들어가며

Google이 Mobile Ads SDK를 처음부터 다시 만들었습니다. "차세대(Next-Gen)"라는 이름을 붙인 이 SDK는 2026년 7월 정식 출시 예정이고 지금은 오픈 베타로 공개되어 있습니다.
광고 SDK를 처음부터 다시 쓰는 건 쉬운 선택이 아니라고 생각합니다.. 하위 호환성이 깨질 수 있고, 마이그레이션 비용도 만만치 않고 예상 못한 버그도 나올 수 있으니까요.
그런데도 Google이 이런 결정을 내린 데는 이유가 있었습니다. Next-Gen SDK가 뭘 해결하려고 하는지 어떻게 접근했는지 실제 개발에서는 어떤 의미를 갖는지 정리해봤습니다.

기존 SDK의 한계

1. 앱 시작 시간에 미치는 영향

모바일 광고 SDK는 좀 애매한 존재입니다. 수익화엔 필수지만 앱의 핵심 기능은 아니거든요. 그래서 SDK 초기화가 앱 실행을 느리게 만들면 UX에 바로 영향을 끼칩니다.
기존 SDK는 메인 스레드에서 초기화를 했습니다. 앱이 쓸 수 있는 상태가 되려면 SDK 초기화가 끝날 때까지 기다려야 했어요. 미디에이션 어댑터까지 쓰면 더 오래 걸렸습니다.

2. ANR의 원인

Android에서 ANR은 UX를 해치는 치명적인 문제입니다. Android는 메인 스레드가 5초 이상 블로킹되면 시스템이 앱을 강제 종료할 수 있습니다.
베타 테스터 중 한 게임 개발사는 기존 SDK에서 ANR이 많이 발생했다고 합니다. 단순히 성능 문제가 아니라 Play 스토어의 앱 품질 점수에도 직접 영향을 줘서 검색 순위까지 떨어뜨릴 수 있는 문제입니다(억울하다).

3. APK 크기 증가

모바일 앱에서 설치 파일 크기는 다운로드 전환율과 직결됩니다. Google Play Console 데이터를 보면, APK 크기가 6MB 증가할 때마다 설치 전환율이 약 1% 감소한다고 해요. 광고 SDK가 차지하는 용량이 크다면 기회 비용이 되겠죠.

Next-Gen SDK의 설계 철학

Kotlin으로의 전환

Next-Gen SDK는 Kotlin으로 완전히 재작성되었습니다. 단순히 언어만 바꾼 게 아니라, 코루틴이나 sealed class, null safety 같은 Kotlin 특성을 제대로 활용해서 더 안전하고 효율적인 코드를 만들었다는 뜻입니다.

기존 Java 기반 SDK에서는 콜백 체인이 깊어지면서 콜백 헬(장풍)이 생기고 메모리 누수 가능성도 있었습니다. Kotlin 코루틴을 쓰면 이런 비동기 작업을 훨씬 직관적이고 안전하게 처리할 수 있죠.

// 기존 방식 (Callback Hell의 가능성)
loadAd(request, object : AdLoadCallback {
    override fun onAdLoaded(ad: Ad) {
        setupAd(ad, object : SetupCallback {
            override fun onSetupComplete() {
                showAd(ad, object : ShowCallback {
                    // 계속 중첩...
                })
            }
        })
    }
})

// Kotlin Coroutines 활용 (예상되는 개선)
lifecycleScope.launch {
    val ad = loadAd(request).await()
    setupAd(ad).await()
    showAd(ad)
}

백그라운드 초기화

Next-Gen SDK에서 가장 중요한 변화 중 하나가 백그라운드 스레드에서의 초기화입니다. 공식 문서를 보면 이런 패턴을 권장하고 있어요.

class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val backgroundScope = CoroutineScope(Dispatchers.IO)
        backgroundScope.launch {
            MobileAds.initialize(
                this@SplashActivity,
                InitializationConfig.Builder(APP_ID).build()
            ) { initializationStatus ->
                // 어댑터 초기화 완료
            }
            // SDK 초기화 완료
        }
    }
}

메인 스레드를 점유하지 않으니까 UI 렌더링과 사용자 인터랙션이 SDK 초기화 영향을 안 받습니다. 앱의 다른 초기화 작업과 동시에 진행할 수도 있고, 초기화가 얼마나 오래 걸리든 ANR이 발생하지 않는 게 가장 큰것 같아요.

근데 여기서 주의할 점이 있습니다. 문서에 명시적으로 "백그라운드 스레드에서 호출해야 하며, 그렇지 않으면 ANR 오류가 발생할 수 있습니다"라고 경고하고 있어요. SDK 내부적으로 여전히 블로킹 작업이 있다는 얘기입니다.🤔

미디에이션 어댑터 초기화의 타임아웃

SDK 초기화는 30초 타임아웃이 걸려 있습니다. 모든 미디에이션 어댑터 초기화가 끝나거나, 30초가 지나면 완료 콜백이 호출됩니다.

MobileAds.initialize(context, config) { initializationStatus ->
    // 이 시점에:
    // 1. 모든 어댑터 초기화 완료, 또는
    // 2. 30초 타임아웃 도달

    // 미디에이션을 사용한다면 여기서 광고 로드 시작
    loadFirstAd()
}

"완벽한 초기화를 무한정 기다리지 않는다"는 철학을 반영한 설계입니다. 일부 어댑터 초기화가 지연되더라도 앱은 계속 진행되어야 하니까요. 실제로 30초 안에 초기화 못한 어댑터는 나중에 초기화되면서 광고 요청에 참여할 수 있습니다.

광고 프리로딩

Next-Gen SDK는 광고 프리로딩이라는 새로운 패러다임을 도입했습니다. 아직 정식 릴리스 전이지만, GitHub PR #19를 통해 작동 방식을 확인할 수 있습니다.

기존 방식은 "광고가 필요할 때 로드"입니다.

// 기존 방식
fun loadAd(context: Context) {
    if (isLoadingAd || isAdAvailable()) return

    isLoadingAd = true
    AppOpenAd.load(request) { ad ->
        appOpenAd = ad
        isLoadingAd = false
        loadTime = Date().time
    }
}

// 광고가 필요한 시점에
fun showAdIfAvailable(activity: Activity) {
    if (!isAdAvailable()) {
        // 아직 로드 안됨 - 사용자는 기다려야 함
        return
    }
    appOpenAd?.show(activity)
}

프리로딩 방식은 "미리 로드해두고 필요할 때 바로 꺼내 쓰는" 방식입니다.

// 프리로딩 방식
fun startPreloading() {
    val adRequest = AdRequest.Builder(AD_UNIT_ID).build()
    // 광고를 1개 미리 로드해둠
    val preloadConfig = PreloadConfiguration(adRequest, 1)
    AppOpenAdPreloader.start(AD_UNIT_ID, preloadConfig)
}

// 광고가 필요한 시점에
fun showAdIfAvailable(activity: Activity) {
    // 미리 로드된 광고를 즉시 가져옴
    val appOpenAd = AppOpenAdPreloader.pollAd(AD_UNIT_ID)
    if (appOpenAd == null) {
        // 아직 준비 안됨
        return
    }
    appOpenAd.show(activity)
}

두 방식의 차이를 시각화해볼까요?

프리로딩이 좋은 이유는 간단합니다. 미리 준비해두니까 사용자가 기다릴 필요가 없습니다. 광고가 미리 준비되어 있으니 표시 실패 확률도 낮아지고, 화면 전환도 끊김 없이 자연스럽게 됩니다.
PR #19 코드를 보면, 프리로딩 시작 시점이 중요하다는 걸 알 수 있어요.

class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        if (googleMobileAdsConsentManager.canRequestAds) {
            // SDK 초기화 완료 시점에 프리로딩 시작
            AppOpenAdManager.startPreloading()
        }
    }
}

즉, SDK 초기화가 완료되자마자 백그라운드에서 광고를 미리 로드하기 시작합니다. 사용자가 앱을 사용하는 동안, 광고는 이미 준비되고 있습니다.

측정 가능한 개선

Google이 공개한 수치를 보면, Next-Gen SDK의 영향이 구체적으로 드러납니다

성능 지표

배너 광고 요청 레이턴시: 최대 27% 감소

광고 요청에서 응답까지의 시간이 줄어든다는 건, 사용자가 광고 보기까지 대기 시간이 줄어든다는 얘기입니다. 사용자가 스크롤을 빠르게 하는 피드 형태 UI에서 특히 중요하겠죠.

APK 크기: 17% 감소

단순히 파일 크기만 작아진 게 아닙니다. Android의 dex 제한이나 instant app 크기 제약도 있고, 무엇보다 사용자의 다운로드 결정에 영향을 줍니다.

실제 사례

베타 프로그램에 참여한 퍼블리셔들의 결과는 더욱 인상적이에요.

  • ANR rate 33% 감소 - 앱 안정성이 직접 개선됨
  • Cold start 50% 빨라짐 - 사용자가 앱 켜고 쓰기까지 시간 단축
  • Fill rate 16% 증가 - 더 많은 광고 요청이 실제 광고로 채워짐
  • ARPDAU 91.5% 증가 - 일일 활성 사용자당 수익이 거의 2배

특히 마지막 ARPDAU 지표가 핵심이라고 생각합니다. SDK가 빨라진 것만이 아니라, 더 많은 광고 노출 기회를 확보했고, fill rate 증가로 인해 실제 수익으로 이어졌다는 뜻이니까요.

마이그레이션 시 고려사항

API 변경사항

Next-Gen SDK는 새로운 패키지 구조를 사용합니다

// 기존
import com.google.android.gms.ads.MobileAds

// Next-Gen
import com.google.android.libraries.ads.mobile.sdk.MobileAds

패키지명이 com.google.android.gms.ads에서 com.google.android.libraries.ads.mobile.sdk로 바뀌었어요. Play Services와 결합도를 낮추고, 독립적인 릴리스 사이클을 가능하게 하려는 의도로 보입니다.

초기화 패턴의 변화

가장 중요한 변화는 초기화가 반드시 백그라운드 스레드에서 이루어져야 한다는 점입니다

// 잘못된 방법 (메인 스레드)
override fun onCreate() {
    super.onCreate()
    MobileAds.initialize(this, config) { } // ANR 위험!
}

// 올바른 방법 (백그라운드 스레드)
override fun onCreate() {
    super.onCreate()
    CoroutineScope(Dispatchers.IO).launch {
        MobileAds.initialize(this@MyActivity, config) { }
    }
}

단순하게 기존 코드를 단순히 패키지명만 바꿔서는 안전하게 마이그레이션할 수 없다는 뜻입니다. 초기화 로직 전체를 검토해야 해요.🥲

광고 로딩 패턴의 변화

PR #19에서 확인할 수 있듯이, 광고 로딩 패턴도 변화하고 있습니다. 특히 App Open Ad 같은 전면 광고의 경우, 프리로딩 패턴으로 전환하는 것이 권장됩니다

// 기존 패턴 (Load on Demand)
class AppOpenAdManager {
    private var appOpenAd: AppOpenAd? = null

    fun loadAd(context: Context) {
        AppOpenAd.load(request) { ad ->
            appOpenAd = ad
            loadTime = Date().time
        }
    }

    fun showAdIfAvailable(activity: Activity) {
        if (appOpenAd == null) return // 로드 안됨
        appOpenAd?.show(activity)
    }
}

// 새로운 패턴 (Preloading)
class AppOpenAdManager {
    fun startPreloading() {
        val preloadConfig = PreloadConfiguration(adRequest, 1)
        AppOpenAdPreloader.start(AD_UNIT_ID, preloadConfig)
    }

    fun showAdIfAvailable(activity: Activity) {
        // 미리 로드된 광고를 즉시 가져옴
        val ad = AppOpenAdPreloader.pollAd(AD_UNIT_ID) ?: return
        ad.show(activity)
    }

    fun stopPreloading() {
        AppOpenAdPreloader.destroy(AD_UNIT_ID)
    }
}

프리로딩 방식의 핵심은 앱 시작할 때 startPreloading() 호출해서 백그라운드에서 광고 준비시키고, 광고 표시 시점에 pollAd()로 즉시 가져오고, 앱 종료할 때 stopPreloading()으로 리소스 정리하는 겁니다.

App Open Ad처럼 사용자가 앱 실행할 때마다 표시되는 광고에 특히 효과적이에요. 광고가 미리 준비되어 있으니 사용자는 로딩을 기다릴 필요가 없어져요.

아키텍처 관점에서의 변화

라이프사이클 관리가 간단해짐

PR #19를 보면, 광고 관리 로직이 어떻게 진화했는지 확인할 수 있습니다. 기존에는 MyApplication 클래스가 복잡한 라이프사이클 로직을 직접 관리했어요.

// 기존 방식 (복잡한 Application 클래스)
class MyApplication : Application(),
    Application.ActivityLifecycleCallbacks,
    DefaultLifecycleObserver {

    private var currentActivity: Activity? = null

    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(this)
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
    }

    override fun onStart(owner: LifecycleOwner) {
        currentActivity?.let { activity ->
            val isAppOpenFragment = // 복잡한 Fragment 체크 로직
            if (shouldShowAd || isAppOpenFragment) {
                AppOpenAdManager.showAdIfAvailable(activity, null)
            }
        }
    }

    override fun onActivityStarted(activity: Activity) {
        currentActivity = activity
    }
    // ... 더 많은 콜백들
}

프리로딩 방식으로 전환하면서 이 모든 로직이 AppOpenAdManager로 이동했습니다

// 새로운 방식 (간단한 Application 클래스)
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // 라이프사이클 관리를 AdManager에 위임
        registerActivityLifecycleCallbacks(AppOpenAdManager)
        ProcessLifecycleOwner.get().lifecycle.addObserver(AppOpenAdManager)
    }
}

// 광고 관련 로직은 AdManager에 집중
object AppOpenAdManager : DefaultLifecycleObserver,
    Application.ActivityLifecycleCallbacks {

    override fun onStart(owner: LifecycleOwner) {
        if (isColdStart && isAppOpenAdOnColdStartEnabled()) {
            showAdIfAvailable(currentActivity)
        }
    }

    // 광고 관련 모든 로직
}

어떠신가요? 구조가 깔끔해졌다고 생각되나요? Application 클래스는 앱 초기화에만 집중하고, AdManager를 독립적으로 테스트할 수 있게 되었습니다. 그리고 광고 로직을 변경해도 Application 클래스에 영향을 끼치지 않습니다.

콜백 처리의 개선

문서를 보면 "백그라운드 스레드에서 콜백 처리"라는 마이그레이션 가이드가 있습니다. SDK의 콜백이 이제 백그라운드 스레드에서 호출될 수 있다는 의미입니다.

adView.loadAd(request, object : AdLoadCallback<BannerAd> {
    override fun onAdLoaded(ad: BannerAd) {
        // 이 콜백은 백그라운드 스레드에서 호출될 수 있음
        // UI 업데이트 시 주의!
        runOnUiThread {
            // UI 작업
        }
    }
})

성능을 위한 트레이드 오프라고 생각합니다. 메인 스레드를 블로킹하지 않으려면 개발자가 스레드 관리를 더 명시적으로 해야하니까요.

광고 객체의 라이프사이클

Next-Gen SDK는 광고 객체의 라이프사이클을 더 명확히 관리합니다

class BannerFragment : Fragment() {
    private var bannerAd: BannerAd? = null

    override fun onDestroyView() {
        binding.adView.destroy() // 명시적 정리 필요
        super.onDestroyView()
    }
}

기존 SDK에서도 destroy() 호출이 권장되었지만, Next-Gen에서는 더욱 강조하고 있어요. 메모리 누수를 방지하고 불필요한 광고 새로고침을 막기 위함입니다.

릴리스 전략의 변화

Next-Gen SDK는 월간 릴리스 주기를 목표한다고 합니다. 기존의 분기별 릴리스보다 훨씬 빠른 속도인데요.

이런 빠른 릴리스 주기는 버그 수정도 빠르게 진행되고 새로운 기능도 계속해서 도입되기 때문에 자동화된 테스트나 CI/CD 파이프라인이 더욱 중요해지지 않을까 추측해봅니다.

마치며

Google이 Mobile Ads SDK를 처음부터 다시 만든건 모바일 앱 환경이 많이 바뀌었기 때문인 것 같습니다. 요즘 사용자들은 느린 앱을 절대 참지 않고 플랫폼은 앱 품질을 깐깐하게 보고 광고주들은 성과를 더 요구하니까요.😶

Next-Gen SDK의 17% 작은 APK, 27% 빠른 광고 로딩, 33% 낮은 ANR rate. 단순한 숫자가 아니라 실제로 UX가 좋아지고 수익도 늘어날 수 있습니다. 개발자한테는 특히 반갑지 않을까요?

특히 주목할 만한 건 광고 프리로딩 같은 새로운 패턴입니다. 아직 머지되지 않은 PR #19에서 확인할 수 있듯이 Google은 광고 로딩 자체의 설계를 바꾸려 하고 있습니다. "사용자가 기다리게 하지 않는다"는 건 모바일 앱의 기본 원칙이고, 이제 광고 SDK도 따라가고 있는것 같습니다.

물론 마이그레이션에는 비용이 듭니다. API 변경을 반영하고 새로운 패턴을 학습하고, 테스트하고, 모니터링해야 해요. 특히 프리로딩 같은 새로운 기능은 메모리와 네트워크 사용량 같은 새로운 고려사항을 만듭니다. 하지만 베타 테스터들의 결과를 보면 충분히 가치가 있어 보입니다.

2026년 7월 정식 출시까지는 아직 시간이 있습니다. 급하게 적용할 필요는 없지만, 준비는 미리 시작하는 건 어떨까요?

그리고 저도 GitHub의 예제 레포지토리를 자주 보려고 합니다. 아직 머지되지 않은 PR들을 통해 Google이 어떤 방향으로 SDK를 발전시키려 하는지 파악하기에 도움이 많이 되었거든요. 개인적인 욕심으로는 하루빨리 코루틴 친화적이게 되었음 합니다.😅

긴 글 읽어 주셔서 감사합니다.

 

참고 자료

  • GMA Next-Gen SDK 공식 문서
  • Google Ads Developer Blog - SDK 발표
  • GitHub 예제 코드
  • PR #19 - App Open Ad Preloading (진행 중)

'Android' 카테고리의 다른 글

Android 17 BETA, 공부 많이 된다  (0) 2026.02.24
BaseViewModel을 쓰지 않는 이유  (4) 2026.01.30
AGP 9.0 마이그레이션, 뭐가 달라졌는데요?  (1) 2026.01.21
HttpLoggingInterceptor JSON 파싱 최적화로 95% 성능 개선하기  (0) 2026.01.21
Android CI 빌드 속도 1분대로 줄여보기  (0) 2026.01.06
'Android' 카테고리의 다른 글
  • Android 17 BETA, 공부 많이 된다
  • BaseViewModel을 쓰지 않는 이유
  • AGP 9.0 마이그레이션, 뭐가 달라졌는데요?
  • HttpLoggingInterceptor JSON 파싱 최적화로 95% 성능 개선하기
아키001
아키001
Android 개발자가 되기까지.
  • 아키001
    미래 가젯 연구소
    아키001
  • 전체
    오늘
    어제
    • 분류 전체보기 (37)
      • Android (27)
        • Compose (13)
        • Jetpack (2)
      • Kotlin (2)
      • 우아한테크코스 (8)
        • 일상, 회고 (0)
        • 레벨1 (0)
        • 레벨0 (4)
        • 프리코스 (4)
  • 블로그 메뉴

    • 홈
    • 안드로이드
    • 태그
  • 링크

    • GitHub
  • 인기 글

  • 태그

    jetpack
    우테코
    레벨0
    Gradle
    Kotlin
    runcatching
    compose
    Android
    build-logic
    coroutine
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
아키001
Google Mobile Ads Next-Gen SDK, 왜 만든건데?
상단으로

티스토리툴바