[Android] APK는 되는데 AAB(Play Store)에서 .so 실행이 안 될 때
jniLibs.useLegacyPackaging = true 를 설정하지 않아 겪은 삽질기
Native library(.so)를 shell에서 실행 파일로 직접 실행하는 경우,
build.gradle.kts에 아래 설정을 반드시 추가하세요.
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
제가 개발하는 앱은 ADB shell 권한으로 별도 프로세스를 실행하는 구조입니다. 앱에 포함된 libstarter.so를 ADB shell 명령으로 직접 실행하고, 이 바이너리가 내부적으로 app_process를 호출하여 서비스 프로세스를 띄웁니다.
개발 중에는 APK 빌드(assembleRelease)로 테스트했고, 모든 기능이 정상 동작했습니다. 그런데 AAB 빌드(bundleRelease)로 Google Play Store에 업로드한 뒤, 실제 기기에서 다운로드 받아 사용하니 보조 서비스가 시작되지 않는 문제가 발생했습니다.
처음에는 R8/ProGuard 난독화 문제를 의심했습니다. 하지만 APK와 AAB 모두 동일한 R8 규칙으로 빌드되므로, R8이 원인이 아니었습니다.
APK와 AAB의 설치 방식 차이
| 구분 | APK (assembleRelease) | AAB (Play Store) |
|---|---|---|
| 설치 파일 | 단일 app-release.apk | base.apk + split_config.arm64_v8a.apk + split_config.ko.apk |
| Native 라이브러리 | 단일 APK에 포함 | split_config.{abi}.apk에 분리 |
| .so 파일 추출 | 파일시스템에 추출됨 | 추출되지 않을 수 있음 |
AAB로 빌드하면 Google Play가 기기에 맞는 Split APK으로 분할하여 배포합니다. 이때 native library는 split_config.arm64_v8a.apk 등 별도의 APK로 분리됩니다.
실제로 기기에서 확인한 결과:
단일 APK 설치 시 — lib 디렉토리
libadb.so libconscrypt_jni.so libstarter.so ...
AAB(Split APK) 설치 시 — lib 디렉토리
(비어 있음)
AGP(Android Gradle Plugin)의 기본값이 변경되면서, AAB 빌드 시 native library를 파일시스템에 추출하지 않고 APK 내에서 직접 메모리 매핑으로 로드하는 방식이 기본값이 되었습니다.
System.loadLibrary()로 로드하는 일반적인 경우에는 문제가 없지만, .so 파일을 shell에서 직접 실행해야 하는 경우에는 파일이 물리적으로 존재하지 않아 실행할 수 없습니다.
실행 흐름 비교
APK 설치 (정상):
AAB 설치 (실패):
build.gradle.kts의 android 블록에 다음을 추가합니다:
// ... 기존 설정 ...
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
useLegacyPackaging = true로 설정하면, AAB/Split APK 설치 시에도 native library를 파일시스템에 물리적으로 추출합니다.
이렇게 하면 .so 파일을 shell에서 직접 실행할 수 있습니다.
android:extractNativeLibs="true"를 AndroidManifest.xml에 설정해도, AGP가 AAB 빌드 시 이를 무시할 수 있습니다. 빌드 로그에 아래 경고가 나타난다면 useLegacyPackaging 설정이 필요합니다:
because android:extractNativeLibs is set to "true" in AndroidManifest.xml.
| 사용 방식 | useLegacyPackaging 필요? |
|---|---|
| System.loadLibrary("name")으로 로드 | 불필요 (기본값으로 동작) |
| dlopen()으로 동적 로드 | 불필요 (기본값으로 동작) |
| shell에서 .so 파일을 직접 실행 (exec/execvp) | 필수 |
| app_process의 CLASSPATH로 APK 경로 전달 | 상황에 따라 필요 |
대표적으로 아래와 같은 프로젝트에서 이 이슈가 발생할 수 있습니다:
저는 이 원인을 찾기까지 아래 과정을 거쳤습니다. 같은 실수를 하지 않으시길 바랍니다.
"Debug에서 되고 Release에서 안 된다"고 해서 무조건 R8/ProGuard를 의심하지 마세요.
APK vs AAB의 패키징 차이가 원인일 수 있습니다.
문제를 좁힐 때는 Release APK를 먼저 테스트하여 R8 문제인지, 패키징 문제인지를 구분하는 것이 중요합니다.
AGP의 기본값 변경은 대부분의 앱에는 문제가 되지 않습니다. 하지만 native 바이너리를 실행 파일로 직접 사용하는 특수한 구조에서는 치명적인 이슈가 됩니다.
이 글이 저와 비슷한 구조의 앱을 개발하시는 분들께 도움이 되었으면 합니다. 특히 Shizuku 기반 앱이나, app_process를 활용하는 프로젝트를 진행 중이라면 useLegacyPackaging = true 설정을 꼭 확인해 보세요.