-
Notifications
You must be signed in to change notification settings - Fork 0
TroubleShooting โ Memory Leak
KakaoTalk_20231207_105337219.mp4
๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฆญ์ด ๋ฐ์ํ๋ค.
- ํ ํ๋ฉด์์ ํ๋จ ๋ค๋น๊ฒ์ด์ ๋ฐ๋ก ์ฌ๋ฌ ๋ฒ ํ๋ฉด์ ์ด๋ํ ๊ฒฝ์ฐ, Java Heap๊ณผ Native Heap์ด GC ์ฝ ์ดํ์๋ ํฌ๊ธฐ๊ฐ ์ค์ง ์๊ณ ๊ณ์ ๋์ด๋๋ค.
- ๋ก๊ทธ์ธ/ํ์๊ฐ์ ํ๋ฉด์์ ๋ก๊ทธ์ธ ์ฑ๊ณต ๋๋ ์คํจ ์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๋ค.
- ์น๋ทฐ๋ฅผ ์ฌ์ฉํ๋ ๋ก์ง์์ ์น๋ทฐ๊ฐ ๋ซํ ๊ฒฝ์ฐ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๋ค.
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
}
๋ฉํ ๋๊ณผ์ ๋ํ๋ฅผ ํตํด LeakCanary๋ฅผ ํตํด ์ด๋์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋์ง ์ถ์ ํ๋ ๋ฐฉ๋ฒ์ ์ฑํํ๋ค.
LeakCanary๋ ์๋๋ก์ด๋ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์๊ณ Activity, Fragment, View, ViewModel์ด ํ๊ดด๋์ ๋ ์๋์ผ๋ก ๊ฐ์งํ์ฌ ObjectWatcher๋ก ์ ๋ฌํ๊ฒ ๋๋ค.
ํ๊ดด ํ 5์ด ์ด๋ด์ GC๊ฐ ๋์ง ์์ผ๋ฉด retained ๋ ๊ฒ์ผ๋ก ํ๋จํ์ฌ ์ด๋ฅผ Logcat์ ๊ธฐ๋กํ๋ค.
retained ๊ฐ์ฒด์๊ฐ ์๊ณ๊ฐ์ ๋์ด๊ฐ๋ฉด LeakCanary๋ Java ํ ๋ฉ๋ชจ๋ฆฌ ์์ญ์ ์๋๋ก์ด๋ ํ์ผ ์์คํ ์ .hprof ํ์ผ๋ก ๋คํํ๋ค.
ํ์ ๋คํํ๋ฉด ์งง์ ์๊ฐ๋์ ์ฑ์ด ์ ์ง๋๋ฉฐ, ์ดํ ํ ๋ฉ๋ชจ๋ฆฌ ๋ถ์์ ํตํด retained ๊ฐ์ฒด๋ค์ ๊ฒ์ฌํ๊ณ ๋ถ์ํ์ฌ ๋ณด์ฌ์ค๋ค.
์ฌ์ฉ์๊ฐ ๊ฒฝํํ๋ ์ฒซ ๋์ ๋ฐ์ ํ๋ฉด์ ๋ก๊ทธ์ธ ๋ฐ ํ์๊ฐ์ ํ๋ฉด์ด์๋ค.
๋ก๊ทธ์ธ ํ๋ฉด๊ณผ ํ์๊ฐ์ ํ๋ฉด์์๋ API ์๋ต์๊ฐ ๋์ ์ํ์ผ๋ก ๋์๊ฐ๋ ๋ก๋ฉ ์์ด์ฝ์ ์ฌ์ฉํ๋๋ฐ,
ํด๋น ์์ด์ฝ์ ๊ทธ๋ฆฌ๋ ๋ถ๋ถ์์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๋ค.
private lateinit var circularProgressIndicator: IndeterminateDrawable<CircularProgressIndicatorSpec>
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
with(binding) {
viewModel = loginViewModel
}
circularProgressIndicator = getCircularProgressIndicatorDrawable(this@LoginActivity)
initListener()
collectEvent()
setContentView(binding.root)
}
...
override fun onDestroy() {
super.onDestroy()
circularProgressIndicator.stop()
}
๋์ ๋ฐ์ ์์ธ์ ์ ๋๋ฉ์ด์ ์ด ์ข ๋ฃ๋์ง ์์๊ธฐ ๋๋ฌธ์ด๋ค.
ํด๋น ์ ๋๋ฉ์ด์ ์ด ๋ค์ด์๋ ์์ด์ฝ์ ๋ง๋ค์ด ์ฌ์ฉํ ํ null๋ก ์ฐธ์กฐ๋ฅผ ์์ด์ง๋ง,
์ค์ ๋ก ์ ๋๋ฉ์ด์ ์ ๋ด์ ๊ฐ์ฒด๋ ๋ฉ์ถ์ง ์๊ณ ๊ณ์ ๋์๊ฐ๊ฒ ๋๋ค.
์ด ๋ ์ ๋๋ฉ์ด์ ์ด ์คํ๋๊ธฐ ์ํด context๋ฅผ ์ฐธ์กฐํ๋๋ฐ ์ด ๊ณผ์ ์์ ๋์๊ฐ ๋ฐ์ํ ๊ฒ์ด์๋ค.
circularProgressIndicator์ ์ ๋๋ฉ์ด์ ์ onDestory์์ ๋ฉ์ถ๋ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ์ถ๊ฐํ์๋ค.
์ ์ฝ๋๋ก ๋ณ๊ฒฝ ํ ๋ค์ ํ ์คํธํ ๊ฒฐ๊ณผ ๋ ์ด์ ๋ก๊ทธ์ธ ํ๋ฉด๊ณผ ํ์๊ฐ์ ํ๋ฉด์์๋ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ์ง ์์๋ค.
๋ค์์ผ๋ก ๋ฐ์ํ ๋ฉ๋ชจ๋ฆฌ ๋์๋ ์ํ ์ถ๊ฐ ๋์๋ง ํ๋ฉด์ด๋ค.
ํด๋น Fragment๋ WebView๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ฌ๊ธฐ์ ๋์๊ฐ ๋ฐ์ํ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLinkHelperWebViewBinding.inflate(layoutInflater)
setContentView(binding.root)
webView = WebView(this)
webView.loadUrl("https://info-kr.priceguard.app/")
binding.wbLinkHelper.addView(webView)
}
override fun onDestroy() {
super.onDestroy()
binding.wbLinkHelper.removeAllViews()
webView.clearHistory()
webView.clearCache(true)
webView.loadUrl("about:blank")
webView.pauseTimers()
webView.destroy()
}
Activity๊ฐ ์ ๊ฑฐ๋ ๋ ๋๋ฉด WebView๋ ๊ณ์ํด์ Activity์ ๋ํ ์ฐธ์กฐ๋ฅผ ์ ์งํ๋ฏ๋ก ํ๋์ด ๋ฉ๋ชจ๋ฆฌ์์ ์ ๊ฑฐ๋์ง ์๋๋ค.
์ด๋ ๊ฒ ๋ ๊ฒฝ์ฐ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค.
์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์น๋ทฐ๋ฅผ xml์์ ๋ง๋ค๋๋ก ํ์ง ์๊ณ ์ฝ๋์์ ์์ฑํ์ฌ ์ด๋ฅผ ์ฐ๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ํํ๋ค.
https://issuetracker.google.com/issues/273117792#comment4
์ค์ ๋ก ๋์๋ ์ด๋์ ๋ ์กํ์ง๋ง, ์ฌ์ ํ LeakCanary๊ฐ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๊ฒฝ๊ณ ํ๊ณ ์๋ค.
์ด์ ๋ฅผ ์ฐพ๋ ๋์ค ํ์ฌ ์ผ์ฑ ๊ธฐ๊ธฐ ์ค ์๋๋ก์ด๋ 13๋ฒ์ ์ OneUI 5๋ฒ์ ์์ LeakCanary ์ฌ์ฉ ์ ๋์๊ฐ ๋ฐ์ํ๋ค๋ ์ด์๋ฅผ ๋ฐ๊ฒฌํ์๋ค.
ํ ์ํฉ๊ณผ ๊ฐ์ ์ํฉ์ด๋ผ ์๊ฐํ๊ณ , ์ฌ๋ฌ ๋ฒ์ ํ ์คํธ ๊ฒฐ๊ณผ ํ๋กํ์ผ๋ง์์ ์ฌ๊ฐํ ์คํ์ดํฌ๋ ์ ์ง์ ์ธ ํ ์์ญ ์ฆ๊ฐ๋ ์์๋ค.
๋ฐ๋ผ์ ํ๊ณผ ์๋ ผ์ ํตํด ํด๋น ์ด์์ ๋ํ ํ์์ ์ฌ๊ธฐ์ ์ค๋จํ์๋ค.
์ํ ๋ชฉ๋ก ํ๋ฉด ํ๋๊ทธ๋จผํธ์์๋ ๋์๊ฐ ๋ฐ์ํ๊ณ ์์๋ค.
ํด๋น ํ๋ฉด์์ RecyclerView๋ฅผ ๊ตฌํํ๊ธฐ ์ํด adapter๋ฅผ ์ค์ ํ๊ณ ์์๋๋ฐ,
RecyclerView ์ด๋ํฐ๋ฅผ ์ค์ ํ๋ฉด ์๋ฐฉํฅ ํต์ ๋ฐฉ๋ฒ์ด ์ค์ ๋๋ค.
ํด๋น ์ฐ๊ฒฐ์ ์ ๋ฆฌํ์ง ์์ผ๋ฉด ๊ฐ๋น์ง ์์ง๊ธฐ๊ฐ RecyclerView๋ฅผ ์ง์ธ ์ ์์ผ๋ฏ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๊ฒ ๋๋ ๊ฒ์ด๋ค.
override fun onDestroyView() {
super.onDestroyView()
binding.rvRecommendedProduct.adapter = null
}
๋ค์๊ณผ ๊ฐ์ด ์ด๋ํฐ๋ฅผ ๋ชจ๋ onDestoryView์์ null๋ก ์ด๊ธฐํํ๋ค.
์ด์ ๋ ์ด์ Fragment ๋ผ์ดํ์ฌ์ดํด ์ดํ ๋ฉ๋ชจ๋ฆฌ์ ๋จ์ง ์์๋ ๋๋ค.
๋ฌธ์ ํด๊ฒฐ ์ดํ ๋ ์ด์ Fragment๋ก ์ธํ ๋์๋ ์์ด์ก๋ค.
BottomNavigatorView์์ Navigate ์ฝ์ ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถํ๋ ์๋๋ฆฌ์ค์์
์ด๋ํฐ์ ๋ฆฌ์ค๋ ์ด๊ธฐํ๋ฅผ ํ์ง ์์ ๊ฒฝ์ฐ Java Heap 63MB, Native Heap 104.6MB์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ต์ข ์ ์ผ๋ก ํ ๋น๋์ง๋ง,
์ด๋ํฐ์ ๋ฆฌ์ค๋ ์ด๊ธฐํ๋ฅผ ํ ๊ฒฝ์ฐ Java Heap์ 15 ~ 25MB, Native Heap 28 ~ 33MB ์ฌ์ด์์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ ์ง๋๋ค.
++์ถ๊ฐ
Fragment์์ Adapter๋ฅผ onViewCreated ํ ๋ ๋ง๋ค ์์ฑํ๊ณ ์๋ค์. ์๊น ๋ฉ๋ชจ๋ฆฌ ๋ฆญ์ ์ฌ์ฌ์ฉํ๋ ํํ๋ก adapter๋ 1ํ ๋ง๋ค๊ณ , ์ด๊ฑธ view์ ์ ์ฉ๋ง ํ๋ค๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๊ฐ ์กฐ๊ธ ๋ ๋นจ๋ฆฌ ํด๊ฒฐํ ์ ์์ง ์์๊น ์๊ฐ๋๋ค์. Fragment view lifecycle ์ฐธ๊ณ .(๊ตฌ๊ธ binding์ nullable๋ก ํ๋ ์ด์ ์ ๊ฐ๊ธดํฉ๋๋ค.)
๋ค์๊ณผ ๊ฐ์ ํ์ ์์ ํผ๋๋ฐฑ์ด ์์๋ค.
๋ง์ํ์ ๋ด์ฉ๋๋ก adapter๋ฅผ ์บ์ฑํ์ฌ ์คํ ์ค ํ ๋ฒ๋ง ์์ฑํ๊ณ ์ฌ์ฌ์ฉํ๋๋ก ํ์ผ๋ฉด ํจ์ฌ ๋ ๊น๋ํ๊ฒ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ๋์์ ๊ฒ ๊ฐ๋ค.
์ถํ ๋ฆฌํฉํ ๋ง ๊ณผ์ ์ ๋ฐ์ํด์ผ๊ฒ ๋ค.
- Trouble Shooting - Navigation Error
- Trouble Shooting - Memory Leak
- ์๋๋ก์ด๋ ํ ๋ง ์ ์ฉ
- ๊ทธ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ 4. ๊ทธ๋ํ ํ ๋ง ์ ์ฉํ๊ธฐ
- ํ ๋ง ๋ณ๊ฒฝ(compose vs xml)
- CoroutineScope launch
- ๊ทธ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ 5. ํฐ์น, ์ํธ์์ฉ ์ฒ๋ฆฌํ๊ธฐ
- ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ๋์ ์ ๊ฒ
- ์ปค์คํ ๋ทฐ - ๋ฐ์ ์ฌ๋ผ์ด๋
- TroubleShooting - RecyclerView์ View๊ฐ ์คํฌ๋กค ๋ฐ์ผ๋ก ๋ฒ์ด๋ฌ๋ค ์ค๋ฉด ๊ฐ์ด ๋ฐ๋๋ ํ์
- ๊ทธ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ 0. ๊ธฐํ, ์๋ฃ๊ตฌ์กฐ
- ๊ทธ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ 1. ์ปค์คํ ๋ทฐ ๋ง๋ค๊ธฐ
- ๊ทธ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ 2. ๊ทธ๋ํ ์ถ ๊ทธ๋ฆฌ๊ธฐ
- ๊ทธ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ 3. ๊ทธ๋ํ ๋ฐ์ดํฐ ๊ทธ๋ฆฌ๊ธฐ
- ๊ทธ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ 6. Maven Central์ ๋ฐฐํฌํ๊ธฐ
- Deeplink ์ง์ ๋ฐ Deeplink ์ด๋์ฉ ์นํ์ด์ง ๊ตฌ์ถํ๊ธฐ