MyAI(마이아이)개발 · 제품 · 운영 학습 노트

모바일 Store 배포: App Store와 Google Play

Web은 배포 즉시 모든 사용자에게 반영되지만, 모바일 앱은 Store 심사와 사용자 Update라는 두 관문을 지난다. 그래서 일정·정책·단계적 출시를 미리 설계해야 한다.

전체 흐름

flowchart LR
    B[Build / Sign] --> UP[Upload]
    UP --> T[Test Track / TestFlight]
    T --> R{Store Review}
    R -->|승인| P[단계적 출시]
    R -->|거절| F[수정 / 재제출]
    F --> UP
    P --> M[Crash / 지표 관찰]
    M -->|이상| H[중단 / Hotfix]
    M -->|정상| A[100% 출시]

계정과 비용 모델

항목 Apple Google
개발자 계정 Apple Developer Program 연간 Membership Play Console 1회 등록비
심사 도구 App Store Connect Play Console
사전 Test TestFlight Internal / Closed / Open Testing Track

금액은 지역과 시점에 따라 다르므로 가입 화면에서 확인한다.

Apple: 심사와 Test

Apple: 2026년 확인한 요구사항

시행일 요구사항
2024-05-01 목록에 있는 API(Required Reason API)를 쓰면 승인된 사유를 Privacy Manifest에 기재
2026-01-31 새 연령 등급 질문에 응답해야 Update 제출이 막히지 않음
2026-04-28 Xcode 26 이상, iOS 26 등 26 SDK로 Build해야 Upload 가능
2026-09-09 iOS · iPadOS 앱은 iOS 13 이상을 Target해야 Upload 가능
2026-09 (시행 중) 신규 앱과 Update 제출, 대체 배포용 Notarization에 연령 등급 설문의 Social Media 기능 질문 응답 필요. Social Media 기능이 있다고 답하면 Time Allowance의 Social Media 범주에 들어가고 최소 연령 등급 13+. 13세 미만에게 그 기능을 끈 앱은 Declared Age Range API로 연령대를 확인해야 한다
2027-04 (예고) iOS · iPadOS 앱은 iOS 27 · iPadOS 27 SDK 이상(Xcode 27)으로 Build해야 Upload 가능. tvOS · visionOS · watchOS도 각 27 SDK

Google Play: Track과 신규 계정 조건

Track 용도
Internal testing 최대 100명, 빠른 내부 QA
Closed testing 지정한 Tester 그룹, Track 여러 개 가능
Open testing 누구나 참여, Play Store에 Test 버전 노출
Production 일반 사용자 배포, Staged Rollout 가능

2023-11-13 및 그 이후에 만든 개인 개발자 계정은 Production 접근을 신청하기 전에 12명 이상의 Tester가 14일 연속 참여한 Closed Test를 거쳐야 한다. 조건을 채워도 자동 승인은 아니다. 조직 계정과 그 이전 개인 계정은 해당하지 않는다.

Google Play: 2026년 확인한 요구사항

시행일 요구사항
2026-08-31 신규 앱과 Update는 Android 16(API 36) 이상 Target. Wear OS · Automotive는 API 35, TV · XR은 API 34
2026-08-31 기존 앱은 API 35 이상이어야 더 높은 Android 버전의 신규 사용자에게 계속 노출
2026-11-01 위 기한의 연장 요청 시 최대 기한
2026-09-30 Brazil, Indonesia, Singapore, Thailand에서 Android Developer Verification 시작(인증 기기, 참여 Store)
2025-11-01 (시행 중) Android 15 이상을 Target하는 신규 앱과 Update는 64-bit 기기의 16 KB Page Size를 지원해야 함. Native(C/C++) 코드나 Native Library를 포함한 앱이 대상이며, Kotlin · Java만 쓰는 앱은 이미 호환
2027-02-01 연장 기간이 끝난 뒤 기존 앱 Update의 최종 기한: 16 KB Page Size를 지원하지 않는 Update는 출시 불가
android {
    defaultConfig {
        targetSdk = 36
    }
}

