user15460872
user15460872

Reputation:

How to fix memory leak issue in Frame Layout?

I am using leakcanary in my android app and it detects FrameLayout Leaked. But I can't find or fix this issue,

How can I fix this leak? Ref my leak canary report. FrameLayout Leaked

 ┬───
    │ GC Root: System class
    │
    ├─ leakcanary.internal.InternalLeakCanary class
    │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
    │    ↓ static InternalLeakCanary.resumedActivity
    ├─ com.android.zigmaster.MainActivity instance
    │    Leaking: NO (MapViewFragment↓ is not leaking and Activity#mDestroyed
    │    is false)
    │    mApplication instance of com.android.zigmaster.MyApplication
    │    mBase instance of androidx.appcompat.view.ContextThemeWrapper
    │    ↓ ComponentActivity.mActivityResultRegistry
    ├─ androidx.activity.ComponentActivity$2 instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    Anonymous subclass of androidx.activity.result.ActivityResultRegistry
    │    this$0 instance of com.android.zigmaster.MainActivity with mDestroyed =
    │    false
    │    ↓ ActivityResultRegistry.mKeyToCallback
    ├─ java.util.HashMap instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ HashMap.table
    ├─ java.util.HashMap$HashMapEntry[] array
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ HashMap$HashMapEntry[].[4]
    ├─ java.util.HashMap$HashMapEntry instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ HashMap$HashMapEntry.value
    ├─ androidx.activity.result.ActivityResultRegistry$CallbackAndContract instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ ActivityResultRegistry$CallbackAndContract.mCallback
    ├─ androidx.fragment.app.FragmentManager$10 instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    Anonymous class implementing androidx.activity.result.
    │    ActivityResultCallback
    │    ↓ FragmentManager$10.this$0
    ├─ androidx.fragment.app.FragmentManagerImpl instance
    │    Leaking: NO (MapViewFragment↓ is not leaking)
    │    ↓ FragmentManager.mParent
    ├─ com.android.zigmaster.ui.trips.FragmentTripPlanner instance
    │    Leaking: NO (Fragment#mFragmentManager is not null)
    │    ↓ FragmentTripPlanner.mMap
    │                          ~~~~
    ├─ com.google.android.gms.maps.GoogleMap instance
    │    Leaking: UNKNOWN
    │    Retaining 765.4 kB in 10828 objects
    │    ↓ GoogleMap.zzg
    │                ~~~
    ├─ com.google.android.gms.maps.internal.zzg instance
    │    Leaking: UNKNOWN
    │    Retaining 142 B in 2 objects
    │    ↓ zza.zza
    │          ~~~
    ├─ com.google.maps.api.android.lib6.impl.bn instance
    │    Leaking: UNKNOWN
    │    Retaining 765.0 kB in 10821 objects
    │    A instance of com.android.zigmaster.MyApplication
    │    ↓ bn.w
    │         ~
    ├─ android.widget.FrameLayout instance
    │    Leaking: UNKNOWN
    │    Retaining 2.9 kB in 65 objects
    │    View not part of a window view hierarchy
    │    View.mAttachInfo is null (view detached)
    │    View.mWindowAttachCount = 1
    │    mContext instance of com.android.zigmaster.MyApplication
    │    ↓ View.mParent
    │           ~~~~~~~
    ╰→ android.widget.FrameLayout instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.google.android.
    ​     gms.maps.SupportMapFragment received Fragment#onDestroyView() callback
    ​     (references to its views should be cleared to prevent leaks))
    ​     Retaining 1.9 kB in 48 objects
    ​     key = 265fafb3-b2a3-4462-a4e7-d5cc2afbc6fe
    ​     watchDurationMillis = 57439
    ​     retainedDurationMillis = 52422
    ​     View not part of a window view hierarchy
    ​     View.mAttachInfo is null (view detached)
    ​     View.mWindowAttachCount = 1
    ​     mContext instance of com.android.zigmaster.MainActivity with
    ​     mDestroyed = false
    
   

