Files
sodalive-android/docs/20260424_Yandex광고추가구현계획.md

18 KiB

20260424 Yandex 광고 추가 구현 계획

작업 체크리스트

  • 대상 화면 3곳의 광고 삽입 위치와 기존 Yandex 광고 패턴을 조사한다.
    QA: LiveFragment, LiveRoomDetailFragment, AudioContentDetailActivity, MyPageFragment, LiveRoomActivity, 공식 Yandex 문서를 근거로 각 광고 형식과 삽입 위치를 설명할 수 있어야 한다.
  • AD_UNIT_ID 운영 방식을 기존 Yandex 광고와 같은 구조로 정하고, 지면별 분리 여부를 판단한다.
    QA: 기존 BuildConfig 주입 패턴을 유지하면서 어떤 지면은 반드시 분리하고 어떤 경우에만 공용이 가능한지 문서에 남아 있어야 한다.
  • app/build.gradle에 신규 광고 지면용 ad unit id를 buildType별로 추가한다.
    QA: debug/release 모두에서 라이브 탭 배너, 라이브 상세 배너, 콘텐츠 상세 전면 광고, 콘텐츠 상세 배너용 BuildConfig 값이 생성되어야 한다.
  • 라이브 탭의 최근 종료한 라이브와 라이브 다시 듣기 사이에 Yandex adaptive inline banner를 추가한다.
    QA: fragment_live.xmlrv_latest_finished_live_channel 다음, ll_replay_live 이전에 배너 컨테이너가 추가되고, 배너 최대 높이는 90dp를 넘지 않아야 한다.
  • 라이브 상세 bottom sheet의 참여자 목록과 크리에이터 프로필 사이에 Yandex adaptive inline banner를 추가한다.
    QA: fragment_live_room_detail.xmlll_participate_wrapper 다음, 크리에이터 프로필 RelativeLayout 이전에 배너가 배치되고, bottom sheet 해제 시 배너 리소스가 정리되어야 한다.
  • 콘텐츠 상세에서 무료 콘텐츠 재생 또는 미리듣기 시작 시 Yandex interstitial 광고를 추가한다.
    QA: AudioContentDetailActivity.setupPlayArea()의 실제 재생 시작 클릭 경로에서만 광고를 1회 시도하고, 광고 실패 여부와 무관하게 기존 재생 흐름이 유지되어야 한다.
  • 콘텐츠 상세의 오픈예정/theme 표시 영역과 이전화/다음화 영역 사이에 Yandex adaptive inline banner를 추가한다.
    QA: activity_audio_content_detail.xmlll_previous_next_content와 theme/open 예정 RelativeLayout 사이에 배너가 추가되고, 최대 높이는 90dp를 넘지 않아야 한다.
  • 각 화면 생명주기에 맞는 광고 로드/정리 코드를 반영한다.
    QA: 배너는 화면 종료 시 destroy()가 호출되고, 전면 광고는 listener와 ad 참조가 화면 종료 시 정리되어야 한다.
  • 검증 결과를 문서 하단에 누적 기록한다.
    QA: 최소 빌드, 테스트, 수동 확인 계획과 실제 실행 결과가 문서 하단에 남아 있어야 한다.

범위 메모

  • 이번 요청 범위는 아래 4개 광고 지면 추가로 한정한다.
    • 라이브 탭 배너 1개
    • 라이브 상세 배너 1개
    • 콘텐츠 상세 전면 광고 1개
    • 콘텐츠 상세 배너 1개
  • AD_UNIT_ID는 기존 Yandex 광고와 동일하게 app/build.gradledebug/release buildConfigField로 관리한다.
  • 배너 광고는 모두 Yandex adaptive inline banner를 기준으로 구현한다.
  • 사용자가 명시한 제약에 따라 모든 배너의 최대 높이는 90dp를 상한으로 둔다.
  • 전면 광고는 AudioContentDetailActivity에서 무료 콘텐츠 재생 또는 미리듣기 시작 시점에만 노출을 시도한다.
  • 앱 전역 Yandex SDK 의존성과 기본 초기화는 이미 app/build.gradle, SodaLiveApp.kt에 존재하므로 이번 작업에서 신규 SDK 도입이나 앱 초기화 구조 변경은 제외한다.
  • 기존 구현 패턴은 배너는 MyPageFragment, 전면 광고는 LiveRoomActivity를 우선 따른다.
  • 같은 포맷이라도 페이지 목적과 노출 맥락이 다르면 AD_UNIT_ID를 분리하는 방향을 기본값으로 둔다.
  • 실제 ad unit id 값은 아직 전달되지 않아 신규 4개 지면은 BuildConfig에 교체용 placeholder 문자열로 추가하고, 코드 구조와 주입 경로를 먼저 확정한다.

