안드로이드 앱에 오픈소스 라이선스 화면 붙이다 크래시를 두 번 냈습니다

안드로이드 앱에 오픈소스 라이선스 화면 붙이다 크래시를 두 번 냈습니다

2026년 4월 · 더보기 화면 하나 고치는 데 일주일 걸린 이야기

#안드로이드개발 #오픈소스라이선스 #AboutLibraries #JetpackCompose #인디개발자블로그


인디 앱 개발하시는 분들, 더보기 메뉴에 “오픈소스 라이선스” 항목 하나쯤은 다 있으실 거예요. 제 앱 케톤에도 있습니다. 이게 사실 별거 아닌 화면이잖아요. 앱에 쓴 오픈소스 라이브러리 목록 쭉 뿌려주는 그 흔한 화면.

근데 이 별거 아닌 화면 하나 때문에 최근 일주일을 꽤 시끄럽게 보냈습니다. 크래시를 두 번이나 냈거든요. 한 번은 인기 있는 라이브러리를 고른 탓이었고, 다른 한 번은 최신 버전이 제일 안전할 거라고 믿은 탓이었습니다. 둘 다 제 선택의 결과였고요.

이번 글은 남 탓이 아니라 제 라이브러리 고르는 습관에 대한 반성문에 가깝습니다. 같은 실수 안 하시라고 공유합니다.


첫 번째 삽질 — “제일 유명한 거 쓰면 되겠지”

시작은 이랬습니다. 케톤 더보기 탭에 오픈소스 라이선스 화면을 넣자. 안드로이드 개발자라면 가장 먼저 떠오르는 게 구글이 공식으로 제공하는 play-services-oss-licenses죠. 플러그인 붙이면 빌드 시 알아서 라이선스 목록 만들어주고, Activity 하나 띄워주면 끝. 간단합니다.

저도 그냥 그걸 썼습니다. 별 고민 없이요. “구글 공식인데 뭐 문제 있겠어” 하는 마음으로요.

1
2
// MoreScreen.kt
startActivity(Intent(context, OssLicensesMenuActivity::class.java))

이 두 줄이면 끝납니다. 쉽죠. 릴리즈 때도 잘 돌아갔고, 저도 몇 번 눌러봤는데 라이선스 목록 멀쩡하게 뜨더라고요. 그렇게 한참을 잊고 지냈습니다.


그러다 Crashlytics에 빨간 딱지가 하나 떴습니다

4월 11일이었어요. Crashlytics 들여다보다가 새 Fatal Exception이 하나 올라와 있는 걸 봤습니다.

crash-stacktrace

1
2
OssLicensesActivity.onCreate(): NullPointerException
Attempt to read from field 'java.lang.String nc.b.r' on a null object reference

OssLicensesActivity.onCreate() 안에서 NPE. nc.b.r이 뭔지도 모르겠고 (난독화된 필드명이라 원래 모릅니다), 스택트레이스는 전부 구글 라이브러리 내부였습니다. 제 코드는 한 줄도 안 나와요. 그냥 startActivity로 Activity 띄운 게 전부니까요.

그리고 여기서부터 좀 아팠는데요. 이 크래시를 맞은 분들이 있었습니다. 루마니아 부쿠레슈티에서 신규 유저 두 분이 그날 저녁에 설치를 했어요. 한 분은 온보딩 29초 만에 끝내고 체중 입력하고 단식 타이머 들어가서 56초 탐색하다 크래시. 다른 한 분은 온보딩 스킵하고 단식 바로 시작했다가 화면 탐색 중 크래시. 그리고 두 분 다 크래시 뜨고 2분 안에 앱을 지웠습니다.

물론 그 두 분이 그냥 앱이 별로라서 나갔을 수도 있어요. 근데 하필 크래시 난 2분 뒤에 삭제한 걸 보면… 네, 크래시 때문이었다고 저는 굳게 믿고 있습니다. 앱 자체가 부족해서였다면 더 슬프니까요.


