[Android] AdMob 배너 광고 레이아웃 깨짐 방지 및 가변 크기(Adaptive Banner) 적용 방법

앱을 운영하다 보면 AdMob 배너 광고를 붙여야 할 때가 많습니다. 저의 경우도 하단에 배너를 달아두고 운영 중인데, 최근 나오는 Android 기기들은 화면 크기나 비율이 워낙 제각각이라(폴더블, 태블릿, 가로 모드 등) 고정 크기 배너만 고집하면 레이아웃이 붕 뜨거나 어색하게 깨지는 문제가 발생하더군요.

자꾸 까먹기도 하고 실무에서 매번 찾아보기 귀찮아서, 안정적으로 배너 광고를 올리는 레이아웃 구조와 가변 배너(Anchored Adaptive Banner) 적용 방법을 정리해 둡니다.

1. 배너 광고 적용 시 자주 겪는 문제와 원인

단순히 "화면 아래에 View 하나 얹으면 되겠지" 하고 접근하면 운영하다가 꼭 아래와 같은 안 좋은 상황을 마주하게 됩니다.

  • 레이아웃 튐 현상: 광고가 로딩되기 전에는 높이가 0이었다가, 광고가 로드되는 순간 갑자기 화면이 툭 밀려 내려가면서 사용자가 버튼을 잘못 누르는 오인 클릭이 발생합니다.

  • 화면 크기 미대응: XML에 320x50 같은 고정 크기만 넣어두면 태블릿이나 가로 모드에서 양옆이 훤히 비어 보입니다.

  • 메모리 누수: Fragment나 Activity가 종료될 때 리소스를 제대로 해제하지 않으면 메모리를 계속 잡아먹습니다.

  • 정책 위반 위험: UMP(개인정보 동의) 흐름이 끝나기도 전에 광고를 요청하거나, 개발 중에 실제 광고 ID를 그대로 박아서 테스트하면 계정이 정지될 수 있습니다.

따라서 배너는 광고 컨테이너 높이, 화면 회전, 라이프사이클 관리를 처음부터 세트로 묶어서 설계해야 안전합니다.

2. 해결 방법: XML 레이아웃 구조 잡기

광고를 콘텐츠 위에 겹쳐 올리면 100% 버튼 오인 클릭으로 구글 경고를 받거나 사용자 원성을 삽니다. 아예 콘텐츠 영역과 광고 영역을 아래처럼 확실하게 분리해 주는 것이 좋습니다.

XML
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/contentContainer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/adContainer"
        app:layout_constraintStart_of="parent"
        app:layout_end_of="parent" />

    <FrameLayout
        android:id="@+id/adContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_of="parent"
        app:layout_end_of="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Tip: 광고 컨테이너에 android:minHeight="50dp" 정도로 최소 높이를 잡아두면, 광고가 늦게 뜨더라도 밑에 공간이 미리 확보되어 있어서 레이아웃이 위아래로 출렁거리는 현상을 막을 수 있습니다.

3. Kotlin 구현 코드 (Adaptive Banner 적용 및 해제)

공식 문서에서도 일반적인 상/하단 배너에는 화면 폭에 맞춰 높이가 결정되는 Anchored adaptive banner 방식을 권장합니다. 현재 디바이스의 가로 폭(dp)을 구해서 동적으로 광고 크기를 요청하는 컨트롤러 클래스 예시입니다.

Kotlin
class BannerAdController(
    private val activity: Activity,
    private val adContainer: FrameLayout,
    private val adUnitId: String,
) {
    private var adView: AdView? = null

    fun load() {
        // 현재 컨테이너 폭이나 디바이스 폭을 기준으로 dp 단위 가로 크기 계산
        val widthPx = adContainer.width.takeIf { it > 0 }
            ?: activity.resources.displayMetrics.widthPixels
        val density = activity.resources.displayMetrics.density
        val adWidth = (widthPx / density).toInt()

        val bannerView = AdView(activity).apply {
            // 현재 방향과 가로 폭에 맞는 가변 배너 사이즈 설정
            setAdSize(
                AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
                    activity,
                    adWidth,
                ),
            )
            this.adUnitId = adUnitId
            adListener = object : AdListener() {
                override fun onAdFailedToLoad(error: LoadAdError) {
                    // 실무 운영 시 에러 코드 정도만 로그를 남기고, 광고 ID는 로그에 찍지 마세요.
                }
            }
        }

        // 기존 뷰 정리 후 교체
        adContainer.removeAllViews()
        adContainer.addView(bannerView)
        adView = bannerView

        // 광고 로드
        bannerView.loadAd(AdRequest.Builder().build())
    }

    // Activity나 Fragment의 onDestroy에서 반드시 호출해줘야 메모리 누수가 안 납니다.
    fun destroy() {
        adContainer.removeAllViews()
        adView?.destroy()
        adView = null
    }
}

