광고 SDK 초기화가 끝나기 전에 광고를 요청하면 꺼진다: 막는 장치를 만들고도 세 번 다시 나온 이유
운영 중인 앱에서 「MobileAds.initialize must be called」 크래시를 처음 고친 뒤에도 같은 크래시가 다른 모양으로 세 번 더 나왔습니다. 초기화 완료를 기다리는 장치는 있었지만, 시작 시점에 켜지는 값, 동의만 보고 켜지는 값, 그 장치를 거치지 않는 새 광고 자리가 빈틈이었습니다. 각 경우의 원인과 23개 앱 전수 점검 결과를 정리합니다.
처음 본 크래시
9월 22일, 진드기 촬영 앱 1.1.22 의 Crashlytics 에 이런 크래시가 올라왔습니다. Play 사전 출시 보고서의 테스트 로봇이 낸 것이었습니다.
java.lang.IllegalStateException: MobileAds.initialize must be called
before using the Google Mobile Ads SDK
우리 앱들은 Google Mobile Ads Next-Gen SDK 를 씁니다. 초기화는 메인 스레드를 막지 않으려고 백그라운드 스레드에서 돌리는데, 그 초기화가 끝나기 전에 화면이 먼저 떠서 네이티브 광고를 요청한 겁니다. 이 SDK 는 초기화가 끝나지 않은 상태에서 광고를 요청하면 위 예외를 던지고, 앱이 꺼집니다.
처음 만든 장치: 끝날 때까지 줄 세우기
광고 요청을 바로 하지 않고, 초기화가 끝났을 때 한꺼번에 실행하는 작은 클래스를 만들었습니다.
public final class AdInit {
private static volatile boolean ready = false;
private static final List<Runnable> pending = new ArrayList<>();
private static final Handler main = new Handler(Looper.getMainLooper());
/** 초기화 완료 콜백에서 부른다. */
public static void markReady() {
List<Runnable> run;
synchronized (pending) {
ready = true;
run = new ArrayList<>(pending);
pending.clear();
}
for (Runnable r : run) main.post(r);
}
/** 준비됐으면 바로, 아니면 준비된 뒤에 실행. */
public static void ensureThen(Runnable r) {
synchronized (pending) {
if (!ready) { pending.add(r); return; }
}
if (Looper.myLooper() == Looper.getMainLooper()) r.run(); else main.post(r);
}
}
MobileAds.initialize 의 완료 콜백에서 markReady() 를 부르고, 배너와 네이티브 광고를 요청하는 함수의 첫 줄을 AdInit.ensureThen(...) 으로 감쌌습니다. 진드기 촬영 앱은 같은 날 1.1.23 으로 고쳤고, 9월 22일부터 25일 사이에 View 로 화면을 만드는 다른 앱들에도 같은 장치를 넣었습니다. Compose 로 만든 앱들은 「동의와 초기화가 끝났다」는 상태값을 화면에 내려 주고, 광고 컴포저블이 그 값을 보고 요청하는 구조였습니다.
여기서 끝난 줄 알았습니다.
두 번째: 「시작」에 켜지는 값
10월 11일, 반응속도 게임 앱 1.0.20 의 Crashlytics 에서 같은 예외를 확인했습니다. 결과 화면의 네이티브 광고를 요청하는 곳이었고, 10월 14일 기준 30일 동안 2건, 사용자 1명이었습니다.
결과 화면 광고 요청은 초기화 여부를 아예 보지 않았습니다. 다른 광고 요청이 확인하던 adsInitialized 도 막는 역할을 하지 못했습니다. 이 값은 초기화를 한 번만 시작하려고 둔 값이라, 초기화가 끝날 때가 아니라 초기화를 시작할 때 켜졌기 때문입니다. 이름은 「초기화됨」이지만 실제 뜻은 「초기화 시작함」이었습니다. 또 사용자가 광고 동의를 거부하면 초기화를 아예 시작하지 않는데, 결과 화면 광고는 그때도 요청을 했습니다.
진드기 촬영 앱과 같은 방식(완료 콜백에서 켜지는 값 + 대기열)으로 바꾸고, 결과 화면 광고·보상형 광고·종료 광고를 모두 그 뒤로 옮겼습니다. 동의를 거부해 초기화가 없으면 대기열의 요청은 실행되지 않고 남습니다. 이 수정은 다음 버전에 들어가 있고, 아직 출시 전입니다.
세 번째: 장치를 거치지 않는 새 광고 자리
10월 14일 오후, 일출·일몰 앱 1.0.22 에서 같은 예외가 났습니다. Android 16 기기 한 대에서 1건이었습니다.
java.lang.IllegalStateException: MobileAds.initialize must be called ...
at ...ui.components.StartAdSheetKt$StartAdSheet$3$1
이 앱에는 동의와 초기화가 끝나야 켜지는 LocalAdsConsentReady 가 있었고, 기존 네이티브 광고 컴포저블은 이 값을 보고 있었습니다. 그런데 9월 6일 광고 UI 를 개편하면서 새로 만든 시작 광고 시트와 배너 자리는 이 값을 보지 않고 바로 광고를 요청하고 있었습니다. 장치는 있었는데, 새 광고 자리가 그 장치를 지나가지 않은 겁니다.
배너 쪽은 요청을 try 로 감싸 두어서 크래시는 나지 않았지만, 예외가 나면 그 실행 동안 배너 자리가 비어 있었고, 동의를 받기 전에 요청이 나갈 수 있는 구조이기도 했습니다.
두 곳 모두 같은 값을 기다리게 했습니다.
@Composable
fun StartAdSheet() {
val adsReady = LocalAdsConsentReady.current // 동의 + SDK 초기화 완료
val allowed = remember { StartAdGate.shouldShow(ctx) }
if (!allowed || !adsReady) return
// ... 여기서부터 NativeAdLoader.load
}
23개 앱 전수 점검
한 앱에서 다시 나왔으니 다른 앱에도 있을 수 있다고 보고, 운영 중인 23개 앱의 소스와 Crashlytics 30일 기록을 모두 확인했습니다.
- 고칠 것 2개 — 일출·일몰 앱(위 내용), 그리고 AdMob 수익 대시보드 앱. 대시보드 앱은 Crashlytics 에 0건이었지만, 광고를 켜는 값이 동의만 끝나면 켜지고 있었습니다. 초기화는
Application에서 백그라운드로 시작만 하고 끝을 알리는 신호가 없어서, 동의가 초기화보다 먼저 끝나면 시작 광고와 종료 광고 선로드가 초기화보다 앞설 수 있었습니다. 진드기 촬영 앱과 같은AdInit.ensureThen을 넣고, 광고를 켜는 값·종료 광고 선로드·보상형 광고를 모두 그 완료 콜백 뒤로 옮겼습니다. - 이미 고친 것 1개 — 반응속도 게임 앱(두 번째 경우, 다음 버전에 반영됨).
- 문제 없음 20개 — View 앱 15개는 요청 함수 첫 줄에 장치가 있었고, Compose 앱 5개는 광고 컴포저블이 완료 상태값을 보고 있었습니다.
점검 중에 한 번 틀렸습니다. 처음에는 검색한 이름이 빠져서 Compose 앱 5개도 같은 문제가 있다고 봤는데, 소스를 다시 열어 보니 다른 이름으로 같은 일을 하고 있어서 정정했습니다. 이름으로 찾는 점검은 이름이 하나라도 다르면 틀립니다.
두 앱은 갤럭시 노트10+ 에서 디버그 빌드와 R8 을 켠 출시 빌드 둘 다로 시작 광고가 뜨고 크래시가 없는 것을 확인한 뒤, 10월 14일 일출·일몰 앱 1.0.23, 대시보드 앱 1.0.54 로 내보냈습니다.
정리
- Next-Gen SDK 에서는 광고 요청이 초기화 완료 뒤에 나가야 합니다. 초기화를 백그라운드에서 돌리면 화면이 더 빨리 뜨는 경우가 실제로 생깁니다.
- 「준비됨」 값은 완료 콜백에서 켭니다. 초기화를 시작할 때 켜면 이름만 맞고 막지는 못합니다.
- 동의를 거부해 초기화를 안 하는 경로도 같이 봅니다. 그 경로에서는 요청이 아예 나가지 않아야 합니다.
- 장치를 광고 자리마다 붙이면, 새 광고 자리를 만들 때 빠지기 쉽습니다. View 앱처럼 요청 함수 자체가 기다리게 하면 새 자리도 저절로 그 장치를 지나갑니다.
- 이번 크래시는 30일에 1건이었습니다. 드물게 나는 순서 문제라 한 건이라도 보이면 같은 구조를 가진 앱을 모두 열어 보는 편이 빠릅니다.
이 글의 일출·일몰 앱은 선글로우 - 골든아워·일출일몰·달(Google Play) 입니다.