이전 회사에서 네이버 지도를 사용하다 발생하였던 이슈에 대해서 정리를 한번 하고자 합니다.
개발 상에서는 이슈가 전혀 없던 앱이 상용 배포 후 Crashlytics를 통해 MissingLibraryException를 종종 발생한다는 이슈였었죠.
발단
네이버 지도를 붙인 기능이 배포된 후 얼마 후 Crashlytics를 통해서 이상한 오류를 잡아내기 시작합니다.
해당 오류에서 가르키는 부분들은 개발 중에는 본 적이 없는 키워드 들이었습니다.
Caused by com.getkeepsafe.relinker.MissingLibraryException
Could not find 'libnavermap.so'. Looked for: [arm64-v8a, armeabi-v7a, armeabi], but only found: [].
멀쩡히 잘 넣어둔 라이브러리가 실종이라니 처음에는 황당한 이슈였습니다.
분석
어쨋든 발생하고 있는 이슈이니 분석을 해봅니다.
발견 당시 기준으로 소수의 사용자들이 반복적으로 크래쉬를 경험하고 있었습니다.
단말도 넥서스, 픽셀나 에뮬레이터로 보이는 단말명들이었죠.
그래서 첫번째 추정을 하였습니다. 특정 칩셋을 쓰는 단말들에서만 발생하는건가??
그러면서 추가 수집된 정보로 arm, x86 단말들이 섞여 있었죠.
사무실에 가지고 있는 단말과 에뮬레이터로 테스트 해보았지만 이상이 없었습니다.
애초에 네이버 지도가 arm, x86을 다 지원하고 있으므로 문제가 될 것이 없었죠.
구글플레이에 업로드하였던 aab 파일을 받아서 다시 까보았습니다.
amr, x86 so 파일들이 다소곳이 잘 들어가 있었습니다.
첫번째 추정은 정답이 아니었습니다.
두번째 추정을 해보았습니다. 누군가 앱 설치 후 APK를 백업하여 재설치를 해본건가??
저희는 당시 apk가 아닌 aab로 버전을 업로드하였습니다.
aab는 split apk를 통해 다운받는 단말에 필요한 조각들로 앱이 설치됩니다.
https://developer.android.com/guide/app-bundle/app-bundle-format
Android App Bundle 형식 | Android 개발자 | Android Developers
Android App Bundle 형식 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android App Bundle은 Google Play에 업로드하는 파일(파일 확장자는 .aab)입니다. App Bundle은 그림 1
developer.android.com
하지만 설치된 단말과 백업한 앱을 다시 설치한 단말의 칫셉이 다르다면? 혹은 split apk의 base apk만 설치된다면??
한번 설치된 앱의 APK를 추출하여 다른 단말에 설치 후 실행해봅니다.
두둥. 재현이 되기 시작합니다.
테스트
당시의 테스트 방법이었습니다.
1. 일반 1-1. 구글플레이를 통해 설치한 앱의 APK 를 추출. a. CX파일탐색기 같은 앱 사용하여 APK 추출. (구글플레이 검색) b. Android Studio의 Device File Explorer 등을 사용하여 APK 복사 1-2. AAB로 업로드된 앱을 설치 후 1번 방법으로 APK 를 추출하면 정상적으로 설치가능한 APK가 나오지 않음. 1-3. 추출한 APK 속의 base.apk 를 추출하여 단말에 설치. 1-4. 앱 실행. (이때 크래시 발생)
2. aab로 split apks 변환 후 테스트시 2-1. bundletool을 이용해 aab 파일을 split apks로 변환 a. bundletool 다운로드 : https://github.com/google/bundletool/releases b. 아래 명령어로 변환
java -jar bundletool-all-1.14.0.jar build-apks \
--bundle=app-googlePlayStore-dev-debugWithProguard.aab \
--output=output.apks \
--connected-device
2-2. output.apks 파일을 zip 파일로 확장자 변경 후 압축 해제 2-3. splits 폴더 안의 base-master.apk 을 단말에 설치 2-4. 앱 실행. (이때 크래시 발생)
3. split apks 설치 방법 (만약에 split apks를 정상적으로 설치해보고 싶다면!!) 1. adb install-multiple *.apk
해결?
원인은 확인하였지만, 당시에 이슈를 바로 해결할 수가 없었습니다.
다음과 같은 이유에서였죠.
확인 결과 MissingLibrary는 네이버 맵 SDK에서 사용하는 라이브러리에서 제공하는 것임.
네이버 맵 SDK에서 해당 부분을 사용하는 부분은 static 영역인데, static 영역이 초기화 중에 exception이 발생하여 ExceptionInInitializerError가 발생함.
Exception이 아니라 Error라 핸들링할 수가 없음.
네이버 맵 SDK 에서 수정되지 않는 이상 Exception 핸들링도 불가하였고, 일반적이지 않고 극소수의 이슈라 우선순위도 낮아 다른 업무에 조금씩 치여가고 있었습니다.
그리고 네이버 클라우드 플랫폼에 해당 현상을 문의하였고, 답변에서도 비정상적 활동이고 빈도가 높지 않을 것으로 보이니 무시하는 것을 권장한다 고 하였죠. 나름 뭉갤 수 있는 정당성이 부여되었다라고 죄책감을 조금 내려놓을 때 쯔음....
불현듯이 찾아온 컨퍼런스 영상 속에서 그 해답을 찾게 되었습니다.
[드로이드나이츠 2021] 차영호 - AppBundle 괴담 영상 속에서 제가 필요로 하던 답이 있었습니다.
https://youtu.be/EVYnTe6aXWQ?t=706
VIDEO
그리고 위 이슈를 SideLoading이라는 키워드로 부르는 것을 알게 되었고, 아래와 같은 포스팅에서도 도움을 얻게 되었습니다.
App Bundle and Sideloading: how to prevent crashes
Read here, why the new Google Android App Bundle format can cause sideloading crashes when using a native library. Learn how to fix it.
objectbox.io
덕분에 최종 수정은 아래와 같이 하였습니다.
앱 실행시 split apk 체크로 문제가 인지되면, 토스트 띄우고 앱 종료함.
split apk 체크로직은 앱이 build 타입이 release일때만 체크하도록 함.
fun Context.checkSplitApks(): Boolean {
val splitNames = packageManager.getPackageInfoCompat(packageName, 0).splitNames ?: emptyArray()
// 네이버 지도 라이브러리는 arm/x86으로 split apk 확인이 가능하네 ㅎㅎ
val armPattern = "arm"
val x86Pattern = "x86"
// split apk 추출 후 base or master apk만 설치시 dpi가 없으니 확인하자!!
val dpiPattern = "dpi"
return splitNames.any { it.contains(armPattern) || it.contains(x86Pattern) }
&& splitNames.any { it.contains(dpiPattern) }
}
추가로 해당 처리로 앱 종료 처리된 경우 Crashlytics 로그 추가하여 추이를 보기로 하였습니다.
위 수정사항으로 더이상 저런 헤괴망측한 오류는 나오지 않았습니다.
하지만 간헐적으로 종종 로그는 수집되고 있었답니다.
오늘의 괴담은 여기까지입니다. ^^