조사 근거

  • 기존 배너 구현
    • app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt
    • app/src/main/res/layout/fragment_my.xml
  • 기존 전면 광고 구현
    • app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt
  • 앱 초기화
    • app/src/main/java/kr/co/vividnext/sodalive/app/SodaLiveApp.kt
  • 대상 화면
    • app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt
    • app/src/main/res/layout/fragment_live.xml
    • app/src/main/java/kr/co/vividnext/sodalive/live/room/detail/LiveRoomDetailFragment.kt
    • app/src/main/res/layout/fragment_live_room_detail.xml
    • app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt
    • app/src/main/res/layout/activity_audio_content_detail.xml
  • 공식 문서
    • https://ads.yandex.com/helpcenter/ko/dev/android/adaptive-inline-banner
    • https://ads.yandex.com/helpcenter/ko/dev/android/interstitial

구현 계획

1. ad unit id 주입 지점 확정

  • 수정 대상: app/build.gradle
  • 계획:
    • debug/release 각각에 신규 광고 지면용 buildConfigField를 추가한다.
    • 계획 후보 키
      • YANDEX_INLINE_BANNER_LIVE_TAB_AD_UNIT_ID
      • YANDEX_INLINE_BANNER_LIVE_ROOM_DETAIL_AD_UNIT_ID
      • YANDEX_INTERSTITIAL_AUDIO_CONTENT_PLAY_AD_UNIT_ID
      • YANDEX_INLINE_BANNER_AUDIO_CONTENT_DETAIL_AD_UNIT_ID
  • 이유:
    • 기존 YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID, YANDEX_INTERSTITIAL_LIVE_ROOM_AD_UNIT_ID와 동일한 관리 방식을 유지해야 하기 때문이다.

1-1. AD_UNIT_ID 분리 전략

  • 권장안:
    • 이번 작업의 4개 지면은 모두 서로 다른 AD_UNIT_ID를 사용한다.
  • 근거:
    • 라이브 탭 배너, 라이브 상세 배너, 콘텐츠 상세 배너는 모두 같은 banner 포맷이지만 화면 맥락과 가시성, 스크롤 위치, 성과 측정 대상이 다르다.
    • 콘텐츠 상세 전면 광고는 포맷 자체가 interstitial이라 배너와는 반드시 분리해야 한다.
    • 지면별 AD_UNIT_ID를 분리하면 추후 리포트, fill rate, CTR, 운영 정책 조정, 특정 지면만 교체하는 작업이 쉬워진다.
  • 공용 ID가 가능한 경우:
    • 완전히 동일한 포맷이고,
    • 동일한 UX 목적을 가지며,
    • 운영/리포트도 합산으로 봐도 되는 경우에만 공용 사용을 검토할 수 있다.
  • 이번 요청에서의 판단:
    • 라이브 탭 배너 ↔ 라이브 상세 배너 ↔ 콘텐츠 상세 배너는 위치와 사용자 의도가 모두 달라 공용으로 묶지 않는 것이 낫다.
    • 따라서 이번 계획에서는 페이지별·지면별 분리를 기준으로 문서와 코드 반영을 진행한다.

2. 라이브 탭 배너 추가

  • 수정 대상:
    • app/src/main/res/layout/fragment_live.xml
    • app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt
  • 위치:
    • fragment_live.xmlrv_latest_finished_live_channel 다음, ll_replay_live 이전
  • 계획:
    • 스크롤 섹션 사이에 BannerAdView 또는 배너 전용 컨테이너를 추가한다.
    • LiveFragment에 기존 MyPageFragment.setupBottomInlineBanner()와 같은 크기 계산/로드 로직을 화면 구조에 맞게 추가한다.
    • maxAdHeightDp는 90으로 제한한다.
    • onDestroyView()에서 배너 destroy()를 호출한다.

