[사용자관리] [로그인] 특정 기기에서 kakao login SDK 로 로그인 시도시 Memory leak 발생

SDK Version : 2.11.0

카카오톡 및 카카오계정 로그인을 지원해 로그인 기능을 활용하고 있는데, 특정 기기 ( 현재 이상 기기 : ZFlip 3 ) 에서만 Memory leak 이 발생해 로그인 화면 이후 진입이 안되는 현상을 겪고 있습니다. ( 성공적으로 작동하는 기기 : S20 Ultra, S21, A25 )

구동원리는 액티비티에서 버튼 클릭시 kakao Login 을 요청하고, 콜백을 이용해 유저아이디를 가져오는 것이 성공하였을 시에 액티비티에 붙어 있는 뷰모델의 자사 서버 내 검증 로직을 요청합니다. 플로우는 아래와 같습니다.
Activity → Kakao → Activity → Call ViewModels IO Function

성공적으로 작동하는 기기로 명시한 기기들에서는 정상 작동하는 것을 확인하였으나, ZFlip3 기기에서 아래와 같은 메모리릭이 확인되며 결과적으로 뷰모델의 function 을 호출하지 못해 이후 정상 진행이 되지 않습니다. ( 출처 : Leak Canary Dump )

 ┬───
    │ GC Root: Global variable in native code
    │
    ├─ android.os.ResultReceiver$MyResultReceiver instance
    │    Leaking: UNKNOWN
    │    Retaining 168.1 kB in 2945 objects
    │    ↓ ResultReceiver$MyResultReceiver.this$0
    │                                      ~~~~~~
    ├─ com.kakao.sdk.auth.AuthCodeClient$resultReceiver$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 167.5 kB in 2944 objects
    │    Anonymous subclass of android.os.ResultReceiver
    │    ↓ AuthCodeClient$resultReceiver$1.$callback
    │                                      ~~~~~~~~~
    ├─ com.kakao.sdk.user.UserApiClient$loginWithKakaoTalk$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 167.5 kB in 2942 objects
    │    Anonymous subclass of kotlin.jvm.internal.Lambda
    │    ↓ UserApiClient$loginWithKakaoTalk$1.$callback
    │                                         ~~~~~~~~~
    ├─ com.theangelbridge.tab.presentation.app.ui.sign.SignActivity$requestKakaoLogin$1$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 167.4 kB in 2940 objects
    │    Anonymous subclass of kotlin.jvm.internal.Lambda
    │    this$0 instance of com.theangelbridge.tab.presentation.app.ui.sign.SignActivity with mDestroyed = true
    │    ↓ SignActivity$requestKakaoLogin$1$1.this$0
    │                                         ~~~~~~
    ╰→ com.theangelbridge.tab.presentation.app.ui.sign.SignActivity instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.theangelbridge.tab.presentation.app.ui.sign.
    ​     SignActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
    ​     Retaining 167.3 kB in 2938 objects
    ​     key = 6a6b562a-d1a0-459e-b707-be78822593c1
    ​     watchDurationMillis = 12410
    ​     retainedDurationMillis = 7410
    ​     mApplication instance of com.theangelbridge.tab.TabApp
    ​     mBase instance of androidx.appcompat.view.ContextThemeWrapper

아래는 구현부 코드 스니펫입니다.

UserApiClient.instance.run {
            if (isKakaoTalkLoginAvailable(this@SignActivity)) loginWithKakaoTalk(context = this@SignActivity) {
                token, error ->
                if (error != null) {
                    if (error is ClientError && error.reason == ClientErrorCause.Cancelled) return@loginWithKakaoTalk
                    loginWithKakaoAccount(context = this@SignActivity, callback = callback)
                } else if (token != null) {
                    UserApiClient.instance.me { user, parseError ->
                        if (parseError != null) handleKakaoError()
                        else {
                            user?.let {
                                val kakaoData = KakaoUtil.extractData(it)
                                viewModel.requestRegistrationStatusValidation(kakaoData)
                            }?: run { handleKakaoError() }
                        }
                    }
                }
            }
   ...

안녕하세요.

어떤 문제인지 파악을 위해

(1) 앱ID를 알려주세요.


앱ID
https://developers.kakao.com/ 의 내 애플리케이션>앱 설정>요약 정보 : 기본정보에 있는 앱 ID
숫자로된 ID 입니다~
ex) 123456

(2) Android SDK Full Source & Samples 을 다운 받아 동일한 증상 발생하는지 확인해주세요.

Android | Kakao Developers 문서