GitHub 이슈를 열어봤더니

혹시나 싶어서 google/play-services-plugins 리포로 가봤습니다. 같은 NPE 스택트레이스 이슈가 있더라고요. #119. 2023년에 올라왔고, 댓글도 여럿 달려있는데, 미해결 상태로 남아있었습니다.

그때서야 라이브러리 릴리즈 페이지를 열어봤어요. play-services-oss-licenses:17.1.0, oss-licenses-plugin:0.10.6. 둘 다 2023년 이후로 새 릴리즈가 없습니다. 이슈는 쌓여있는데 대응이 없어요.

솔직히 좀 놀랐습니다. "구글 공식"이라는 타이틀에 속았다고 하긴 좀 그래요. 정확히는 제가 도입 전에 확인을 안 한 거죠. 라이브러리 페이지 들어가서 마지막 릴리즈 날짜랑 열린 이슈 대충 훑어보는 데 1분도 안 걸리는데, 그 1분을 안 쓴 건 저거든요.

[!note] 교훈 하나
인기 ≠ 활발한 관리. 구글이 만들었든, star가 몇천 개든, 마지막 커밋이 언제인지부터 보자.

그래서 교체하기로 했습니다. 대안으로 유명한 건 AboutLibraries. Compose 친화적이고, 활발히 관리되고 있고, UI도 커스터마이징 가능하고. 좋아 보였어요. 최신 버전이 11.6.3이길래 당연히 그걸 썼습니다.

그러고 나서 두 번째 삽질이 시작됐어요.


두 번째 삽질 — “최신이면 제일 안전하겠지”

AboutLibraries 11.6.3libs.versions.toml에 적어 넣고, 플러그인 붙이고, LibrariesContainer 컴포저블 쓰고, Navigation에 LicenseRoute 하나 추가했습니다. 빌드 성공. APK 잘 나오고. 기기에 설치.

더보기 탭 들어가서 “오픈소스 라이선스” 탭. 화면 로딩. 크래시.

1
2
3
java.lang.NoSuchMethodError: No static method FlowRow(
...FlowRowOverflow;...
) in class Landroidx/compose/foundation/layout/FlowLayoutKt;

NoSuchMethodError. 이거 보면 대충 감이 옵니다. 컴파일은 됐는데 런타임에 실제 메서드를 못 찾는 거. 뭔가 ABI가 틀어진 거죠. 근데 **FlowRow**가 왜 없어요? Jetpack Compose Foundation에 분명히 있는데?

한참 파봤습니다. 답은 좀 허무한데 이런 구조였어요.

compose-abi-mismatch

  • AboutLibraries 11.6.3JetBrains Compose(org.jetbrains.compose) 1.7.3으로 컴파일돼 있습니다. KMP 지원용이에요.
  • **JetBrains Compose의 FlowRow**는 FlowRowOverflow라는 파라미터를 추가로 받습니다.
  • **Jetpack Compose의 FlowRow**는 그 파라미터가 없어요.
  • 제 프로젝트는 순수 안드로이드 — Jetpack Compose입니다.

Gradle은 의존성 해석할 때 JetBrains Compose를 Jetpack Compose로 리다이렉트해줍니다. 그래서 빌드는 성공해요. 근데 컴파일 시점에 라이브러리 코드 안에 박힌 메서드 시그니처는 JetBrains 버전 그대로라, 런타임에 실제 FlowRow를 호출하면 "그런 메서드 없는데요?"가 뜨는 겁니다.

이게 그 유명한 ABI 불일치라는 놈이었어요. 같은 이름 쓰는 두 스택(Jetpack vs JetBrains)이 섞일 때 빌드 성공이 런타임 호환을 보장하지 않는다는 거.

버전을 쭉 내려봤습니다. 11.2.3이 Jetpack Compose로 컴파일된 마지막 안정 버전이더라고요. 내리고 실행. 조용. 라이선스 화면이 얌전히 뜹니다. KetonTheme 색상까지 잘 적용돼서요.

