2

When I open my fragment that used data binding, memory leak occur as shown in below. Is there any appropriate answer to solve this?

I used

  • Data binding
  • Navigation
  • Lifecycle
  • Material ui
┬───
│ GC Root: System class
│
├─ androidx.databinding.ViewDataBinding class
│    Leaking: NO (a class is never leaking)
│    ↓ static ViewDataBinding.sReferenceQueue
│                             ~~~~~~~~~~~~~~~
├─ java.lang.ref.ReferenceQueue instance
│    Leaking: UNKNOWN
│    ↓ ReferenceQueue.head
│                     ~~~~
├─ androidx.databinding.ViewDataBinding$WeakListener instance
│    Leaking: UNKNOWN
│    ↓ ViewDataBinding$WeakListener.mObservable
│                                   ~~~~~~~~~~~
├─ androidx.databinding.ViewDataBinding$LiveDataListener instance
│    Leaking: UNKNOWN
│    ↓ ViewDataBinding$LiveDataListener.mLifecycleOwner
│                                       ~~~~~~~~~~~~~~~
╰→ com.norm.news.ui.source.NewsSourceFragment instance
​     Leaking: YES (ObjectWatcher was watching this because com.norm.news.ui.source.NewsSourceFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
​     key = 55b68d7d-56bb-46ff-ad17-e29f01b6a808
​     watchDurationMillis = 5207
​     retainedDurationMillis = 139
​     key = 2fc4bd04-641e-4bea-8e13-71650f6e2a25

METADATA

Build.VERSION.SDK_INT: 24
Build.MANUFACTURER: samsung
LeakCanary version: 2.1
App process name: com.norm.news.debug
Analysis duration: 12283 ms

Here is a snippet code in my fragment.

This code is referenced from Android Kotlin Fundamentals 05.3: Data binding with ViewModel and LiveData

override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding: FragmentNewsSourceBinding = DataBindingUtil.inflate(
            inflater,
            R.layout.fragment_news_source,
            container,
            false
        )
        // Specify the current activity as the lifecycle owner of the binding.
        // This is necessary so that the binding can observe LiveData updates.
        binding.lifecycleOwner = this

        val application = requireNotNull(this.activity).application
        val viewModelFactory = NewsSourceViewModelFactory(application)
        val newsSourceViewModel =
            ViewModelProviders.of(this, viewModelFactory).get(NewsSourceViewModel::class.java)
        binding.newsSourceViewModel = newsSourceViewModel

        binding.rvSourceLists.adapter = NewsSourceAdapter(NewsSourceAdapter.OnClickListener {
            newsSourceViewModel.displayNewsSourceDetails(it)
        })

        newsSourceViewModel.navigateToSelectedItem.observe(viewLifecycleOwner, Observer {
            if (it != null) {
                this.findNavController()
                    .navigate(NewsSourceFragmentDirections.actionNewsSourceFragmentToNewsFragment(it.id))
                newsSourceViewModel.displayNewsSourceDetailsComplete()
            }
        })

        return binding.root
    }
mixin27
  • 159
  • 8

2 Answers2

4

This looks like a bug in the data binding library, based on reading the leaktrace and the sources. I'm not sure whats triggering it.

There's a reference queue, WeakListener instances are added to it when the instance they weak ref is weakly reachable. So we know that this is the state that listener is in in this case. WeakRefs enqueued will stay there until the queue is processed by consuming code (here the data binding lib), which normally isnt an issue since the ref is null. However here the weakref has a mObservable fields which endsup leaking a lifecycle owner which is a destroyed fragment. This will stop leaking when the ref queue is processed next, though it's unclear when that happens.

So this looks like a design flaw / bug in how the data binding lib handles weak refs.

You should probably file a bug to that project with this context an extra details like.the lib version number. Providing the heap dump (easy to share from leakcanary) would help too.

Pierre-Yves Ricau
  • 8,209
  • 2
  • 27
  • 43
2

I was getting the same leak previously. I resolved it by setting the DataBinding parameters to null before setting the DataBinding to null in onDestroyView callback of Fragment.

Currently from your code, you have the following parameters in your DataBinding i.e. lifecycleOwner, newsSourceViewModel, rvSourceLists. Make your binding as a global variable and set these parameters later to null in your onDestroyView lifecycle callback of Fragment.

Refer to the below code:

override fun onDestroyView() {
    super.onDestroyView()
    binding.lifecycleOwner = null
    binding.newsSourceViewModel = null
    binding.rvSourceLists = null
}

Moreover, this is the StackOverflow post that shows how to deal with such leaks with Fragments.

Nikhil Jain
  • 649
  • 5
  • 11