앱 아이디 : 689840
(2) 에 안내주신 방향으로 동일하게 적용했는데, zflip3 에서만 메모리 릭이 지속적으로 발생하네요

@alenheo

동일하게 적용하는 것이 아니라, 다운로드한 샘플앱으로 테스트 해보셨을까요?
환경적 요인을 판단하기 위함이니 확인 부탁드려요.

네, 현재 DI 모듈로 HIlt 를 사용중이며, 안드로이드 기본 프로젝트로 생성 후 Hilt 및 카카오 SDK, LeakCanary 만 의존성 추가 후
버튼 하나 + 위 코드 온클릭리스너로 실행하였을 때도 동일하게 메모리 릭이 발생합니다.
**Dump ( Leak Canary ) **

┬───
    │ GC Root: Global variable in native code
    │
    ├─ android.os.ResultReceiver$MyResultReceiver instance
    │    Leaking: UNKNOWN
    │    Retaining 161.1 kB in 3221 objects
    │    ↓ ResultReceiver$MyResultReceiver.this$0
    │                                      ~~~~~~
    ├─ com.kakao.sdk.auth.AuthCodeClient$resultReceiver$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 160.6 kB in 3220 objects
    │    Anonymous subclass of android.os.ResultReceiver
    │    ↓ AuthCodeClient$resultReceiver$1.$callback
    │                                      ~~~~~~~~~
    ├─ com.kakao.sdk.user.UserApiClient$loginWithKakaoTalk$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 160.5 kB in 3218 objects
    │    Anonymous subclass of kotlin.jvm.internal.Lambda
    │    ↓ UserApiClient$loginWithKakaoTalk$1.$callback
    │                                         ~~~~~~~~~
    ├─ com.theangelbridge.myapplication.FirstFragment$kakao$1$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 160.4 kB in 3216 objects
    │    Anonymous subclass of kotlin.jvm.internal.Lambda
    │    ↓ FirstFragment$kakao$1$1.this$0
    │                              ~~~~~~
    ╰→ com.theangelbridge.myapplication.FirstFragment instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.theangelbridge.myapplication.FirstFragment received
    ​     Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
    ​     Retaining 160.4 kB in 3214 objects
    ​     key = b55fdfc0-376d-4216-be8a-a5efc2eedcae
    ​     watchDurationMillis = 5275
    ​     retainedDurationMillis = 275
    ​     componentContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper,
    ​     wrapping activity com.theangelbridge.myapplication.MainActivity with mDestroyed = true

네, 확인 감사합니다.

Android SDK담당자에게 확인 요청을 했습니다.
피드백이 오면 공유 드리도록 하겠습니다.

빠르고 친절한 답변 감사드립니다.

동일 환경 조성후 단계적 디버깅 및 테스트 중인데,
해당 문제가 Kakao login API 를 호출한 액티비티가 onDestroy 될 때 해당 컨텍스트를 물고 있는 Kakao AuthCodeClient 의 ResultReceiver 가 참조하고 있는 액티비티의 컨텍스트가 해제되면서 해당 Memory Leak 이 발생하는 것 같습니다.
코드 클리닝 진행 후에 Viewmodel 을 붙이고 테스트했을 때 별다른 문제가 발생하지 않았으며 해당 액티비티에서 다른 액티비티를 startActivity 하는 시점에서 Memory Leak 이 발생하는 것으로 확인됩니다.
특정 기기에서만 발생하고 대다수의 기기에서 미발생하는 것으로 보았을 때, CPU 혹은 메모리 스펙에 따라 속도 차이를 보이면서 해당 문제점이 발생하는 것으로 보입니다.

안녕하세요

안드로이드 SDK 담당자 토니입니다.

말씀하신 것처럼 SDK 내부적으로 사용되는 resultReceiver가 카카오API를 호출한 activity의 context를 참조하고 있기 떄문에 activity가 소멸되면서 메모리릭이 발생하는 것으로 보입니다.

하지만 제가 가지고 있는 ZFlip 3 디바이스에서는 동일한 이슈가 발생하지 않아서 이슈를 재현해보기 위해 몇 가지 테스트를 진행해보았는데요. 결론부터 말씀드리면 ZFlip 3 디바이스 이슈는 아닌 것으로 보이고, 앱의 정상 동작 시에 메모리릭은 발생하지 않을 것으로 보입니다.

startActivity() 등의 안드로이드 API를 통해 화면을 전환하게 되면 activity가 소멸되지는 않기 때문에 onPause() 혹은 onStop() 메서드까지만 호출되고 onDestroy()는 호출되지 않습니다. (참고: 안드로이드 공식 가이드)
따라서 일반적인 앱의 동선으로는 activity가 소멸되지 않고, 그렇기 때문에 메모리릭이 발생하지 않습니다.