서명과 키 관리

모바일 앱은 서명 키가 곧 앱의 신원이다. 키를 잃거나 만료를 놓치면 코드가 멀쩡해도 Update를 낼 수 없다.

Android: Play App Signing

키 보관 용도 잃어버리면
App Signing Key Google 사용자 기기에 설치되는 APK 서명. 앱 수명 동안 바뀌지 않는다 Google이 보관하므로 개발자가 잃을 일이 없다
Upload Key 개발자 AAB를 Play Console에 올리기 전 서명 Play Console에서 Upload Key Reset 요청. 계정 소유자(Account owner)만 요청 가능. 같은 앱으로 Update를 계속 낼 수 있다

Apple: Certificate와 Provisioning Profile

CI에서 키는 어디에 두나

방식 설명
CI Secret Keystore · p12를 Base64로 Secret에 넣고 Job에서 임시 파일로 복원한다. 가장 흔하지만 교체와 감사는 사람 몫
fastlane match Certificate와 Profile을 암호화해 별도 저장소(Git 등)에 두고 팀과 CI가 공유한다. CI에서는 readonly 모드로 새 Certificate 생성을 막는다
EAS (Expo) Keystore, Distribution Certificate를 EAS 서버에 암호화 보관하고 Build 때 쓴다
Xcode Cloud Apple의 CI/CD. Cloud Signing으로 Certificate와 Profile 관리를 Apple에 맡긴다

Release 자동화 도구: fastlane(upload_to_play_store / supply, App Store · TestFlight 업로드 Action), EAS Submit, Xcode Cloud(TestFlight · App Store Connect 배포), Google Play Developer API(Edit 단위로 AAB 업로드, Track · Rollout 지정).

비싼 실패

실수 결과
직접 관리하던 Android 서명 키 분실 기존 앱에 Update 불가
Upload Key나 Keystore Password를 Repository · Build 파일에 둠 유출로 보고 Upload Key Reset. Password는 Build 파일 밖(환경 변수, 별도 파일)으로 뺀다
Distribution Certificate 만료를 출시 당일에 발견 Upload 불가로 일정 지연. 만료일을 Calendar와 경보에 등록
In-house Certificate 만료 사내 앱이 한꺼번에 실행 불가
한 사람의 PC에만 키가 있음 퇴사 · 분실이 곧 출시 중단

CI Upload 예시 (Android → Internal Track)

jobs:
  android-release:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@<commit-sha>
      - run: echo "$UPLOAD_KEYSTORE_B64" | base64 -d > "$RUNNER_TEMP/upload.jks"
        env:
          UPLOAD_KEYSTORE_B64: ${{ secrets.UPLOAD_KEYSTORE_B64 }}
      - run: ./gradlew bundleRelease
        env:
          UPLOAD_KEYSTORE_PATH: ${{ runner.temp }}/upload.jks
          UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
      - run: >-
          fastlane supply
          --aab app/build/outputs/bundle/release/app-release.aab
          --package_name com.example.app
          --track internal
          --json_key_data "$PLAY_JSON"
        env:
          PLAY_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}

iOS → TestFlight는 fastlane Lane으로 쓴다. App Store Connect API Key로 인증하고, match는 readonly로 기존 Certificate와 Profile만 내려받는다.

lane :beta do
  key = app_store_connect_api_key(key_id: ENV["ASC_KEY_ID"], issuer_id: ENV["ASC_ISSUER_ID"], key_content: ENV["ASC_KEY_P8"])
  match(type: "appstore", readonly: true, api_key: key)
  build_app(scheme: "App")
  upload_to_testflight(api_key: key)
end

Staged Rollout 운영

나쁜 예 / 좋은 예

나쁜 예: 출시 전날 첫 제출. Demo 계정 없이 로그인 필수 앱을 올려 거절, 일정 연기.
좋은 예: 2주 전 TestFlight와 Closed Test 시작, 심사용 Demo 계정 준비,
        Phased Release와 Server Feature Flag로 문제 발생 시 노출을 줄인다.

제출 전 Checklist

참고 자료