오랫만에 데이터바인딩 설정을 잡을 일이 있어서 안드로이드 개발 문서를 살펴보던 중이었습니다.

https://developer.android.com/topic/libraries/data-binding/start?hl=ko

시작하기  |  Android 개발자  |  Android Developers

시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android 스튜디오의 데이터 결합 코드 지원을 비롯하여 개발 환경에서 데이터 결합 라이브러리를 함

developer.android.com

 

android {
        ...
        dataBinding {
            enabled = true
        }
    }

New Project로 빈 프로젝트를 만듭니다.

(최근의 Android Studio가 권장하는 Kotlin DSL (build.gradle.kts)를 선택하였습니다.)

그리고 개발 문서의 가이드대로 데이터바인딩 설정을 해봅니다.

하지만 build.gradle이 불이 붙은 채로 꺼지지 않습니다.

 

 

엇.. 뭐지? 이런 저런 것들을 건드려 보고, StackOverflow를 뒤져보고요.

구글께서 시키는 대로 하였는데, 왜 나에게 이런 시련을 주시지? 라는 믿음이 흔들리는 마음도 먹어봅니다.

불현듯 생각나서 개발 문서를 다시 확인해봅니다.

 

한국어???

설마???

 

오잉????

 

다시 영문 버전으로 바꿔봅니다.

https://developer.android.com/topic/libraries/data-binding/start?authuser=1

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

 

코드가 달라졌습니다......

 

잊고 있었습니다.

많은 선후배 동료님들의 개발문서의 한글버전을 믿지 말라는 그 말을요....

 

히스토리를 찾아보니 Android Gradle Plugin 4.0 버전부터의 차이가 있었네요.

https://developer.android.com/build/releases/past-releases/agp-4-0-0-release-notes?authuser=1#buildFeatures

 

Android 스튜디오  |  Android Developers

Android Gradle 플러그인 4.0.0 출시 노트

developer.android.com

 

기존 방식은 deprecated가 되었고, buildFeatures의 dataBinding으로 설정하는 방법으로요.

제 어리석음을 탓하며 시간낭비 삽질을 한 케이스였습니다.

최근에야 많이들 Compose를 쓰시느라 볼 일이 별로 없으시겠지만요...

혹여나 저처럼 방황하실 분들을 위해 남겨봅니다. :)

이전 회사에서 네이버 지도를 사용하다 발생하였던 이슈에 대해서 정리를 한번 하고자 합니다.

개발 상에서는 이슈가 전혀 없던 앱이 상용 배포 후 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

 

그리고 위 이슈를 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 로그 추가하여 추이를 보기로 하였습니다.

위 수정사항으로 더이상 저런 헤괴망측한 오류는 나오지 않았습니다.

하지만 간헐적으로 종종 로그는 수집되고 있었답니다.

오늘의 괴담은 여기까지입니다. ^^

Jenkins로 Google Play에 APK, AAB를 업로드하기 위해서는 플러그인 설치 및 Google Play 콘솔에서 서비스 계정 설정이 필요함.

 

[Google Play] Google Play 콘솔 로그인 후 설정 - API 액세스 선택

 

[Google Play] 새 프로젝트 만들기 선택 후 저장

 

[Google Play] 프로젝트 보기 선택하여 Google Cloud Platform 으로 이동

 

[Google Cloud ] 메뉴 - IAM 및 관리자 - 서비스 계정 선택

 

[Google Cloud Platform] + 서비스 계정 만들기 선택

 

[Google Cloud Platform] 서비스 계정 세부 정보의 서비스 계정 이름, 서비스 계정 설명 등 기입 후 만들고 계속하기 선택

 

[Google Cloud Platform] 이 서비스 계정에 프로젝트에 대한 액세스 권한 부여 에서 역할소유자 선택 후 계속

 

[Google Cloud Platform] 사용자에게 이 서비스 계정에 대한 액세스 권한 부여 에서는 필요시 추가 후 완료

 

[Google Cloud Platform] 생성된 서비스 계정을 선택

 

[Google Cloud Platform] 키 탭을 선택 후 키 추가 선택

 

[Google Cloud Platform] 새 키 만들기 - JSON - 저장 선택하여 json 파일 저장

 

 

[Google Play] 다시 Google Play 콘솔로 넘어와서 설정 - API 액세스 에서 서비스 계정의 서비스 계정 새로고침 선택

 

[Google Play] 서비스 계정에서 권한 부여 선택

 

[Google Play] 사용자 초대에서 계정 권한 탭에서 출시 관련 항목들 선택

 

[Google Play] 앱 권한 탭 선택 후 **애플리케이션 추가**로 앱을 선택

 

[Google Play] 앱 추가시 권한 확인 후 적용 선택

 

[Google Play] 사용자 초대 선택

 

[Google Play] 사용자 초대 완료 후 추가된 사용자 확인

 

[Jenkins] Jenkins 플러그인 설정에서 Google Play Android Publisher Plugin 을 설치

 

[Jenkins] Add Credentials 에서 Google Service Account from private key 선택

 

[Jenkins] Project Name 기입 후 GCP에서 서비스 계정 생성시 저장한 json 파일을 선택한 후 OK 선택

 

[Jenkins] 업로드할 job 에서 플러그인과 추가한 Credentials로 업로드 설정

  • Pipeline 사용시 Jenkinsfile에 추가
stage('Publish artifacts') {
    steps {
        androidApkUpload googleCredentialsId: 'test',
            apkFilesPattern: '**/outputs/**/*.aab',
            trackName: 'internal',
            rolloutPercentage: '100'
    }
}
  • job 설정에서 직접 설정시
    • 빌드 후 조치에 Upload Android AAB/APKs to Google Play 추가
    • Google Play account - Specific credentials 선택 후 추가하였던 Credentials ****의 Project Name 선택
    • APK/AAB 위치 설정
    • 배포할 트랙 선택, 단계적 배포 수준, In-app Update priority 등을 설정
    • 업데이트 문구도 설정 가능함
    • 설정 저장 후 사용

  •  

+ Recent posts