[!note] 교훈 둘
최신 버전이 항상 안전한 건 아니다. 특히 같은 이름(“Compose”)을 쓰는 두 스택이 섞일 수 있는 라이브러리라면, 내 프로젝트 스택이랑 맞는 버전이 따로 있는지 확인하자.

이 두 번째는 사실 진짜 깊은 함정이었어요. 라이브러리 README 어디에도 "KMP 버전이라 Jetpack Compose 프로젝트에서는 11.2.3 쓰세요"라고 안 적혀있거든요. 그냥 “latest version: 11.6.3” 이렇게만 되어 있어요. 그래서 저처럼 최신 버전부터 집어넣는 사람은 한 번은 당합니다. 당한 사람이 저만은 아닐 거예요. 아마도…


그래서 지금은

지금은 AboutLibraries 11.2.3 + Compose 네이티브 UI로 돌아가고 있습니다. KetonTheme 색상도 적용됐고, slideAnimatedComposable로 들어가는 애니메이션도 앱 전체랑 일관되게요. 크래시도 조용해졌고, 더보기 > 오픈소스 라이선스 들어가면 라이브러리 목록이 깔끔하게 뜹니다.

루마니아에서 크래시 맞으셨던 두 분은 이미 앱을 지우고 가셨으니 이제 이 글을 보실 일은 없겠지만, 혹시라도 이 글 보고 계신다면 진심으로 죄송합니다. 그때 제가 라이브러리 한 번만 더 들여다봤으면 그 크래시는 안 났을 텐데요.


다음부터 쓸 라이브러리 선정 체크리스트 (1분 컷)

이번에 아프게 배워서 이제는 라이브러리 고를 때 이거부터 봅니다.

library-health-check

  1. 마지막 릴리즈가 언제인가 — 2년 넘게 릴리즈 없으면 일단 한 번 더 생각합니다. 유명세랑 방치는 별개예요.
  2. open issues와 PR이 살아있는가 — 이슈는 쌓이는데 답변이 없으면 신호입니다. 특히 크래시 리포트가 방치돼 있으면 피하는 게 좋아요.
  3. 내 스택과 호환되는가 — 특히 "Compose"처럼 같은 이름 쓰는 두 스택(Jetpack vs JetBrains)이 있는 영역에서는, 라이브러리가 어느 쪽으로 컴파일됐는지 확인하는 게 중요합니다. dependencies 트리에서 org.jetbrains.compose.*가 transitive로 딸려오면 KMP 쪽 빌드예요.

앞의 두 개는 라이브러리 깃허브 페이지만 열면 30초면 확인합니다. 세 번째는 좀 품이 드는데, ./gradlew app:dependencies | grep compose 한 줄이면 대부분 판별 가능해요. 빌드 성공 ≠ 런타임 호환, 이거 하나만 머리에 넣어두시면 됩니다.

라이브러리 하나 잘못 고른다고 앱이 망하진 않아요. 근데 오늘 봤듯이 첫 세션에서 크래시 맞은 유저는 거의 100% 이탈합니다. 그것도 우리가 손 써볼 틈도 없이요. 그게 무서운 거예요.

저는 이번에 두 번 맞고 배웠는데, 이 글 읽으시는 분들은 한 번도 안 맞으시길 바라면서 마칩니다.


👉 케톤 — Play Store에서 보기

#안드로이드개발 #오픈소스라이선스 #AboutLibraries #JetpackCompose #인디개발자블로그 #Crashlytics #라이브러리선정 #안드로이드크래시 #KMP #ComposeABI


  • [[oss-licenses-crash]] — Problem 정의
  • [[spec-oss-licenses-crash]] — AboutLibraries 교체 Spec
  • [[learn-aboutlibraries-compose-compat]] — ABI 함정 상세 학습 노트
  • [[insight-crash-retention-killer]] — 크래시 → 즉시 삭제 패턴