Here is my xml code: XML:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.trips.FragmentTripPlanner">


    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Here is my fragment Fragment:

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.gms.maps.CameraUpdate
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng



class MapViewFragment : Fragment() {

    private lateinit var mMap: GoogleMap
    private var userLatitude     = 38.2500486
    private var userLongitude    = -85.7647484
    private lateinit var mapFragment : SupportMapFragment

    private var binding : MapViewFragmentBinding ?=null

    private fun zoomingLocation(): CameraUpdate {
        return CameraUpdateFactory.newLatLngZoom(LatLng(userLatitude, userLongitude), 14f)
    }

    private fun configActivityMaps(googleMap: GoogleMap): GoogleMap {
        // set map type
        googleMap.mapType = GoogleMap.MAP_TYPE_NORMAL
        // Enable / Disable zooming controls
        googleMap.uiSettings.isZoomControlsEnabled = false

        // Enable / Disable Compass icon
        googleMap.uiSettings.isCompassEnabled = true
        // Enable / Disable Rotate gesture
        googleMap.uiSettings.isRotateGesturesEnabled = true
        // Enable / Disable zooming functionality
        googleMap.uiSettings.isZoomGesturesEnabled = true

        googleMap.uiSettings.isScrollGesturesEnabled = true
        googleMap.uiSettings.isMapToolbarEnabled = true

        return googleMap
    }

    
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {
        binding = MapViewFragmentBinding.inflate(inflater)

        mapFragment = (childFragmentManager.findFragmentById(R.map.id) as SupportMapFragment?)!!
        mapFragment.getMapAsync { googleMap ->
            mMap = configActivityMaps(googleMap)
            mMap.moveCamera(zoomingLocation())
        }

        return binding!!.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        
        mapFragment =null
        binding=null
    }
}

....................

Tried: Same issue

import androidx.fragment.app.Fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions

class MapsFragment : Fragment() {

    private val callback = OnMapReadyCallback { googleMap ->
        val sydney = LatLng(-34.0, 151.0)
        googleMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        googleMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
    }

    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_maps, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
        mapFragment?.getMapAsync(callback)
    }
}

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.pref.MapsFragment" />

Upvotes: 5

Views: 1768

Answers (3)

Andrii Omelchenko
Andrii Omelchenko

Reputation: 13343

Try to use MapView instead of MapFragment/SupportMapFragment. MapView extends FrameLayout, and oriented for cases when, not only map, but other controls needs to be shown on device screen, and designed for modern platforms and may be implemented in other way.

Also, if you need just show the map for user, you can try LiteMode or Static Maps API.

Upvotes: 0

Pavlo Ostasha
Pavlo Ostasha

Reputation: 16699

There are certain situations when memory leaks are inevitable - for example, in this case with GoogleMaps - you will always have a memory leak from GoogleMaps fragment - it is just how it works. It is much better than it was before, though - before 2013 Google Maps leaked the entire map-cache and it was horrible.

Either way, it is a known issue and it is still not fixed. I would suggest not to bother if the leaks happening inside Google-provided libraries. Usually, they all are known, and if they are severe - fixed or if they are light - happily forgotten about.

Upvotes: 0

Martin Zeitler
Martin Zeitler

Reputation: 76699

At first, remove @SuppressLint("RestrictedApi") and fix what you are hiding there.

Then try to make private lateinit var mMap: GoogleMap a local variable ...
keeping not required, because one will get the instance OnMapReadyCallback.

And maybe try calling to super.onDestroyView() last, because that FrameLayout is neither part of the hierarchy, nor attached... and the attach count is 1, so it must have been attached previously. There is a good chance that you try to dispose what was already disposed by super.onDestroyView() and that the whole override might be meaningless.

Guess one would have to see what this android.widget.FrameLayout even is (later API feature a layout inspector - nevertheless on API 25, it's still possible to dump the rendered layout to file).

Upvotes: 1

Related Questions