기본 콘텐츠로 건너뛰기

[상추 키우기] 두 번째 상추...

 게으름으로 인하여 두 번째 상추 재배는 수확까지 끝난 다음에야 포스팅을 하게 되었습니다. 두 번째 상추 재배는 새로운 그로잉스펀지와 재배기에 심을 수 있는 12포트 중에서 4포트만 사용하여 상추를 재배했습니다. 그리고, TDS 측정기를 이용하여 양액의 농도 측정도 하면서 재배를 하였습니다. 수경 재배기를 살 때 포함되어 왔던 그로잉스펀지는 지난번 상추 재배로 인하여 모두 소비되어서 알리에서 새로운 스펀지를 주문했습니다. 주문하면서 실수로 동그란 스펀지가 아닌 네모 스펀지를 주문하는 바람에 포트에 넣었을 때 약간의 공간이 생깁니다만 크게 문제가 되지는 않습니다. 그런데 이번에 스펀지는 번들 스펀지에 비해서 밀도가 높다라고 해야할지 스펀지에 구멍이 적은 것 같습니다. 상추 뿌리가 스펀지를 뚫고 나오는데 지난번 보다 오래 걸린것 같습니다. 다음에 주문할 때에는 이런 부분을 좀 주의해야 할 것 같습니다. 그리고, 스펀지와 함께 TDS 측정기도 함께 구매해서 양액의 농도도 측정하였습니다. 상추의 경우 560 ~ 840 정도의 TDS 범위로 양액을 맞춰주라고 하는데, 수경 재배기에 번들된 양액을 12포트 분량으로 혼합하면 이 범위를 만족합니다. 지난번 재배에서는 이 값을 몰라서 싹을 틔우는 시기와 어린 시기에 양액 농도를 낮게 했었는데 그럴 필요는 없었던 것 같습니다. 약 5주 가량 길러서 수확하기 직전의 상태입니다. 12포트를 키울 떄 보다 빛을 잘 받아서 그런지 웃자람도 없고, 상치 잎의 크기도 지난번보다 상대적으로 컸습니다. 보통 상추를 키울때, 잎이 커지면 일부를 따 먹으면서 계속 키우는데 4포트만 키우게 되면 잎을 따 먹기에는 양이 너무 적어서 다 키워서 한번에 수확을 해야 했습니다. 그래서 다음 재배는 포트수를 조금 더 늘려서 시도해보겠습니다. 

FlutterJNI.java line 116 io.flutter.embedding.engine.FlutterJNI.loadLibrary

 회사에서 운영중인 앱을 flutter로 개발 환경을 변경하고, 스토어에 배포를 한 이후에 몰려오는 문제로 폭풍같은 기간을 보내고 있는데, 건수는 많지 않으나 유독 앱이 실행하지 않는다라는 고객 불만이 많았습니다.

crashlytics에 아래와 같은 비정상 종료 로그가 그 불만의 원인으로 추정되었습니다.

Fatal Exception: java.lang.RuntimeException
Unable to start activity ComponentInfo{xxx.xxxxxxxxx.xxxxxxxxxxxx/xxx.xxxxxxxxx.xxxxxxxxxxxx.MainActivity}: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/base.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/split_config.xxxhdpi.apk"],nativeLibraryDirectories=[/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/lib/arm64, /system/lib64, /hw_product/lib64, /system/product/lib64]]] couldn't find "libflutter.so"

오류는 간단하게도 libflutter.so 파일을 찾을 수 없다라는 것입니다. 오류 내용 만으로는 앱이 동작하지 않는 것이 매우 당연한 것입니다. 그런데 왜 libflutter.so가 빠져 있고, 일부 사용자만 문제가 발생하는 것일까? 라는 의문에서 파고들기 시작했습니다.

당연히 구글 검색으로 시작했고, 나 뿐만 아닌 다른 사람들도 동일한 문제가 있음을 확인했습니다. 나만의 문제가 아니면 이미 해법은 있을 것이라는 가벼운 마음으로....

