18 KiB
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.xml의rv_latest_finished_live_channel다음,ll_replay_live이전에 배너 컨테이너가 추가되고, 배너 최대 높이는 90dp를 넘지 않아야 한다. - 라이브 상세 bottom sheet의 참여자 목록과 크리에이터 프로필 사이에 Yandex adaptive inline banner를 추가한다.
QA:fragment_live_room_detail.xml의ll_participate_wrapper다음, 크리에이터 프로필RelativeLayout이전에 배너가 배치되고, bottom sheet 해제 시 배너 리소스가 정리되어야 한다. - 콘텐츠 상세에서 무료 콘텐츠 재생 또는 미리듣기 시작 시 Yandex interstitial 광고를 추가한다.
QA:AudioContentDetailActivity.setupPlayArea()의 실제 재생 시작 클릭 경로에서만 광고를 1회 시도하고, 광고 실패 여부와 무관하게 기존 재생 흐름이 유지되어야 한다. - 콘텐츠 상세의 오픈예정/theme 표시 영역과 이전화/다음화 영역 사이에 Yandex adaptive inline banner를 추가한다.
QA:activity_audio_content_detail.xml의ll_previous_next_content와 theme/open 예정RelativeLayout사이에 배너가 추가되고, 최대 높이는 90dp를 넘지 않아야 한다. - 각 화면 생명주기에 맞는 광고 로드/정리 코드를 반영한다.
QA: 배너는 화면 종료 시destroy()가 호출되고, 전면 광고는 listener와 ad 참조가 화면 종료 시 정리되어야 한다. - 검증 결과를 문서 하단에 누적 기록한다.
QA: 최소 빌드, 테스트, 수동 확인 계획과 실제 실행 결과가 문서 하단에 남아 있어야 한다.
범위 메모
- 이번 요청 범위는 아래 4개 광고 지면 추가로 한정한다.
- 라이브 탭 배너 1개
- 라이브 상세 배너 1개
- 콘텐츠 상세 전면 광고 1개
- 콘텐츠 상세 배너 1개
- AD_UNIT_ID는 기존 Yandex 광고와 동일하게
app/build.gradle의debug/releasebuildConfigField로 관리한다. - 배너 광고는 모두 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.ktapp/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.ktapp/src/main/res/layout/fragment_live.xmlapp/src/main/java/kr/co/vividnext/sodalive/live/room/detail/LiveRoomDetailFragment.ktapp/src/main/res/layout/fragment_live_room_detail.xmlapp/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.ktapp/src/main/res/layout/activity_audio_content_detail.xml
- 공식 문서
https://ads.yandex.com/helpcenter/ko/dev/android/adaptive-inline-bannerhttps://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_IDYANDEX_INLINE_BANNER_LIVE_ROOM_DETAIL_AD_UNIT_IDYANDEX_INTERSTITIAL_AUDIO_CONTENT_PLAY_AD_UNIT_IDYANDEX_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.xmlapp/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.kt
- 위치:
fragment_live.xml의rv_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.xmlapp/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시작으로 이어진다.
- 계획:
LiveRoomActivity의InterstitialAdLoader/InterstitialAdLoadListener/InterstitialAdEventListener패턴을 재사용한다.- 무료 콘텐츠 재생 또는 미리듣기 클릭 시점에만 광고 노출을 시도한다.
- 재생/일시정지 버튼은 서비스 브로드캐스트 상태를 단일 기준으로 삼아, 재생 중이면 무조건
PAUSE만 보내고 광고 로직은 타지 않도록 분리한다. - 광고가 없거나 실패하면 즉시 기존
playClickAction을 계속 진행한다. - 중복 노출 방지를 위한 1회성 상태를 Activity 생명주기에 맞춰 정의한다.
onDestroy()에서 loader, listener, ad 참조를 정리한다.
5. 콘텐츠 상세 배너 추가
- 수정 대상:
app/src/main/res/layout/activity_audio_content_detail.xmlapp/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광고추가구현계획.mdapp/build.gradleapp/src/main/res/layout/fragment_live.xmlapp/src/main/java/kr/co/vividnext/sodalive/live/LiveFragment.ktapp/src/main/res/layout/fragment_live_room_detail.xmlapp/src/main/java/kr/co/vividnext/sodalive/live/room/detail/LiveRoomDetailFragment.ktapp/src/main/res/layout/activity_audio_content_detail.xmlapp/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.gradle의buildConfigField를 지면별로 추가하는 방향이 계획 문서에 반영됐다.
- 기준 패턴:
- 무엇: AD_UNIT_ID 운영 전략을 기존 Yandex 광고와 같은
- 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_channel과ll_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 - 진단 결과:
.ktLSP 서버 미설정으로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 - 추가 반영: 각
BannerAdView에android: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로 동작한다.
- 수정 파일: