반응형
View's Reference는 Memory Leak의 대상일까?
이어지는 글
2023.07.21 - Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기
2023.07.25 - Memory Leak - 2. 자주 발생하는 안드로이드 메모리 누수 방지하기
배경
- 지난번까지 메모리 누수를 자주 일으키는 요소들에 대해 방지하는 방법에 대해 알아보았다. 그러던 중, View를 참조하는 변수들이 메모리 누수를 일으키는 원인으로 Leakcanary에서 자주 식별되어, View의 요소를 참조하는 것이 메모리 누수를 일으키는 원인이 될 수 있는 지에 대해 알아보고자 한다.
Fragment
에서myTextView = view.findViewById(R.id.myTextViewId)
혹은myTextView = binding.myTextViewId
처럼 initialize해준 뒤,View
가Destroy
되는 경우 해당variable
에 대해null
처리를 해주지 않으면Leak
이 실제로 발생하는 지에 대한 여부를 알아보고자 한다.
View’s Reference에 대해 Null 처리하는 예시
AOSP에서의 코드
- 실제로 AOSP(Android Open Source Project)에서의 ListFragment[Link] and PreferenceFragment[Link] 의 예시코드를 보면 ListView인 mList에 대해
mList = null
를 Destory 하는 경우에 호출해 null 처리 해주는 것을 볼 수 있다. - 그렇다면 실제로 View를 참조하는 것이 메모리 누수를 일으킬 수 있을 확률이 있어 그러는 것일까?
- 이와 같은 의문을 나만 가진게 아니였기에, StackOverflow를 통해 이러한 질문을 가진 사람을 어렵지 않게 찾아볼 수 있었고, 이를 참고했다.
Memory Leak 발생의 가정
Fragment A
가Instance field
로, 해당View
의childView
에 대한reference
가 있다고 가정한다.Fragment A
에서Fragment B
로의navigation
은특정 FragmentTransaction
을 사용한FragmentManager
를 통해 이루어지게 된다.Transaction의 Type
에 따라,Manager
는 View만 kill하고,Fragment A
의instance
는 유지할 수 있다 (lifecycle
부분에서 “fragment
는 Backstack에서 layout으로 돌아간다”를 참조)Fragment B
에서Fragment A
로 다시 이동하는 경우,Fragment A
의instance
의previous instance
가 Front로 이동하지만, 새로운 View가 Created된다.- 이때, 문제는 만약
lateinit property
에서view
를instance
로 유지하고,reference
를 삭제하지 않으면, view가 완전히 destroyed되지 않아 Memory Leak이 발생할수 있다
- 이때, 문제는 만약
→ 위와 같은 단계를 생각해보면, Fragment간 이동 중에 Memory Leak이 발생할 것이라고 예상할 수 있다.
가정에 대한 증명
사고실험
- 먼저 단계별로, 메모리가 어떻게 할당및 해제되는 지에 대해 단계적으로 살펴보자
Fragment A
를 Open할 때- 해당
Content
/root
View
와Textview
가 메모리에 할당된다.
- 해당
Fragment B
로 Naivgate할 때Fragment A
의onDestroyView()
가 호출되지만,Fragment A
의View
는 destroyed되지 않는다.- 왜냐하면
TextView
가Fragment A
에 대한Strong reference
를 유지하고 있고,Fragment A
는TextView
에 대한Strong Reference
를 유지하고 있기 때문이다 - (the
TextView
holds a strong reference to it andFragmentA
holds a strong reference toTextView
)
- 왜냐하면
Fragment B
에서Fragment A
로 Navigate Back 하는 경우View
와TextView
의 이전 할당(previous allocation)이 cleared된다.- 또한, 동시에
onCreateView()
가 호출될 때 새로운 할당(new allocation)을 받게 된다.
Fragment A
에서Back
을 클릭하는 경우- 새로운 할당이 clear 된다.
사고실험의 결론
- 위의 단계를 살펴 봤을 때 2단계에서
View
의retain memory
가 원래대로 확보되지 않아 Memory Leak이 발생하는 것을 볼 수 있다. - 반면 3단계부턴 유저가
Fragment
로 돌아오자마자 메모리가 복구되는 것을 확인할 수 있다. - 따라서,
FragmentManager
가Fragment
를 다시 가져올 때 까지 Memory Leak이 지속됨을 알 수 있다.
실제 수행을 통한 증명: Statical Analysis (Heap Memory 이용)
- 실제로
Button
이 있는Activity
와Fragments
에 대한container
인FrameLayer
가 있다고 하고 이는 다음과 같이 생성한다.Button
을 누르면Container
가Fragment A
로 바뀐다.Fragment A
에는Container
를Fragment B
로 replace하는Button
이 있다Fragment B
는Instance Field
로 Fragment에 저장하는TextView
가 있다
- 위의 Report는 가정을 한 Application을 실행해 Operation을 실행했을 때 관찰되는 Heap Dump와 이외의 정보들이다.
- 위의 리포트는 아래의 Operation을 실행하였을 때 나온 결과이다.
- Opened the app: Activity가 보인다.
- Pressed the Button in the Activity:
FragmentA
visible - Pressed the Button in FragmentA:
FragmentB
visible 되고,FragmentA
onDestroyView()
- Pressed the Button in the Activity:
FragmentA
의 2번째 instance가 Visible되고,FragmentB
onDstroyView()
(위에서 4단계로 수행했던 예시의 2번째 단계와 동일하다.Fragment B
가A 역할
을 하고,Fragment A
의2번째 instance
가B 역할
을 한다는 점을 제외하곤) - Pressed the Button in the 2nd instance FragmentA:
FragmentB
의 2번째 instance가 visible andFragmentA
의 2번째 instance가onDestroyView()
. - Pressed Back Button:
FragmentA
의 2번째 instance가 visible andFragmentB
의 2번째 instance가onDetach()
- Pressed Back Button:
FragmentB
의 1번째 instance가 visible andFragmentA
의 2번째 instance가onDetach()
- Pressed Back Button:
FragmentA
의 1번째 instance가 visible andFragmentB
의 1번째 instance가onDetach()
- Pressed Back Button:
FragmentA
의 1번째 instance가onDetach()
- Pressed Back Button: 앱 종료
Report 분석
- Report를 봤을 때,
1단계
에서 앱이 닫힐 때 까지 각각의view
와모든 view
는 앱이 닫힐 때까지 살아있다. 2단계
에서는Fragment A
의View
(e.g.FrameLayout
과child
,Button
등)이 할당되어 있으며, 예상한대로3단계
에서 모두 clear 되었다.3단계
에서는,Fragment B
의View
(e.g.FrameLayout
과 그child
,TextView
등)이 할당되어 있지만,4단계
에서는 삭제하지 않아 메모리 누수가 발생했다.- 그러나,
7단계
에서View
가 다시 created 되고 새롭게 create된 view에 할당될 때, clear된다. - 반면에
5단계
에서 생성된View
는6단계
에서 삭제돼, 메모리 누수가 발생하지 않았다. 이는,fragment
가detach
되어, fragment가 cleard up하는 것을 prevent하지 않았기 때문이다.
실제 수행의 결과를 통한 결론
- 이러한 과정을 통해 유저가
fragment
로 돌아갈 때 까지,fragment
에view
를 저장함으로써 leak이 발생한다는 것을observe
할 수 있었다. Fragment
가 다시 돌아가는 경우 (e.g.onCreatedView()
가 호출되는 등)에 leak은 다시 복구(recover)된다.- 반면에
fragment
가top
에 있고 뒤로가기만 가능한 경우에는 어떠한 leak도 일어나지 않는다.
결론
- 위와 같이 단계적인 증명과 이를 바탕으로 실제로 앱을 만들어 실행해보았을 때
View’s Reference
들은다른 Fragment
로 이동했을 때 Leak을 발생시키지만,해당 Fragment
로 돌아갔을 때 다시 Recover되므로 현재까지 이에 대해 알지 못했더라도 큰 문제가 되지 않았던 것으로 판단된다. - 따라서,
View’s Reference
의 경우 아래와 같이 유의해서 사용하면 될 듯 하다.Fragment
에서Forward transaction
이 없는 경우,View
는onDetach()
에서 clear되므로,Strong reference
로View
를 저장하는 것에 아무런 문제가 없다.- 만약,
forward Transaction
이 존재하는 경우, view의weak Reference
를 저장함으로써,onDestroyView()
에서 clear되도록 할 수 있다.- 혹은 개인적으로
Destroy
하는 경우에해당 reference
를null
처리 함으로써leak
이 발생하지 않도록 할 수 있을 것이라 생각한다.
- 혹은 개인적으로
참고
Keeping a reference to a View in a Fragment causes memory leaks?
반응형
'IT > Android' 카테고리의 다른 글
[Android/Project] 기존 앱에 Jetpack Compose 도입기: LazyColumn 활용 (0) | 2023.08.12 |
---|---|
[Android/Refactoring] Memory Leak - 2. 자주 발생하는 안드로이드 메모리 누수 방지하기 (0) | 2023.07.25 |
[Android/Refactoring] Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기 (1) | 2023.07.21 |
[Android] RecyclerView Deep Dive - 2. RecyclerView에서의 활용과 최적화 (0) | 2023.07.10 |
[Android] RecyclerView Deep Dive - 1. RecyclerView 정의와 동작원리 및 생명주기 (0) | 2023.07.05 |