참고로 저희 앱은 구글 플레이 스토어를 통해서 배포하고 있고, app bundle 형태로 등록하고 있습니다.


첫번째 시도..

Fatal Exception: java.lang.RuntimeException
Unable to start activity ComponentInfo{xxx.xxxxxxxxx.xxxxxxxxxxxx/xxx.xxxxxxxxx.xxxxxxxxxxxx.MainActivity}: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/base.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.en.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.fr.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.hdpi.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.x86.apk"],nativeLibraryDirectories=[/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/lib/x86, /data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/base.apk!/lib/x86, /data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.en.apk!/lib/x86, /data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.fr.apk!/lib/x86, /data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.hdpi.apk!/lib/x86, /data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-1/split_config.x86.apk!/lib/x86, /system/lib, /vendor/lib, /data/downloads, /data/priv-downloads]]] couldn't find "libflutter.so"

오류에서 보듯이 x86 버전에 대한 라이브러리를 찾고 있었습니다. 안드로이드 계열에 익숙하지 않아서 x86 칩을 사용하는 단말인가 싶어서 오류 발생 단말 정보를 찾아보았습니다. 당연히 ARM 계열 단말이었습니다. arm과 x86을 같이 지원하는 경우도 있나.. 그런가 보다 대충 이해하고 넘어가기로 했습니다. 그게 중요하지 않으니까요..

그럼 x86 라이브러리 만 있으면 어찌 되었던 돌아 가는 것이니까. 라이브러리가 포함되는지 여부 부터 확인을 했습니다. apk 로 빌드해서 라이브러리가 포함 여부를 확인했는데 역시 x86 폴더는 libflutter.so가 포함되어 있지 않았습니다. 

찾아보니 flutter는 x86을 지원하고 있지 않았습니다. 그러니 x86 폴더는 당연히 비어 있는 것이지요.

이 문제에 대한 구글 검색에서 나오는 것들과 같이 abiFilters를 지정해서 해결하기로 결정했다. x86은 지원할 수 없으니까.. 

abiFilters 'armeabi-v7a','arm64-v8a'


두번째 시도..

첫번째 시도에도 불구하고, 여전히 동일한 오류가 계속 등록되었습니다. 예외 내용은 미묘하게 바뀌긴 했습니다.

Fatal Exception: java.lang.RuntimeException
Unable to start activity ComponentInfo{xxx.xxxxxxxxx.xxxxxxxxxxxx/xxx.xxxxxxxxx.xxxxxxxxxxxx.MainActivity}: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/base.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/split_config.xxxhdpi.apk"],nativeLibraryDirectories=[/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/lib/arm64, /system/lib64, /hw_product/lib64, /system/product/lib64]]] couldn't find "libflutter.so"

이번엔 x86이 아닌 lib64였습니다. 문제는 arm64-v8a는 flutter도 지원하고 있다는 것이었습니다. 이 문제에 대한 구글에서 검색되는 해결 방법은 armeabie-v7a만 포함하라는 것이었습니다. 문제는 64비트 지원하는 단말에서 32비트를 사용하고 싶지 않았고, 플레이 스토어에 앱을 등록하려면 이제는 반드시 64비트가 포함되어야 한다는 것이었습니다.

오류가 발생하는 단말의 대체로 샤오미에 몰려 있어서 샤오미 관련 이슈인가라는 의심을 하고 있는 중에서 아래 링크를 보게 되었습니다.

https://issuetracker.google.com/issues/147096055?pli=1#comment6

그래서 해당 코멘트에서 가이드하는 방법으로 수정을 했습니다.

android.bundle.enableUncompressedNativeLibs=false


세번째 시도..

이 정도 했으면 문제가 더 이상 발생하지 않을 것도 같은데, 여전히 문제는 계속 발생했습니다.

구글 검색에서 더 이상 새로운 내용은 보이지 않고, 전체 사용자에 비해서 빈도가 높지 않아서 묻고 갈까도 고민을 했으나, 유독 고객 리뷰에 문제 제기가 많아서 묻을 수도 없어서 원점으로 돌아가서 다시 문제를 보기로 했습니다.

