TigerDocu 개발기 · 3/3

TigerDocu 개발기 ③ Google Play 에 내기까지 — 서명키부터 145개국 출시까지

9분 분량조회 –

1편에서 말했듯 이 앱의 첫 목적은 Google Play 의 프로덕션 액세스였다. 게임을 만드는 것보다 이쪽에서 배운 게 더 많다. 날짜순으로 정리한다.

날짜 사건
8월 29일 서명된 AAB 첫 빌드, 비공개 테스트 검토 제출
9월 5일 npm run release 한 줄 배포
9월 10일 GA4 계측, 실제 광고로 전환, 데이터 보안 양식 갱신
9월 16일 프로덕션 액세스 부여, 프로덕션 검토 제출
10월 2일 Google Play 게시 (대한민국)
10월 7일 AdMob 앱 인증 완료
11월 1일 영어 지원, 145개국 출시

서명키: 잃으면 그 앱은 끝이다

Play 에 올리는 AAB 는 업로드 키로 서명한다. 이 키를 잃으면 그 앱은 다시 업데이트할 수 없다. 새 앱을 새 이름으로 올리는 수밖에 없다. 그래서 키를 만들자마자 저장소 밖 세 곳에 백업했고, 저장소에는 절대 넣지 않았다.

처음 파이프라인은 GitHub Actions 였다. 태그를 달 때만 릴리스 빌드가 돌고, 빌드 뒤에 서명을 검증한다. 이 검증 에서 실수를 하나 했다. 처음 조건은 “META-INF/MANIFEST.MF 파일이 있으면 통과” 였다. 그런데 그 파일은 서명이 없어도 있다. 검사가 아무것도 검사하지 않고 있었다. 실제 서명 파일(.RSA 등)이 있는지, 그 인증서의 지문이 우리 키와 같은지를 보게 바꿨다. 통과하는 검사가 정말 무언가를 막고 있는지 는 일부러 실패시켜 봐야 안다.

9월 5일에는 로컬에서 한 줄로 끝나게 바꿨다.

npm run release   # 버전 +1 → 웹 빌드 → Capacitor 동기화 → 서명 AAB → Play 업로드

업로드는 Gradle Play Publisher 플러그인이 서비스 계정으로 한다. 두 가지를 정했다.

  • versionCode 는 버전 이름에서 계산한다. major×10000 + minor×100 + patch. 1.4.9 는 10409 다. 같은 versionCode 는 두 번 못 올리니, 스크립트가 patch 를 올리는 걸 빼먹으면 두 번째 배포부터 무조건 실패한다.
  • 서비스 계정의 프로덕션 권한은 이 앱 하나에만 준다. 계정 단위로 주면 같은 계정의 모든 앱에 프로덕션 배포 권한이 열린다.

실기기에서 먼저 확인해야 하는 버전은 --track internal 로 내부 테스트에 올린다. 내부 테스트는 검토 없이 몇 분 안에 폰에 깔린다.

비공개 테스트: 문서에 없는 함정들

8월 29일에 비공개 테스트 트랙을 만들고 첫 검토를 넣었다. 그 과정에서 걸린 것들이다.

  • 구글은 테스터에게 메일을 보내지 않는다. 이메일 목록에 넣었다고 끝이 아니다. 옵트인 링크 를 직접 전해야 하고, 그 링크로 참여를 누른 사람만 12명에 센다.
  • “검토를 위해 전송” 버튼은 서명된 AAB 가 하나라도 있어야 켜진다. 버튼이 회색인 이유를 화면은 알려 주지 않는다.
  • 설정 진행률이 10/11 에서 멈춰 있었다. 앱 카테고리 하나를 빠뜨려서였다.

테스트 기간에도 계속 업데이트했다. 1.11.4, 1.4.21.4.9 까지 열 번 남짓이다. 버그를 고치는 게 주목적이었지만, 기간 중 업데이트가 있는 편이 심사에도 낫다는 조언도 있었다.

데이터 보안 양식: 순서가 곧 정책이다

처음 계획은 “수집 안 함, 5분이면 끝” 이었다. AdMob 과 GA4 가 들어오면서 답이 바뀌었다.

항목 수집 공유 용도
앱 활동 > 앱 상호작용 예 아니요 애널리틱스
기기 또는 기타 ID 예 예 애널리틱스, 광고, 앱 기능(순위표)

헷갈렸던 건 공유 의 기준이다. 구글이 개발자를 대신해서 처리하면(Analytics) 공유가 아니다. 구글이 자기 광고 목적으로 쓰면(AdMob) 공유다.

그리고 순서. 새로 무언가를 수집하는 버전을 낼 때는 양식을 먼저 검토에 넣고, 그다음에 앱을 낸다. 반대로 하면 선언하지 않고 수집하는 구간이 생긴다. 반대로 실제보다 많이 선언하는 건 문제가 되지 않는다.

