[Android] Moloco SDK와 Ktor 버전 충돌

Daemon·2025년 12월 7일

Android

목록 보기
8/13
post-thumbnail

들어가며

R8 규칙이나 버전 업데이트도 하지 않았고 아무것도 변경하지 않았는데 앱이 크래시가 발생했다.
이 글에서는 코드를 한 줄도 건드리지 않았음에도 발생한 크래시의 원인을 추적하고, Third-Party SDK의 숨겨진 의존성이 어떻게 앱을 파괴할 수 있는지 분석한다. 그리고 이 이슈를 Ktor 팀에 직접 보고한 과정도 공유한다.


1. 무슨 일이 벌어졌나

1.1 크래시 로그

Fatal Exception: java.lang.NoClassDefFoundError
Failed resolution of: Lio/ktor/client/plugins/UserAgent;

com.moloco.sdk.internal.http.a$a.a (SourceFile:1)
com.moloco.sdk.internal.http.a$a.invoke (SourceFile:1)
io.ktor.client.HttpClientKt.HttpClient (HttpClient.kt:648)
io.ktor.client.HttpClientJvmKt.HttpClient (HttpClientJvm.kt:25)
com.moloco.sdk.internal.http.a.a (SourceFile:1)
...
com.moloco.sdk.publisher.Moloco$initialize$1.invokeSuspend (SourceFile:6)

1.2 이상한 점

스택 트레이스를 자세히 보면 뭔가 이상하다:

  • com.moloco.sdk.internal.http - Moloco SDK 내부 코드
  • io.ktor.client.HttpClientKt.HttpClient - Ktor HttpClient 초기화
  • io.ktor.client.plugins.UserAgent - 찾을 수 없는 클래스

내 앱 코드에서 UserAgent 플러그인을 사용하지만 코드를 변경한 적이 없다. Moloco SDK 내부에서 발생한 문제였다.


2. 왜 이런 일이 벌어졌나

2.1 상황 재구성

[내 앱의 Ktor 3.x]
        +
[Moloco SDK의 내부 Ktor 사용]
        ↓
    Gradle 의존성 해결
        ↓
    하나의 Ktor 버전만 사용
        ↓
    R8 코드 축소 (release 빌드)
        ↓
"UserAgent는 앱에서 직접 안 쓰니까 제거해도 되겠네?"
        ↓
Moloco SDK 초기화 시 ClassNotFoundException 발생

2.2 왜 R8이 클래스를 삭제했을까?

Ktor UserAgent 문서에 따르면, UserAgent 플러그인은 ktor-client-core 아티팩트에 포함되어 있다.

// UserAgent 플러그인 사용 예시
val client = HttpClient(CIO) {
    install(UserAgent) {
        agent = "Ktor client"
    }
}

문제는 Moloco SDK가 내부적으로 이 플러그인을 사용하는데, R8 입장에서는 "앱 코드에서 직접 참조 안 하니까 필요 없겠네"라고 판단한 것이다.

SDK는 난독화된 AAR로 배포되니까 R8이 내부 동작을 제대로 분석하지 못한 것 같다.

2.3 Ktor 3.x의 변경사항이 영향을 줬을까?

마이그레이션 문서를 보면 Ktor 3.x에서 꽤 많은 변경이 있었다:

  • ServiceLoader 패턴 변경: Android 성능 최적화를 위해 변경됨
  • kotlinx-io 전환: I/O 라이브러리가 완전히 교체됨
  • 내부 구조 리팩토링: 여러 모듈이 통합되거나 분리됨

이런 변경들이 R8 최적화와 충돌할 여지를 만든 것 같다.


3. 돌아보기

3.1 내가 한 것

  • 아무것도 변경하지 않음
  • Ktor 버전 그대로 유지
  • ProGuard 규칙 이미 적용됨

ProGuard 규칙도 이미 있었다:

# 이미 적용되어 있던 규칙
-keep class io.ktor.client.** { *; }
-keep class io.ktor.client.plugins.** { *; }

3.2 실제 문제의 원인

  • Moloco SDK 내부에서 Ktor HttpClient를 특정 방식으로 초기화
  • SDK가 사용하는 Ktor 버전/패턴과 R8 최적화 간의 충돌
  • SDK 제공사가 이러한 호환성 문제를 테스트하지 않음

3.3 Third-Party SDK의 숨겨진 위험

미리 대비할 수 있으면 좋겠지만 Third-Party SDK는 블랙박스인 것 같다.

SDK 제공사가 결정하는 것들:

  • 내부적으로 어떤 라이브러리를 사용할지
  • 어떤 버전을 사용할지
  • Transitive 의존성을 어떻게 관리할지

개발자가 통제할 수 없는 것들:

  • SDK 소스코드 접근 불가 (난독화됨)
  • 내부 의존성 버전 선택 불가
  • 문제 발생 시 직접 수정 불가

4. Ktor GitHub에 이슈 등록

이 문제를 발견한 후, 직접 Ktor 공식 레포지토리에 이슈를 생성했다:

https://github.com/ktorio/ktor/issues/5240

4.1 이슈에 포함한 내용

  1. 상세한 크래시 로그 - 전체 스택 트레이스
  2. 재현 환경 - 기기, OS 버전, 앱 버전
  3. ProGuard 설정 - 이미 적용된 keep 규칙
  4. 핵심 질문들:
    • Ktor 3.x에서 알려진 이슈인가?
    • 추가적인 ProGuard/R8 규칙이 필요한가?
    • ktor-client-core가 UserAgent 클래스를 R8에 명시적으로 노출해야 하는가?

4.2 왜 Moloco가 아니라 Ktor 팀에 보고했나?

Moloco SDK의 문제이긴 하지만, 근본 원인은 Ktor와 R8의 상호작용에 있다고 봤다:

  1. Ktor가 consumer ProGuard rules를 제대로 제공하지 않을 수 있음
  2. Ktor 3.x의 내부 변경이 R8 호환성에 영향을 줬을 수 있음
  3. 다른 SDK들도 동일한 문제를 겪을 수 있으므로 라이브러리 레벨에서 해결이 필요

5. 해결 방법

내가 선택한 방법: Ktor 버전 다운그레이드

# gradle/libs.versions.toml
[versions]
# ktor = "3.x.x"  # 문제 발생
ktor = "2.3.12"   # 안정 버전으로 롤백

6. 다음부터는

6.1 SDK의 Transitive 의존성 파악하기

# SDK가 어떤 라이브러리를 사용하는지 확인
./gradlew :app:dependencies --configuration releaseRuntimeClasspath | grep -E "(ktor|moloco)"

프로젝트에 어떤 의존성이 들어오는지 정기적으로 확인해야 하는데 생각보다 개발하면 고려해야할 부분이 참 많은 것 같다.

6.2 SDK 업데이트 시 철저한 회귀 테스트

SDK 버전을 업데이트할 때는:

  • 전체 앱 기능 테스트
  • Release 빌드로 테스트
  • Staged Rollout 사용 (단계적 배포)

정리하자면

통제할 수 없는 문제가 발생하면 생기는 무력감은 정말 맛이 없다... Third-Party SDK를 사용할 때는 SDK의 내부 의존성을 파악하고, Release 빌드로 철저히 테스트하고, 문제 발생 시 빠르게 롤백할 준비를 해둬야겠다.


참고 자료

1개의 댓글

comment-user-thumbnail
2025년 12월 11일

Moloco 최신 버전 "4.3.1" 변경 후에 NoClassDefFoundError 미발생합니다 :)

답글 달기