예외 메시지를 다시 들여다 봤습니다.

Fatal Exception: java.lang.RuntimeException
Unable to start activity ComponentInfo{xxx.xxxxxxxxx.xxxxxxxxxxxx/xxx.xxxxxxxxx.xxxxxxxxxxxx.MainActivity}: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/base.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/split_config.xxxhdpi.apk"],nativeLibraryDirectories=[/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/lib/arm64, /system/lib64, /hw_product/lib64, /system/product/lib64]]] couldn't find "libflutter.so"

libflutter.so 파일이 시스템에 포함된 파일은 아니기 때문에 /lib/arm64 와 같은 시스템 폴더에서 찾을 수 없는 것은 당연한 것이기 때문에 무시하고, base.apk, split_config.xxxhdpi.apk 두개 중에 어디인가에 파일이 포함되어 있어야 하는게 맞다는 소리인데 어디에 포함되어 있는게 정상일까?

app bundle로 배포될 경우에 각 단말에 맞는 형태로 앱이 변경되어 설치된다 정도만 알고 있었기 때문에 app bundle을 다시 공부해보았습니다.

간단히 요약하면 앱 번들 형태로 배포시 앱의 기본 모듈을 포함하는 base.apk, abi 별 라이브러리가 포함되는 split_config.abi.apk, 해상도별 리소스를 포함하는 split_config.dpi.apk 형식으로 나뉘어져서 배포되는 걸로 설명되어 있었습니다. 

그런데 예외를 보면 base.apk와 split_config.dpi.apk 에 해당하는 것은 보이나, split_config.abi.apk는 보이지 않습니다. 

내가 만든 앱 번들이 잘못되었나? 라는 의문을 갖고 확인해보기로 했습니다.

bundletool build-apks --bundle=build/app/outputs/bundle/release/app-release.aab --output=out_app.apks --connected-device --ks=password.jks --ks-pass=file:password.pwd --ks-key-alias=keyalias --key-pass=file:password.pwd

앱 번들을 연결된 단말의 형태에 맞는 apk를 생성해주는 명령어입니다.

base.apk, split_config.arm64_v8a.apk, split_config.xxxhdpi.apk 세개의 파일이 생성되고, split_config.arm64_v8a.apk에 libflutter.so 파일이 포함되어 있음을 확인했습니다. 

그렇다면 내가 만든 앱 번들은 문제가 없고, 설정이나 그런 이유로 플레이 스토어에서 배포되는 과정에서 파일 누락이 발생했다는 이야기가 되었습니다. 그래서 다시 구글신의 도움을 받아 보기로 했으나 검색되는 내용으로는 어떤 특별한 유저들이 구글 플레이가 아닌 다른 경로를 통해서 앱을 설치하는 경우에 누락이 발생할 수 있다라는 이해할 수 없는 내용들이 검색되었습니다. 뭐 유저들이 그런 경로를 통해서 다운로드를 받으셨다고 치고, 그런데 그 왜 유독 abi에 해당하는 파일만 누락되며, 그렇게 다른 경로를 통해서 받으셨는데 플레이 스토어에 리뷰를 달고 계실까..

답이 없는 것으로 판단하고 그럼 이 문제를 회피할 수 없을까? apk로 전체를 배포 하면 되는데 그건 플레이스토어에서 막혀 있으니, 앱 번들이면서 apk 형태로 배포할 수 있는 방법을 없을까를 찾아 보았습니다.

역시나 방법은 있었습니다. 앱 번들 설정에서 abi를 분리하지 않도록 설정할 수 있었습니다.

bundle {
abi {
enableSplit = false
}
}

이렇게 설정을 하면 base.apk에 모든 abi 파일들이 포함되기 때문에 앱 번들로 배포하는 효과는 퇴색됩니다. (arm64_v8a 뿐만 아니라 armeabi_v7a도 함께 포함됩니다.) 라이브러리 파일들이 용량이 크면 최대 용량 초과 등의 문제를 발생시킬 수는 있습니다만, 그 정도 상황은 아니고 설치는 되었으나 앱이 안돌아가는 상황보다는 좋을 것이기 때문에 이 정도로 문제를 닫기로 했습니다.