간혹 디바이스의 메모리가 부족한 상황에서 loginWithKakaoTalk() 등의 메서드를 호출했을 경우에는 안드로이드 시스템에 의해 activity가 소멸될 수 있지만 이는 매우 드문 경우이고, 제보주신 내용에 따르면 이슈가 간헐적으로 발생하는 것은 아니라 계속해서 발생하는 것으로 보이기 때문에 디바이스의 메모리가 부족해서 발생하는 이슈도 아니라고 판단했습니다.

테스트를 하면서 개발자 설정의 ‘활동 유지 안함’ 옵션이 활성화되어있는 경우에는 처음에 말씀드린 이유로 인해서 이슈가 재현되는 것을 확인할 수 있었는데요, 해당 옵션 활성화를 통해 ZFlip 3 디바이스 뿐만 아니라 다른 디바이스에서도 동일하게 이슈를 재현할 수 있었습니다.

’활동 유지 안함’ 옵션은 앱 개발을 진행하면서 테스트를 목적으로 자주 사용되는 옵션이므로 테스트하신 ZFlip 3 디바이스에 해당 옵션이 활성화되어있는지 확인 부탁드리고, 해당 옵션이 활성화되어있지 않는데도 동일한 이슈가 발생하고 있다면 조금 번거로우시더라도 이슈 재현을 위해 테스트하셨던 프로젝트 파일 첨부 부탁드리겠습니다.

감사합니다.

cc. @tim.l

@tony.mb
sdk에서는 전달받은 activity context에 대한 참조를 언제까지 유지하나요?
LoginActivity에서 카카오 로그인을 하는 예제를 만들어봤는데
LoginActivity에서 로그인 성공하고

startActivity(Intent(this, SecondActivity::class.java))
finish()

코드로 다른 액티비티로 이동하면 메모리릭 발생합니다

정상 동작 시에 메모리릭은 발생하지 않을 것으로 보입니다. 라고 하셨는데
기존 액티비티를 종료하는 것은 정상 동작이 아니라는 말씀이신지 궁금합니다.

작성해주신 코드에서 새 액티비티를 호출하는 곳에서는 아직 호출한 액티비티의 라이프사이클이 유지되고 있으므로 메모리릭이 발생하지 않습니다. 아래 첨부해주신 코드에 대한 액티비티의 라이프사이클을 주석으로 참조하였는데, Kakao User SDK 의 경우 현재 ResultReceiver 에 대한 참조 Activity Context 를 WeakReference 가 아닌 StrongReference 를 통해 참조하고 있어 해당 액티비티를 종료시킬 경우 필연적으로 해당 메모리릭이 발생하는 구조로 짜여져 있었습니다. ( SDK 는 살아있으나 참조중이던 컨텍스트는 소멸 )

startActivity() // called Activity : onPause
finish() // called Activity : onDestory

하지만 이후 동일한 ( 화면전환을 위해 종료된 액티비티 ) 컨텍스트를 사용하여 KakaoSdk 를 사용하지 않으므로 GC 가 도는 시점에서 액티비티 컨텍스트 해제 후 메모리릭이 발생한 KakaoSDK 또한 사용중이지 않음을 파악하고 해제시킬 것이므로 애플리케이션 자체에 영향을 끼치지 않는 것을 확인하였습니다.

안녕하세요

답변이 많이 늦어졌네요ㅠㅠ

위에서 ‘정상 동작 시에 메모리릭은 발생하지 않을 것으로 보입니다’ 라고 답변했던 부분은 문의주신 분의 코드 로직 상 로그인을 수행하는 activity 혹은 fragment가 소멸되지 않는 것으로 보여 첨부된 코드가 제대로 동작한다면 이슈가 없을 것이라고 답변한 것이었습니다.

이와 별개로 sdk에서 activity의 참조를 유지하는 것은 gc가 돌기 전까지 유지되고 있기 때문에 내부적으로도 개선이 필요한 부분이라고 판단했고, 로그인 메서드 콜백 내에서 startActivity() 후에 곧바로 finish() 를 닫는 경우에 메모리릭이 발생하는 것 확인했습니다. 그래고 로그인 메소드 콜백이 실행되면 바로 참조를 제거할 수 있도록 수정한 2.11.1 버전 배포했습니다.

답변이 많이 늦어진 점 양해 부탁드리고 제보주셔서 감사합니다.