3. 라이브 상세 배너 추가

  • 수정 대상:
    • app/src/main/res/layout/fragment_live_room_detail.xml
    • app/src/main/java/kr/co/vividnext/sodalive/live/room/detail/LiveRoomDetailFragment.kt
  • 위치:
    • ll_participate_wrapper 아래, 크리에이터 프로필 RelativeLayout
  • 계획:
    • bottom sheet의 세로 흐름을 유지하면서 배너 컨테이너를 삽입한다.
    • 참가자 영역이 숨겨지는 경우와 방장 여부에 따라 UI가 달라져도 광고 영역이 레이아웃을 깨지 않도록 표시 조건을 명확히 정한다.
    • 배너 크기 계산 시 실제 측정 너비와 90dp 상한을 사용한다.
    • fragment 종료 또는 dialog dismiss 시 배너 리소스를 해제한다.

4. 콘텐츠 상세 전면 광고 추가

  • 수정 대상:
    • app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt
  • 진입 경로 근거:
    • setupPlayArea()에서 binding.ivPlayOrPause.setOnClickListener(playClickAction)
    • binding.llPreview.setOnClickListener(playClickAction)
    • 실제 재생은 playClickAction 내부의 AudioContentPlayService 시작으로 이어진다.
  • 계획:
    • LiveRoomActivityInterstitialAdLoader / InterstitialAdLoadListener / InterstitialAdEventListener 패턴을 재사용한다.
    • 무료 콘텐츠 재생 또는 미리듣기 클릭 시점에만 광고 노출을 시도한다.
    • 재생/일시정지 버튼은 서비스 브로드캐스트 상태를 단일 기준으로 삼아, 재생 중이면 무조건 PAUSE만 보내고 광고 로직은 타지 않도록 분리한다.
    • 광고가 없거나 실패하면 즉시 기존 playClickAction을 계속 진행한다.
    • 중복 노출 방지를 위한 1회성 상태를 Activity 생명주기에 맞춰 정의한다.
    • onDestroy()에서 loader, listener, ad 참조를 정리한다.

5. 콘텐츠 상세 배너 추가

  • 수정 대상:
    • app/src/main/res/layout/activity_audio_content_detail.xml
    • app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt
  • 위치:
    • ll_previous_next_content 다음, tv_scheduled_to_open/tv_theme를 포함한 RelativeLayout 이전
  • 계획:
    • 현재 스크롤 흐름을 유지하면서 중간 섹션으로 배너를 삽입한다.
    • NestedScrollView 내부 측정 너비 기준으로 adaptive inline banner를 로드한다.
    • maxAdHeightDp는 90으로 제한한다.
    • Activity 종료 시 배너 destroy()를 호출한다.

예상 수정 파일

  • docs/20260424_Yandex광고추가구현계획.md
  • app/build.gradle
  • app/src/main/res/layout/fragment_live.xml
  • app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt
  • app/src/main/res/layout/fragment_live_room_detail.xml
  • app/src/main/java/kr/co/vividnext/sodalive/live/room/detail/LiveRoomDetailFragment.kt
  • app/src/main/res/layout/activity_audio_content_detail.xml
  • app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt

검증 계획

  • 공통
    • ./gradlew :app:assembleDebug
    • ./gradlew :app:testDebugUnitTest
  • 수동 확인
    • 라이브 탭 진입 후 최근 종료한 라이브와 라이브 다시 듣기 사이에 배너가 보이는지 확인한다.
    • 라이브 상세 bottom sheet를 열어 참여자 목록과 크리에이터 프로필 사이 배너 위치와 높이를 확인한다.
    • 콘텐츠 상세에서 무료 콘텐츠 재생과 미리듣기 각각에 대해 전면 광고 시도 후 기존 재생 흐름이 유지되는지 확인한다.
    • 콘텐츠 상세에서 이전화/다음화와 theme/open 예정 영역 사이 배너 위치와 높이를 확인한다.
    • 모든 배너가 90dp를 넘지 않는지 레이아웃 검사 또는 화면 캡처로 확인한다.