구글 플레이 스토어의 알 수 없는 문제인 걸로 마무리를 짖습니다.

혹시나 제가 잘못 이해하고 있거나 하면 댓글로 남겨주시기 바랍니다. 

추가 : 그러면 flutter로 변경하기 이전 버전에서는 문제가 없었는가를 찾아 보았습니다. 비슷한 빈도로 다른 라이브러리를 로드하지 못했다라는 예외가 발생하였으나, flutter 처럼 앱이 당장 실행되지 않는 문제는 아니기에 부각이 덜 되었던 것 같습니다.

댓글

이 블로그의 인기 게시물

[상추 키우기] 수경재배기에 양액 투입

 상추를 파종한지 10일이 지나서 상추들이 이만큼 자랐습니다. 상추의 뿌리가 포트 밖으로 빠져나올 정도로 길게 자랐습니다. 그래도 아직은 뿌리의 가닥 수도 작고, 가늘고 약합니다. 사무실에 사람이 있는 동안은 난방을 하니까 괜찮은데, 저녁에는 아직도 기온이 많이 내려가기 때문에 성장이 더딘것 같습니다. 이제 낮기온도 꽤 올라가고 꽃도 피기 시작하니까 멀지 않아서 폭풍 성장을 할 것 같습니다. 수경재배기 메뉴얼에서는 2주에 한 번 전체 물을 갈아주고, 그 외에는 보충을 해주라고 되어 있습니다. 아직 상추가 싹이 작기도 하고, 10일 밖에 지나지 않았지만, 그 동안 물로만 키우고 있었기 때문에 오늘은 양액을 넣어 주기로 했습니다. 그래서 전체 물갈이를 할 예정입니다. 우선 양액을 준비합니다. 국내에서 많이 판매되는 양액이 있기는 합니다만, 수경재배기에 포함되어 있는 양액을 먼저 사용하려고 합니다. 수경재배기에 포함되어 있는 양액은 액체 상태가 아닌 알갱이 상태로 두 개의 병에 담겨 있습니다. 각 병에 물 100ml 씩을 넣고 알갱이가 다 녹을 때까지 섞어 줍니다. 병에 100ml 눈금이 표시되어 있기 때문에 눈금까지 넣어 주면 됩니다. 메뉴얼 상에는 역삼투압 방식으로 정수된 물을 사용하라고 하는데, 전 그냥 수돗물을 사용했습니다. 순수한 물을 사용하면 아무래도 양액의 농도나 같은 것들이 좀 더 정확해 지지 않을까 싶긴 합니다만, 사무실에 정수기도 없고.... 그렇다고 약국에 증류수를 사러가기도 뭐하고.. 근처에 약국도 없고.. ^^;;;;;;; 여튼, 알갱이가 다 녹을 때까지 섞어 줍니다. A용액은 약간 노란빛이 드는 액체가 되고, B용액은 투명색을 그대로 유지합니다. 그리고, 2L 생수병에 물 2L를 채우고, A용액을 10ml 넣어준 다음에 B용액 10ml 를 추가하였습니다. A/B용액이 직접 만나게 되면 화학 반응이 일어날 수 있는 모양입니다. 그래서 A용액이 물에 충분히 희석된 다음에 B용액을 넣었습니다.  메뉴얼에는 12포트이면 각각 15...

Admob UMP(Google User Messaging Platform) SDK를 이용한 GDPR(General Data Protection Regulation) 대응

