2026년 5월 마감 이후에도 구글플레이가 내 플러터 앱을 거부한다면: 16KB 페이지 크기 실전 대응 가이드

“분명히 작년에 대응 끝냈는데 왜 또 거부되지?” 최근 몇 주 사이 플러터 개발자 커뮤니티와 이슈 트래커에서 반복해서 올라오는 질문입니다. 구글플레이의 16KB 메모리 페이지 크기 요구사항은 원래 2025년 11월 1일 시행이었고, 개별 신청을 통해 최대 2026년 5월 31일까지 유예를 받을 수 있었습니다. 오늘이 2026년 7월 24일이니, 이 유예 기간마저 이미 끝난 지 두 달 가까이 됐습니다. 그런데도 여전히 많은 앱이 업데이트를 올리다가 거부당합니다. 이유는 대부분 하나입니다. 앱 자체 코드는 대응했지만, 의존하는 서드파티 네이티브 플러그인이 아직 16KB로 정렬되지 않았기 때문입니다.
이 글은 공지성 요약이 아니라, 지금 이 문제로 실제로 막혀 있는 사람을 위한 실전 대응 가이드입니다.
핵심 요약
- 구글플레이는 Android 15(API 35) 이상을 타깃하고 네이티브 코드(.so)를 포함한 앱의 새 앱/업데이트에 16KB 페이지 정렬을 요구합니다. 순수 Dart 코드만 쓰는 앱은 영향이 없습니다.
- 원 마감은 2025년 11월 1일, 개별 유예 신청 시 최대 2026년 5월 31일까지 연장 가능했습니다. 이 시점 이후로는 유예 없이 즉시 적용됩니다.
- Flutter 엔진(
libflutter.so,libapp.so) 자체는 Flutter 3.32.8 이상에서 16KB를 지원합니다. 문제는 대부분 서드파티 플러그인이 번들로 갖고 있는.so파일입니다.zipalign,llvm-objdump, Play Console의 앱 번들 탐색기로 사전에 직접 검증할 수 있습니다.- CI에 정렬 검증 스텝을 넣지 않으면 이 문제는 반드시 재발합니다.
16KB 페이지 크기 요구사항, 왜 생겼고 누구에게 적용되나
전통적으로 Android는 메모리를 4KB 단위(페이지)로 관리해 왔습니다. Android 15부터 AOSP는 16KB 페이지 크기를 지원하기 시작했고, 이후 출시되는 고사양 기기(RAM이 큰 기종)는 기본값으로 16KB 페이지를 쓰는 방향으로 이동하고 있습니다. Google이 공식 문서(developer.android.com/guide/practices/page-sizes)에서 밝힌 자체 테스트 기준 성능 개선폭은 다음과 같습니다. (단, Google 스스로도 “초기 테스트 결과이며 실제 기기에서는 다를 수 있다”고 명시하고 있습니다.)
- 메모리 압박 상황에서 앱 실행 시간 평균 3.16% 단축, 일부 앱은 최대 30%까지 개선
- 앱 실행 시 전력 소모 평균 4.56% 감소
- 카메라 핫스타트 평균 4.48%, 콜드스타트 평균 6.60% 단축
- 시스템 부팅 시간 평균 8% (약 950ms) 단축
이 수치 때문에 구글은 2025년 11월 1일부터 Android 15(API 35) 이상을 타깃하는 신규 앱과 기존 앱 업데이트는 64비트 기기에서 16KB 페이지 크기를 지원해야 한다고 못박았습니다. 이후 다수의 개발자 커뮤니티 스레드(Google Play 개발자 커뮤니티의 “Clarification on 16 KB page size extension” 등)에서 확인되듯, Play Console에서 개별적으로 유예를 신청하면 2026년 5월 31일까지 한시적으로 미룰 수 있었습니다. 이 글을 쓰는 지금(2026년 7월)은 그 유예마저 끝난 시점이라, 대응하지 않은 앱은 업데이트 업로드 단계에서 곧바로 막힙니다.
중요한 건 네이티브 코드가 없는 순수 Dart/Flutter 앱은 아무 조치도 필요 없다는 점입니다. Google 공식 블로그도 “네이티브 코드가 없는 앱은 변경이 필요 없고 이미 호환된다”고 명시합니다. 문제는 카메라, 애니메이션, DB, 지도, 결제 SDK 등을 위해 .so 파일을 포함한 네이티브 플러그인을 쓰는 절대다수의 실무 플러터 앱입니다.
내 앱이 영향받는지 확인하는 법: 3단계 진단
1단계 — 네이티브 플러그인 목록부터 뽑는다
pubspec.yaml의 의존성 중 네이티브 코드를 포함할 가능성이 있는 패키지를 먼저 추립니다. 카메라, DB(sqlite, realm, isar 네이티브 백엔드), 지도(google_maps_flutter), 애니메이션(rive, lottie 네이티브 렌더러), 이미지 처리(flutter_image_compress), 결제/보안 SDK, 크래시 리포터(sentry_flutter, firebase_crashlytics), WebRTC, ML 추론(tflite, onnxruntime) 계열이 대표적인 용의선상입니다.
2단계 — 빌드 산출물 안의 .so 파일을 직접 들여다본다
의심만으로는 부족하니 실제 빌드 결과물을 열어서 확인합니다.
# release 빌드부터 만들어 둔다
flutter build appbundle --release
# 번들 안에 포함된 .so 목록 확인
unzip -l build/app/outputs/bundle/release/app-release.aab | grep "\.so"
각 .so가 16KB로 정렬돼 있는지는 llvm-objdump로 직접 확인할 수 있습니다(macOS 기준 경로 예시):
$ANDROID_HOME/ndk/<NDK_VERSION>/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objdump \
-p path/to/librive_text.so | grep LOAD
출력에서 align 2**14(16384바이트)이면 정상이고, 2**13(8192) 이하로 나오면 4KB 정렬이라 문제가 됩니다. AAB 전체를 한 번에 확인하려면 bundletool도 유용합니다.
bundletool dump config --bundle=app-release.aab | grep alignment
# PAGE_ALIGNMENT_16K 이면 정상, PAGE_ALIGNMENT_4K 이면 미대응
APK 단위로는 zipalign의 검증 모드를 씁니다.
$ANDROID_HOME/build-tools/35.0.0/zipalign -v -c -P 16 4 app-release.apk
Android Studio를 쓴다면 File > Profile or Debug APK(또는 Build > Analyze APK)로 열어 lib/arm64-v8a 폴더의 Alignment 컬럼을 보는 게 가장 빠릅니다. 경고 아이콘이 뜬 파일이 바로 범인입니다.
3단계 — Play Console 업로드 전 사전 검증
Play Console의 **앱 번들 탐색기(App Bundle Explorer)**는 AAB를 업로드하면 16KB 미정렬 라이브러리를 자동으로 표시해 줍니다. 실제 거부 사례에서 반복적으로 나오는 메시지 형태는 다음과 같습니다.
- “App bundle contains native libraries that aren’t aligned to 16 KB page boundaries”
- “librive_text.so — 4 KB LOAD section alignment, but 16 KB is required for 16 KB devices”
메시지에 파일명이 정확히 찍혀 나오므로, 그 .so가 어느 플러그인에서 왔는지만 역추적하면 됩니다.
Flutter / NDK / AGP 버전 호환 매트릭스
| 구성 요소 | 최소 요구 | 권장 | 비고 |
|---|---|---|---|
| Flutter SDK | 3.32.8+ | 최신 stable(3.35 이상) | 3.32.8부터 엔진 자체가 16KB 대응. flutter doctor -v로 현재 버전 확인 |
| Android NDK | r27+ | r28 계열 | r28부터 기본값으로 16KB 정렬 컴파일. r27은 링커 플래그를 수동 추가해야 함 |
| AGP (Android Gradle Plugin) | 8.5.1+ | 최신 8.7.x 이상 | 8.3~8.5는 16KB 정렬은 되지만 bundletool이 APK를 자동 zipalign하지 않음 |
| Gradle | 8.9+ | AGP 버전에 맞는 최신판 | AGP 8.5.1과의 호환 조합 기준 |
| Android SDK Build-Tools | 35.0.0+ | 최신 | zipalign -P 16 옵션이 이 버전부터 지원됨 |
| compileSdk / targetSdk | 35 | 35 이상 | Android 15 API 레벨 |
한 가지 함정이 있습니다. 2026년 7월 기준 Flutter의 flutter_tools 그래들 플러그인은 기본 NDK 버전으로 27.0.12077973을 지정합니다(flutter/flutter 이슈 #175022에서 확인된 내용). r27은 요구사항을 만족하는 최소선이긴 하지만, r27은 기본적으로 16KB 정렬 컴파일을 하지 않으므로 안전하게 가려면 직접 링커 플래그를 추가하거나 NDK를 r28 계열로 올려야 합니다. android/app/build.gradle(또는 .kts)에서 이렇게 지정합니다.
android {
ndkVersion "28.2.13676358" // 다운로드 페이지에서 최신 r28.x 확인 후 대체
defaultConfig {
externalNativeBuild {
cmake {
// r27을 그대로 써야 한다면 이 플래그를 추가
arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
}
}
AGP가 8.0 이하인 레거시 프로젝트라면 gradle.properties에 다음 한 줄도 필요합니다.
android.bundle.enableUncompressedNativeLibs=false
또 하나 실전에서 자주 보고되는 버그가 있습니다. Flutter 3.32.8/3.35.1과 특정 AGP 조합에서 minSdkVersion을 23 이상으로 올리면 새로 생성한 프로젝트에서도 앱 번들 탐색기가 16KB 경고를 띄우는 현상입니다(flutter/flutter 이슈 #173949). minSdkVersion을 21 이하로 내리면 경고가 사라지지만 이는 근본 해결이 아니라 회피이므로, Flutter/AGP를 최신 안정 버전으로 맞추는 쪽을 우선하시길 권합니다.
미대응 서드파티 플러그인을 만났을 때: 실제 사례와 대응 전략
가장 골치 아픈 상황은 내 코드는 다 고쳤는데 의존하는 플러그인의 벤더가 아직 .so를 재빌드하지 않은 경우입니다. 실제로 확인되는 사례 두 가지를 보겠습니다.
사례 1 — Rive 애니메이션. 기존 rive 패키지(0.13.x 계열)는 librive_text.so가 4KB 정렬로 빌드되어 있어 Play Console에서 그대로 거부됩니다. rive-app/rive-flutter 저장소에는 이 요청이 이슈 #458, #488, #524, #539, #547, #553으로 거의 1년 넘게 반복해서 올라와 있습니다. 실제 해결책은 구버전 rive 패키지를 계속 붙잡고 있는 게 아니라, 후속 패키지인 rive_native로 마이그레이션하는 것입니다. rive_native는 0.0.3 버전부터 이슈 #479를 근거로 “Android: support 16 KB page sizes”를 changelog에 명시하고 있습니다.
사례 2 — SQLite. sqlite3_flutter_libs는 0.5.25 버전에서 changelog에 “Support 16KiB page sizes on Android 15”를 명시하며 해결됐습니다. 다만 현재(0.6.0+eol) 이 패키지 자체가 deprecated 상태이고, changelog는 “sqlite3 3.x부터는 sqlite3_flutter_libs가 더 이상 필요 없다”고 안내합니다. 즉 오래된 프로젝트일수록 패키지를 최신으로 올리는 것 자체가 이미 해결책인 경우가 많습니다.
이 두 사례에서 얻을 수 있는 실전 대응 순서는 다음과 같습니다.
-
먼저 최신 버전으로 업그레이드를 시도한다. 대부분의 인기 패키지는 이미 대응 버전을 냈거나 내는 중입니다. changelog에서 “16KB”, “page size”, “alignment” 키워드로 먼저 검색하세요.
-
dependency_overrides로 특정 transitive 의존성 버전을 강제한다. 최상위 패키지는 업데이트가 안 됐지만 그 안의 네이티브 바인딩 패키지만 새 버전이 나온 경우 유용합니다.dependency_overrides: rive_native: ^0.0.5 -
Gradle
exclude+ 직접 최신 AAR 명시. 벤더가 별도 Maven 아티팩트로 새.so를 배포했다면, 플러그인이 물고 오는 구버전 aar를 제외하고 새 버전을 직접 의존성으로 추가할 수 있습니다.implementation("com.example:some-native-sdk:2.3.0") { exclude group: "com.example", module: "some-native-sdk-legacy" } -
jniLibs소스셋으로 직접.so를 교체한다. 커뮤니티가 재컴파일한 16KB 정렬 바이너리를 구했거나 직접 NDK로 다시 빌드했다면, 벤더 aar보다 우선순위를 갖도록 소스셋을 추가합니다.android { sourceSets { main { jniLibs.srcDirs += ["src/main/jniLibs16k"] } } packagingOptions { pickFirst "lib/arm64-v8a/librive_text.so" } } -
그래도 안 되면 포크한다. 플러그인이 오픈소스라면 저장소를 포크해 NDK 버전만 r28로 올려 재빌드한 뒤
pubspec.yaml에서 git 경로로 참조하는 것도 현실적인 임시 방편입니다.dependencies: rive: git: url: https://github.com/<your-org>/rive-flutter.git ref: fix/16kb-alignment -
최후 수단으로 대체 라이브러리 검토. 벤더 대응이 기약 없다면 기능이 비슷한 다른 패키지로 교체하는 것도 선택지입니다(예: 오래된 애니메이션 라이브러리 대신 Lottie 계열 검토).
기기 차원의 “16KB 백컴팻 모드”(adb shell setprop bionic.linker.16kb.app_compat.enabled true)는 개발 중 테스트 편의를 위한 것이지, Play Console 심사를 통과시켜 주는 장치가 아니라는 점을 분명히 해 두어야 합니다. 이 모드는 사용자 기기 설정이지 여러분이 배포하는 빌드의 속성이 아닙니다.
CI 파이프라인에 16KB 정렬 자동 검증 추가하기
한 번 고쳤다고 끝나지 않습니다. 플러그인 버전을 다시 내리거나, 새 팀원이 정렬 안 된 SDK를 추가하면 재발합니다. CI에서 릴리스 빌드마다 자동으로 정렬을 검증해 Play Console에 업로드하기 전에 실패시키는 것이 재발 방지의 핵심입니다. GitHub Actions 예시입니다.
name: android-release-check
on:
push:
branches: [main]
jobs:
check-16kb-alignment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- name: Build App Bundle
run: flutter build appbundle --release
- name: Download bundletool
run: |
BUNDLETOOL_URL=$(curl -s https://api.github.com/repos/google/bundletool/releases/latest \
| grep "browser_download_url.*jar" | cut -d '"' -f 4)
curl -sL -o bundletool.jar "$BUNDLETOOL_URL"
- name: Extract universal APK
run: |
java -jar bundletool.jar build-apks \
--bundle=build/app/outputs/bundle/release/app-release.aab \
--output=out.apks --mode=universal
unzip -o out.apks -d out_apks
- name: Verify 16KB page alignment
run: |
ZIPALIGN=$ANDROID_HOME/build-tools/35.0.0/zipalign
$ZIPALIGN -v -c -P 16 4 out_apks/universal.apk || \
(echo "::error::16KB 페이지 정렬 실패 — .so 파일을 확인하세요" && exit 1)
이 스텝이 실패하면 어떤 .so가 문제인지 zipalign 출력에 그대로 나오므로, 그 파일명으로 어떤 플러그인이 원인인지 바로 특정할 수 있습니다. 팀 규모가 있다면 Renovate나 Dependabot으로 rive_native, sqlite3, google_maps_flutter, sentry_flutter처럼 과거에 문제가 있었던 네이티브 패키지들의 업데이트 PR을 자동으로 받아보는 것도 재발 방지에 도움이 됩니다.
마무리 체크리스트
-
pubspec.yaml의 네이티브 의존 플러그인 목록을 뽑았다 -
flutter build appbundle --release후.so정렬을zipalign -c -P 16또는llvm-objdump로 직접 확인했다 - Flutter 3.32.8 이상, NDK r28 계열, AGP 8.5.1 이상, Build-Tools 35.0.0 이상으로 맞췄다
-
minSdkVersion변경 후 앱 번들 탐색기 경고가 새로 뜨는지 재확인했다 - 미대응 플러그인은 changelog에서 “16KB/page size/alignment” 키워드로 대응 버전 여부를 확인했다
- 최신 버전이 없다면
dependency_overrides,jniLibs교체, 포크 순으로 우회 전략을 검토했다 - CI에 정렬 검증 스텝을 추가해 다음 릴리스부터 재발을 막았다
지금 시점에서 이 문제로 막혀 있다면, 대부분의 원인은 여러분의 코드가 아니라 오래된 서드파티 네이티브 의존성입니다. 위 순서대로 하나씩 짚어가면 대개 하루 안에 원인을 특정할 수 있습니다.