Android DataBinding is leaking Memory

10,381

Solution 1

According to fragment lifecycle, onDestroyView() was called, but fragment is not fully destroyed - so onDestroy() is not called. In that case, if you don't reset binding property by hand, it references view tree (it's some kind of leak).

But if I need to do this then what is the point of binding.lifecycleOwner = viewLifecycleOwner then?

If you supply LifecycleOwner to binding object, it allows to observe all LiveData objects inside generated binding class. But it can't know about external references on binding instance outside (from any other classes of your project) - that's why it can't automatically reset them.

Solution 2

Here is the recommended way from google docs to initialize and clear the binding in Fragments:

Kotlin:

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = ResultProfileBinding.inflate(inflater, container, false)
    val view = binding.root
    return view
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

Java:

private ResultProfileBinding binding;

@Override
public View onCreateView (LayoutInflater inflater, 
                          ViewGroup container, 
                          Bundle savedInstanceState) {
    binding = ResultProfileBinding.inflate(inflater, container, false);
    View view = binding.getRoot();
    return view;
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    binding = null;
}

Also, here is a medium blog post to read to get rid of binding memory leak with Property Delegate.

Solution 3

Just call binding.unbind() in onDestroyView() method.

Share:
10,381
OhhhThatVarun
Author by

OhhhThatVarun

I post questions here.

Updated on June 06, 2022

Comments

  • OhhhThatVarun
    OhhhThatVarun about 2 years

    I'm using data-binding and I have declared a lateinit var for the binding and when I'm going to different fragment Leaky canary showing a leak.

    Fragment

    class HomeFragment : BottomNavViewHostBaseFragment() {
    
        private lateinit var viewModel: HomeViewModel
        private lateinit var binding: FragmentHomeBinding
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
            binding.lifecycleOwner = viewLifecycleOwner
            binding.viewModel = viewModel
            return binding.root
        }
    
       ...
    }
    

    Here is the Info from Leaky Carny

    androidx.constraintlayout.widget.ConstraintLayout has leaked:
    Toast$TN.mNextView
    ↳ LinearLayout.mContext
    ↳ MainActivity.navigationView
    ↳ NavigationView.listener
    ↳ BaseFragment$setNavigationDrawerItemSelectedListener$1.this$0 (anonymous implementation of com.google.android.material.navigation.NavigationView$OnNavigationItemSelectedListener) ↳ OrdersHostFragment.mFragmentManager
    ↳ FragmentManagerImpl.mActive
    ↳ HashMap.table
    ↳ array HashMap$HashMapEntry[].[0]
    ↳ HashMap$HashMapEntry.value
    ↳ HomeFragment.!(binding)!
    ↳ FragmentHomeBindingImpl.!(mboundView0)!
    ↳ ConstraintLayout
    

    How do I solve this and Should I need to do binding=null inside onDestroyView? But if I need to do this then what is the point of binding.lifecycleOwner = viewLifecycleOwner then?

    • Agent_L
      Agent_L almost 3 years
      Why are you holding the binding? What is it useful to? Genuine question, I've always used the binding as onCreateView local variable.
    • OhhhThatVarun
      OhhhThatVarun almost 3 years
      @Agent_L There could be multiple Views that I need to access outside of onCreateView to perform actions. Like Visibility of a button etc.
    • Agent_L
      Agent_L almost 3 years
      For me, the whole point of DataBinding is to never have to access Views again.
    • OhhhThatVarun
      OhhhThatVarun almost 3 years
      @Agent_L how? Do you do all the manipulations via ViewModel and Binding Adapters?
    • Agent_L
      Agent_L almost 3 years
      Yeah, that's the point. No more code in Fragment/Activity.
  • OhhhThatVarun
    OhhhThatVarun almost 5 years
    What should I do for solving it? binding = null in onDestroyView?
  • beigirad
    beigirad over 4 years
    if referencing to a view is kind of leak, for example, an instance of TextView in my fragment is leak too? so traditional usage that defined all of the view in the top of fragment/activity is a memory-leak. so how should we access to views is OK?
  • ConstOrVar
    ConstOrVar over 4 years
    @beigirad, referencing to view is not necessarily a leak. For example, if you have activity and save view in variable - it's not memory leak, GC resolves that after activity is destoryed. If you have fragment, you can use view only between onCreateView and onDestroyView calls. If you can access view, after onDestroyView was called - it's problem. That's also actual for binding's instances.
  • Mohsen Einhesari
    Mohsen Einhesari over 4 years
    @VarunRaj did you solve your problem? by making binding = null in onDestroyView ? I only found this solution working but I don't think it's a good approach.
  • ConstOrVar
    ConstOrVar over 4 years
    @MohsenEinhesari, if you save binding in some variable in fragment, it's absolutely normal to null it by hand (as you need to do for each view stored in variable), because lifetime of fragment's view is less than lifetime of fragment.
  • Mohsen Einhesari
    Mohsen Einhesari over 4 years
    @ConstOrVar I think in most cases we should store binding in a variable, right? and another thing, what does binding.unbind do? I thought it should avoid memory leak too ( like binding = null).
  • ConstOrVar
    ConstOrVar over 4 years
    @MohsenEinhesari, store binding in variables is not always required - so it depends on screen's logic. unbind() according to documentation simply removes listeners, but still have view's cached.
  • OhhhThatVarun
    OhhhThatVarun over 4 years
    @MohsenEinhesari yeah it kinda did solve my problem. Then what should I do then?
  • Mohsen Einhesari
    Mohsen Einhesari over 4 years
    @VarunRaj nothing! I was looking for a better solution but after ConstOrVar comments I think it's good enough.
  • Daniel Wilson
    Daniel Wilson almost 4 years
    Note that link refers to ViewBinding, but I assume the concept is the same with DataBinding even though I can't find a doc that says so and the samples don't nullify view references either.
  • Aorlinn
    Aorlinn almost 3 years
    I cannot see any unbind method in my binding.
  • Aorlinn
    Aorlinn almost 3 years
    sorry my fault, I was using ViewBinding instead of DataBinding
  • hsm59
    hsm59 almost 3 years
    Can we use WeakReference<FragmentClassDataBinding> for this, and not worry about the unbinding / setting binding = null in onDestroyView() ?
  • hsm59
    hsm59 almost 3 years
    Can we use WeakReference<FragmentClassDataBinding> for this, and not worry about the unbinding / setting binding = null in onDestroyView() ? ConstOrVar, @Mohsen Einhesari