개인정보처리방침도 같은 날 고쳤다. 광고를 넣는 순간 방침의 “광고가 없습니다” 는 거짓이 된다. 부정확한 방침은 그 자체로 정책 위반이다. 방침 원본은 저장소 한 곳에만 두고, 배포 스크립트가 공개 주소에서 HTTP 200 이 오는지, 내용이 원본과 같은지까지 확인한다.

AdMob: 배너가 제스처 바 위에 떠 있었다

광고 SDK 를 넣으면서 매니페스트에 세 가지를 넣었다.

  • AdMob 앱 ID — 없으면 SDK 초기화 때 앱이 죽는다.
  • 광고 콘텐츠 등급 G — 앱 등급(3세 이상)보다 높은 광고가 뜨면 정책 위반이다.
  • AD_ID 권한 — Android 13 부터 광고 ID 를 쓰려면 선언해야 한다.

9월 10일에 테스트 광고에서 실제 광고로 바꿨는데, 실기기에서 보니 배너가 화면 아래 제스처 바보다 한참 위에 떠 있었다. 원인이 세 겹이었다.

  1. HTML 에 viewport-fit=cover 가 없어서 Capacitor 가 웹뷰 아래에 시스템 바만큼 여백을 줬다. 그런데 AdMob 플러그인도 Android 15 이상에서 같은 여백을 한 번 더 더했다.
  2. 광고 자리 아래에 40px 여백이 따로 깔려 있었다.
  3. 적응형 배너의 높이는 기기 폭에 따라 50~90dp 인데, 60 으로 박아 두었다.

셋을 다 고치고 하나를 더 넣었다. 광고를 못 불러오면(오프라인) 광고 자리를 0 으로 접는다. 빈 띠만 남아 있으면 앱이 고장 난 것처럼 보인다.

“광고 게재 제한” 과 app-ads.txt

출시 직후 AdMob 이 이 앱에 광고 게재 제한 을 걸었다. 앱 인증이 안 돼서였다. AdMob 은 Play 등록정보에 적힌 개발자 웹사이트의 루트에 있는 app-ads.txt 로 앱 주인을 확인한다. 그런데 등록정보의 웹사이트 칸이 비어 있었다. 넣었다고 기록해 두었지만 실제로는 저장되지 않았던 것이다. 다시 넣고 AdMob 이 파일을 다시 읽기까지 이틀이 걸렸다.

광고에 대해 하나 더. “테스터가 실제 광고를 보면 무효 트래픽이 된다” 는 말을 흔히 듣는데, 사실이 아니다. 실제 사용자가 광고를 보는 건 정상이다. 진짜 위험은 개발자 본인이 자기 광고를 누르는 것 이다. 같은 AdMob 계정에 다른 앱도 묶여 있으면, 계정이 정지될 때 전부 같이 멈춘다.

GA4: 무엇을 보려 했나

Play Console 은 설치 수와 유지율까지 보여 준다. 알고 싶었던 건 어디서 그만두는가 였다. 핵심 이벤트는 puzzle_abandon 이다. 완료율만 보면 “어렵다” 까지만 안다. 포기한 시점의 차수, 진행률, 걸린 시간을 같이 보내면 12차 9×9 에서 3분을 넘기고 절반쯤 놓은 상태로 나간다 까지 보인다.

개인을 식별하는 값은 보내지 않는다(setUserId 를 쓰지 않는다). 사용자 속성은 최고 차수, 진행 방식, 연속 일수, 설치한 스토어뿐이다.

GA4 에서 걸린 함정도 적어 둔다.

  • 설정 파일이 없으면 계측이 조용히 꺼진다. Capacitor 템플릿은 google-services.json 이 있을 때만 Firebase 플러그인을 붙인다. 파일이 빠져도 빌드는 성공한다.
  • 커스텀 매개변수는 GA4 에 “맞춤 정의” 로 등록해야 리포트에 나온다. 등록 전에 들어온 데이터는 소급되지 않는다. 16개를 등록했다.
  • 숫자는 측정기준이 아니라 측정항목으로 등록한다. 그래야 평균을 낼 수 있다.

프로덕션 심사: 16일

9월 16일에 프로덕션 액세스를 받았다. 같은 날 비공개 테스트에서 검증한 1.4.9 를 그대로 프로덕션으로 올려 검토에 넣었다. 그 전에 고친 것들이 있다.

  • 그래픽 이미지의 “광고 없는” 을 “숫자 없는” 으로. 광고를 넣은 뒤로 거짓이 된 문구였다. 그래픽을 만드는 스크립트에 되돌리지 말 것 이라는 주석을 박았다.
  • 스크린샷 네 장을 새로 찍었다. 예전 것에는 차수 모드도 새 호랑이도 없었다.

함정 하나. 국가/지역 설정은 트랙마다 따로다. 비공개 테스트에 대한민국을 넣어 두었어도 프로덕션 트랙은 0개국에서 시작하고, 국가를 고르기 전에는 저장이 안 된다.