얼마 전 부터 운영중인 앱의 Admob 계정을 들어가면 화면 상단에 아래와 같은 눈에 거슬리는 공지가 표시되기 시작했습니다. "올해 안에 EEA 및 영국 사용자에게 광고를 게재하는 모든 게시자는 Google에서 인증한 동의 관리 플랫폼(CMP)을 사용해야 합니다. Google의 자체 동의 관리 솔루션을 포함하여 Google에서 인증한 CMP 이면 어떤 것이든 이 목적으로 사용할 수 있습니다. Google의 동의 관리 솔루션을 사용하는 데 관심이 있으면 먼저 GDPR 메시지를 설정하고 UMP SDK를 구현하세요." 유럽쪽 사용자에 대해서 GDPR(General Data Protection Regulation) 동의 를 받을 수 있도록 하라는 것이다. GDPR 동의에 대해서 처리해야할 법적인 절차를 잘 모르니 친절한(?) Google의 도움을 받을 수 있다고 하니 받는 것으로 했습니다. 메시지를 보면 구글에서 인증한 동의 관리 플랫폼(CMP)를 사용하거나, 구글에서 제공하는 UMP SDK를 이용해서 구현 하면 된다는 내용입니다. 구글에서 UMP SDK를 제공한다고 하니, 이용하기로 합니다. UMP SDK 는 별도의 SDK 형태로 배포되지 않고, Admob SDK에 포함되어 있고, UMP SDK를 이용하여 구현하는 것은 가이드 페이지에 있는 것을 따라가면 되는 수준이라 여기에서는 생략하도록 합니다. # 참고 : - https://support.google.com/admob/answer/10113207 - https://developers.google.com/admob/ios/privacy - https://developers.google.com/admob/android/privacy - https://developers.google.com/admob/flutter/eu-consent 저의 경우에는 구현은 어렵지 않게 했으나, 이 것의 동작을 이해하는데 오랜 시간이 필요했습니다. 그 이유는 사용자로부터 개인정보 이용에 대한 동의를 얻지 못했을 때 광...

[상추 키우기] 두 번째 상추...

 게으름으로 인하여 두 번째 상추 재배는 수확까지 끝난 다음에야 포스팅을 하게 되었습니다. 두 번째 상추 재배는 새로운 그로잉스펀지와 재배기에 심을 수 있는 12포트 중에서 4포트만 사용하여 상추를 재배했습니다. 그리고, TDS 측정기를 이용하여 양액의 농도 측정도 하면서 재배를 하였습니다. 수경 재배기를 살 때 포함되어 왔던 그로잉스펀지는 지난번 상추 재배로 인하여 모두 소비되어서 알리에서 새로운 스펀지를 주문했습니다. 주문하면서 실수로 동그란 스펀지가 아닌 네모 스펀지를 주문하는 바람에 포트에 넣었을 때 약간의 공간이 생깁니다만 크게 문제가 되지는 않습니다. 그런데 이번에 스펀지는 번들 스펀지에 비해서 밀도가 높다라고 해야할지 스펀지에 구멍이 적은 것 같습니다. 상추 뿌리가 스펀지를 뚫고 나오는데 지난번 보다 오래 걸린것 같습니다. 다음에 주문할 때에는 이런 부분을 좀 주의해야 할 것 같습니다. 그리고, 스펀지와 함께 TDS 측정기도 함께 구매해서 양액의 농도도 측정하였습니다. 상추의 경우 560 ~ 840 정도의 TDS 범위로 양액을 맞춰주라고 하는데, 수경 재배기에 번들된 양액을 12포트 분량으로 혼합하면 이 범위를 만족합니다. 지난번 재배에서는 이 값을 몰라서 싹을 틔우는 시기와 어린 시기에 양액 농도를 낮게 했었는데 그럴 필요는 없었던 것 같습니다. 약 5주 가량 길러서 수확하기 직전의 상태입니다. 12포트를 키울 떄 보다 빛을 잘 받아서 그런지 웃자람도 없고, 상치 잎의 크기도 지난번보다 상대적으로 컸습니다. 보통 상추를 키울때, 잎이 커지면 일부를 따 먹으면서 계속 키우는데 4포트만 키우게 되면 잎을 따 먹기에는 양이 너무 적어서 다 키워서 한번에 수확을 해야 했습니다. 그래서 다음 재배는 포트수를 조금 더 늘려서 시도해보겠습니다.