배너 형식 선택 가이드

운영 중인 화면의 성격에 따라 배너 타입을 맞춰서 써야 뷰가 안 깨집니다.

배너 종류설명추천 상황
Anchored adaptive화면 폭에 맞춰 높이가 자동 결정되는 고정 위치 배너일반적인 상단 / 하단 고정 레이아웃
Inline adaptive스크롤 영역 안에 들어가는 가변 높이 배너피드 목록, 기사 본문 중간 삽입
Fixed banner320x50 등 딱 정해진 dp 크기 사용구형 UI 유지나 특수 목적 레이아웃

4. 실무 운영 시 핵심 체크리스트

코드를 잘 짜놓고도 삐끗하면 계정이 날아가거나 심사 거절을 당하기 십상입니다. 제 경험상 아래 네 가지는 무조건 배포 전에 확인하셔야 합니다.

① UMP 동의 흐름 직후 로드하기

유럽연합 기준(및 최신 정책)으로 UMP 동의 팝업 처리가 완전히 끝나고 광고 요청이 가능한 상태(ConsentStatus)가 되었을 때 배너 로드 함수를 호출해야 안전합니다. 동의를 안 받았거나 거부된 상태에서 무작정 로드 함수부터 돌리면 안 됩니다.

② 테스트 광고 ID 철저 분리

개발이나 테스트 기기에서 실제 운영 광고 ID를 호출해 버리면 무효 클릭 트래픽으로 계정이 정지될 수 있습니다. BuildConfig나 빌드 플래버를 나눠서 개발 빌드에는 무조건 AdMob 공식 테스트 ID를 쓰도록 격리해 두세요.

  • Android 배너 테스트 광고 단위 ID: ca-app-pub-3940256099942544/6300978111

  • 주의: 실제 광고 ID나 테스트 디바이스 해시값은 GitHub 같은 공개 저장소에 소스코드로 커밋되지 않도록 마스킹 처리가 필수입니다.

③ 주요 조작 버튼과의 안전거리 확보

하단 내비게이션 바 바로 위에 배너를 바짝 붙여놓거나, '다음 단계' 버튼 바로 밑에 배너를 두면 오인 클릭 유도로 제재를 받습니다. 마진(Margin)을 주거나 경계선을 명확히 긋는 편이 좋습니다.

5. 마치며 (요약)

  • XML 레이아웃에서 콘텐츠 영역과 광고 컨테이너 영역을 겹치지 않게 완전히 분리하자.

  • AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize를 써서 기기 대응을 해주자.

  • Activity/Fragment 생명주기에 맞춰 destroy() 처리를 안 하면 메모리가 질질 샌다.

  • 실제 광고 ID 관리는 소스코드에 날것으로 노출하지 말고 빌드 환경별로 분리해서 주입하자.

배너 광고의 핵심은 많이 노출해서 클릭을 유도하는 게 아니라, 사용자가 앱 서비스를 이용하는 흐름을 방해하지 않으면서 묵묵히 제자리에 붙어 있게 만드는 것입니다. 혹시 지금 고정 크기로 띄워둔 배너가 있다면 가변 배너 구조로 가볍게 변경해 보시는 걸 권장합니다.

댓글

이 블로그의 인기 게시물

안드로이드 화면 꺼짐 방지 FLAG_KEEP_SCREEN_ON 및 WakeLock 적용 방법

안드로이드 백그라운드 타이머 구현 및 Foreground Service 알림 설계 정리

관광 앱 다국어 데이터 모델 및 검색 색인 설계 (자꾸 까먹어서 정리)