검토는 16일 걸렸다. 10월 2일에 게시됐다. 12일째에도 정책 상태는 “발견된 문제 없음” 이었다. 문제가 없어도 2주를 넘길 수 있다. 그리고 기다리는 동안 변경사항을 지우거나 다시 제출하면 대기열 맨 뒤로 간다. 기다리는 게 제일 빠르다.

TigerDocu호랑이 배치 논리 퍼즐 · 무료 · Android
Google Play 에서 받기

“12명·14일” 은 앱마다 센다

프로덕션 액세스를 받은 날, 같은 계정의 다른 앱 대시보드를 열어 봤다. 참여를 선택한 테스터 0명. 테스터 요건은 계정에 한 번이 아니라 앱마다 걸려 있었다. 공식 도움말의 문장을 계정 단위로 잘못 읽은 것이다. 다른 앱 대시보드를 한 번 열어 보면 10초면 알 수 있는 일이었다. 원문을 해석하기 전에 콘솔 화면부터 본다.

같은 계정에 올려 둔 앱이 열 개가 넘었다. 앱마다 테스터를 모으는 대신 조직 계정 으로 바꾸는 길을 골랐다. 조직 계정에는 이 테스터 요건이 아예 없고, 이미 올린 앱에도 바로 적용된다. 조건이 몇 가지 있다.

  • D-U-N-S 번호 가 필요하다. 신청서 양식을 잘못 골라 두 번 반려되며 한 달 가까이 썼다. 사업자등록증명(영문)을 첨부해 D&B 지원 사이트로 다시 내자 나흘 만에 나왔다.
  • 조직 계정은 공개 전화번호와 사업장 주소 가 스토어에 공개된다. 빼는 옵션은 없다.
  • 개인사업자 조직 계정은 인앱 결제 정산이 까다롭다. 우리 앱은 전부 무료 + 광고 라 문제가 없었다.

전환 자체는 15분 남짓, 승인은 당일이었다. 10월 22일에 바꾼 뒤 다른 앱들의 프로덕션 트랙이 바로 열렸다. TigerDocu 는 그 전에 이미 정공법으로 통과했으니, 이 전환은 다음 앱들을 위한 일이었다.

영어, 그리고 145개국

11월 1일의 1.5.2 에서 영어를 넣고 출시 국가를 1개국에서 145개국으로 넓혔다.

  • 앱: 기기 언어가 한국어면 한국어, 그 밖에는 영어. 문구는 전부 { ko, en } 짝으로 두고, en 이 빠지면 빌드가 멈추게 했다.
  • 스토어 기본 언어를 en-US 로 바꿨다. 번역이 없는 언어의 사용자에게는 기본 언어 페이지가 보인다. 기본이 한국어면 터키나 태국 사용자가 한국어 설명을 본다.
  • 등록정보는 API 로, 국가는 콘솔로. 문구와 그림은 Play Developer API 로 올렸지만, 국가 설정은 API 가 읽기 전용이라 콘솔에서 골라야 했다.
  • EEA·영국·스위스는 뺐다. 이 지역에서 광고를 보이려면 AdMob 의 동의 창(UMP)이 필요한데, 아직 붙이지 않았다. 33개 지역을 빼고 145개국이다.

원스토어는 멈췄다

9월에는 원스토어에도 올렸다. 검증은 20분 만에 끝나 판매 중이 됐다. 그런데 걸리는 게 있었다. 앱 안의 “다른 게임” 화면에 Play 링크가 있으면 “타 마켓 이동 링크” 로 반려된다. 검증팀이 실제로 앱을 켜서 메뉴를 다 눌러 본다. 스토어마다 빌드를 따로 만드는 대신, 앱은 웹페이지 하나 만 링크하게 바꿔서 해결했다.

그래도 스토어 두 곳의 등록정보와 수집 항목을 따로 관리하는 부담이 컸다. 원스토어는 1.4.5 에서 멈추고 Play 에 집중하기로 했다.

남은 이야기

출시 후에 붙인 것 하나만 덧붙인다. 앱이 켜질 때 최신 버전 번호를 한 번 물어서, 새 버전이 있으면 버튼에 점을 찍는다. 그런데 문구는 “새 버전이 나왔다” 까지만 쓴다. “받을 수 있다” 고는 하지 않는다. Play 에는 심사를 통과했는지 알려 주는 API 가 없기 때문이다. 업로드한 버전이 아직 검토 중일 수 있다. 버전 비교는 문자열이 아니라 정수 versionCode 로 한다. 문자열로 비교하면 "1.4.10" < "1.4.9" 가 된다.

두 달 동안 64번 커밋했고, 새 버전을 열댓 번 올렸다. 돌아보면 가장 쓸모 있었던 습관은 두 가지였다. 추측하기 전에 잰다. 문서보다 화면을 먼저 본다. 이 연재가 같은 길을 가는 분께 조금이라도 지도가 되면 좋겠다.

궁금한 점은 아래 댓글로 남겨 주세요.

댓글