검증 기록

  • 2026-04-24
    • 무엇: Yandex 광고 추가 작업의 구현 계획 문서를 생성했다.
    • 왜: 저장소 규칙에 따라 구현 전에 docs 아래 계획 문서를 먼저 만들고, 그 문서를 기준으로 범위와 검증 기준을 고정해야 하기 때문이다.
    • 어떻게:
      • 생성 파일: docs/20260424_Yandex광고추가구현계획.md
      • 근거 파일: app/build.gradle, SodaLiveApp.kt, MyPageFragment.kt, LiveRoomActivity.kt, LiveFragment.kt, LiveRoomDetailFragment.kt, AudioContentDetailActivity.kt, 각 대응 XML 레이아웃
      • 근거 문서: https://ads.yandex.com/helpcenter/ko/dev/android/adaptive-inline-banner, https://ads.yandex.com/helpcenter/ko/dev/android/interstitial
      • 결과: 광고 형식, 삽입 위치, 90dp 배너 높이 제한, 예상 수정 파일, 검증 계획을 구현 전에 확정했다.
  • 2026-04-24
    • 무엇: AD_UNIT_ID 운영 전략을 기존 Yandex 광고와 같은 BuildConfig 방식으로 정리하고, 지면별 분리 여부 판단을 문서에 반영했다.
    • 왜: 구현 전에 ad unit 관리 방식을 고정해야 이후 코드 반영과 운영 기준이 흔들리지 않고, 지면별 성과 측정 단위도 명확해지기 때문이다.
    • 어떻게:
      • 기준 패턴: MyPageFragment의 inline banner id, LiveRoomActivity의 interstitial id
      • 판단 결과: 이번 작업의 4개 지면은 화면 맥락과 포맷이 달라 모두 별도 AD_UNIT_ID를 사용하는 방향으로 계획을 확정했다.
      • 결과: app/build.gradlebuildConfigField를 지면별로 추가하는 방향이 계획 문서에 반영됐다.
  • 2026-04-24
    • 무엇: 계획 문서 기준으로 라이브 탭, 라이브 상세, 콘텐츠 상세의 Yandex 광고 코드를 실제 반영했다.
    • 왜: 사용자 요청대로 3개 화면의 지정 위치에 배너/전면 광고를 추가하고, 기존 저장소의 Yandex 광고 패턴을 동일하게 확장해야 했기 때문이다.
    • 어떻게:
      • 수정 파일: app/build.gradle, app/src/main/res/layout/fragment_live.xml, app/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt, app/src/main/res/layout/fragment_live_room_detail.xml, app/src/main/java/kr/co/vividnext/sodalive/live/room/detail/LiveRoomDetailFragment.kt, app/src/main/res/layout/activity_audio_content_detail.xml, app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt
      • BuildConfig 반영: 신규 4개 지면용 YANDEX_* ad unit id 필드를 debug/release에 각각 추가했다.
      • 라이브 탭: rv_latest_finished_live_channelll_replay_live 사이에 BannerAdView를 추가하고, LiveFragment에서 90dp 상한의 adaptive inline banner를 로드하도록 구현했다.
      • 라이브 상세: ll_participate_wrapper와 크리에이터 프로필 블록 사이에 BannerAdView를 추가하고, LiveRoomDetailFragment에서 90dp 상한의 adaptive inline banner를 로드하도록 구현했다.
      • 콘텐츠 상세: ll_previous_next_content와 theme/open 예정 블록 사이에 BannerAdView를 추가하고, AudioContentDetailActivity에서 90dp 상한의 adaptive inline banner를 로드하도록 구현했다.
      • 콘텐츠 상세 전면 광고: 무료 재생 또는 미리듣기 클릭 경로만 interstitial로 감싸고, 광고가 없거나 실패하면 기존 재생 액션을 즉시 이어가도록 구현했다.
      • 정리 코드: LiveFragment.onDestroyView(), LiveRoomDetailFragment.onDestroyView(), AudioContentDetailActivity.onDestroy()에서 배너/전면 광고 리소스를 정리했다.
  • 2026-04-24
    • 무엇: 빌드, 테스트, 린트, 수동 확인 가능 여부를 점검했다.
    • 왜: 이번 변경은 BuildConfig, Kotlin, XML, 화면 생명주기를 함께 건드리므로 최소 컴파일·테스트와 실제 실행 가능 여부를 함께 확인해야 하기 때문이다.
    • 어떻게:
      • 진단 도구: lsp_diagnostics
      • 진단 결과: .kt LSP 서버 미설정으로 No LSP server configured for extension: .kt
      • 실행 명령: ./gradlew :app:assembleDebug
      • 실행 결과: BUILD SUCCESSFUL
      • 실행 명령: ./gradlew :app:testDebugUnitTest
      • 실행 결과: BUILD SUCCESSFUL
      • 실행 명령: ./gradlew :app:ktlintCheck
      • 실행 결과: 실패
      • 린트 실패 원인: app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt:1:1 Package name must not contain underscore
      • 린트 판단: 현재 저장소의 기존 패키지 경로 audio_content 때문에 발생한 규칙 위반으로, 이번 작업에서 새로 만든 오류는 확인되지 않았다.
      • 실행 명령: adb devices
      • 실행 결과: 연결 기기 없음
      • 수동 확인 결과: ADB 연결 기기가 없어 앱 설치 및 실제 광고 노출 경로 수동 검증까지는 진행하지 못했다.
      • 비고: 신규 ad unit id는 placeholder 문자열로 넣었으므로, 실제 광고 서버 응답 검증은 실 ad unit id 교체 후 추가 확인이 필요하다.
  • 2026-04-24
    • 무엇: Oracle 검토 의견을 반영해 배너 높이 상한과 interstitial 종료 경로 안전장치를 보강한 뒤 다시 빌드와 테스트를 확인했다.
    • 왜: 코드상 maxAdHeightDp = 90만으로 끝내지 않고 XML 레벨에서도 90dp 상한을 명시해 두는 편이 안전하고, Activity 종료 중 광고 콜백이 들어와도 재생 동작이 이어지지 않도록 방어해야 하기 때문이다.
    • 어떻게:
      • 수정 파일: fragment_live.xml, fragment_live_room_detail.xml, activity_audio_content_detail.xml, AudioContentDetailActivity.kt
      • 추가 반영: 각 BannerAdViewandroid:maxHeight="90dp"를 명시했다.
      • 추가 반영: AudioContentDetailActivity.continuePendingAudioContentPlayAction()에서 isFinishing || isDestroyed일 때 재생 액션을 중단하도록 보강했다.
      • 실행 명령: ./gradlew :app:assembleDebug
      • 실행 결과: BUILD SUCCESSFUL
      • 실행 명령: ./gradlew :app:testDebugUnitTest
      • 실행 결과: BUILD SUCCESSFUL
  • 2026-04-24
    • 무엇: 콘텐츠 상세의 재생/일시정지 버튼과 전면 광고 게이팅 로직을 사용자 의도에 맞게 분리했다.
    • 왜: 기존 구현은 pause 아이콘이 보여도 클릭 리스너가 재생 경로를 유지해, 무료 콘텐츠 또는 미리듣기에서 pause 클릭 시 전면 광고가 늦게 뜨는 문제가 있었기 때문이다.
    • 어떻게:
      • 수정 파일: app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt
      • 상태 기준: AudioContentPlayService 브로드캐스트의 EXTRA_AUDIO_CONTENT_PLAYING 값을 단일 기준으로 사용하도록 정리했다.
      • 버튼 동작: 재생 중이면 PAUSE만 보내고, 재생 시작 시점에만 무료/미리듣기 대상 여부를 판단해 interstitial을 시도하도록 분리했다.
      • 기대 효과: 무료 콘텐츠 또는 유료 콘텐츠 미리듣기에서 “재생 시작 시”에만 전면 광고가 걸리고, pause 클릭은 즉시 pause로 동작한다.