본문 바로가기
학습 기록/안드로이드

프래그먼트에서의 뷰 바인딩과 메모리 누수

by aksworns22 2026. 1. 22.

안드로이드를 학습 중 만났던 여러 글과 AI 코드는 뷰 바인딩을 크게 두 가지 방식으로 사용하고 있었습니다.

  1. lateinit var를 활용하는 방식
  2. backing property를 활용하는 방식

메모리 누수 확인 방식과 LeakCanary

메모리 누수를 확인하기 위해 메인 액티비티에서 HomeFragment와 CafeFragment를 버튼으로 전환하도록 했습니다. 이때 Fragment 전환 시 백스택에 추가하도록 설정했습니다.


실행 순서는 HomeFragment로 버튼 클릭을 통해 이동 후 CafeFragment로 이동하는 방식(버튼 클릭)이었습니다.

 

MainActivity 코드

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        enableEdgeToEdge()
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        binding.homeButton.setOnClickListener {
            supportFragmentManager.beginTransaction().apply {
                replace(binding.fragmentContainerView.id, HomeFragment())
                addToBackStack(null)
                commit()
            }
        }

        binding.cafeButton.setOnClickListener {
            supportFragmentManager.beginTransaction().apply {
                replace(binding.fragmentContainerView.id, CafeFragment())
                addToBackStack(null)
                commit()
            }
        }
    }
}

 

HomeFragment 코드

package com.example.viewbindingmemoryleak

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.viewbindingmemoryleak.databinding.FragmentHomeBinding
import java.time.LocalTime

class HomeFragment: Fragment() {
    private lateinit var binding: FragmentHomeBinding
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d("Jang", "${LocalTime.now()} HomeFragment.onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("Jang", "${LocalTime.now()} HomeFragment.onDestroy")
    }
}

 

CafeFragment 코드

package com.example.viewbindingmemoryleak

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.viewbindingmemoryleak.databinding.FragmentCafeBinding
import com.example.viewbindingmemoryleak.databinding.FragmentHomeBinding
import java.time.LocalTime

class CafeFragment: Fragment() {
    private lateinit var binding: FragmentCafeBinding
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentCafeBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("Jang", "${LocalTime.now()} CafeFragment.onCreate")
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("Jang", "${LocalTime.now()} CafeFragment.onViewCreated")
    }
}

 

LeakCanary는 설치 후 추가적인 설정 없이 Logcat/앱에서 누수 탐색 진행을 확인할 수 있습니다.

 

LeakCanary

🤔 Documentation issue? Report or edit LeakCanary 🐤 LeakCanary is a memory leak detection library for Android. LeakCanary’s knowledge of the internals of the Android Framework gives it a unique ability to narrow down the cause of each leak, helping

square.github.io

lateinit var를 활용하면서 바인딩을 해제하지 않는 경우

lateinit var를 사용하면서 onDestroyView에서 바인딩 값을 null로 해주지 않으면 메모리 누수가 발생함을 확인할 수 있었습니다.

 

Logcat 결과

 

LeakCanary 분석 결과

backing property를 활용하면서 바인딩을 해제하는 경우

위 코드의 HomeFragment만 아래 코드로 바꾸었고 테스트 방식은 홈 이동 후 카페 이동으로 전과 동일했습니다.

package com.example.viewbindingmemoryleak

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.viewbindingmemoryleak.databinding.FragmentHomeBinding
import java.time.LocalTime

class HomeFragment: Fragment() {
    private var _binding: FragmentHomeBinding? = null
    private val binding: FragmentHomeBinding get() = checkNotNull(_binding) { "바인딩된 값이 없습니다" }
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        Log.d("Jang", "${LocalTime.now()} HomeFragment.onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("Jang", "${LocalTime.now()} HomeFragment.onDestroy")
    }
}

 

로그에서도 볼 수 있다시피 전에 발생했던 메모리 누수가 사라짐을 확인해볼 수 있었습니다.

결론 및 원인

프래그먼트의 생명주기가 뷰 생성/삭제와 일치하지 않는 것이 문제인 것 같습니다. 그래서 위에 소개한 두 방식 중 어떤 방식을 사용해도 문제가 없지만 onDestroyView에서 바인딩을 null로 설정해주는 것은 메모리 누수를 막기 위해 필요합니다.

 

참고한 글

 

Avoiding memory leaks when using Data Binding and View Binding

In our current project we are using a lot of Data Binding and recently we have started using View Binding as well.

proandroiddev.com