Reputation:
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
}
}
....................
Build.VERSION.SDK_INT: 25
Build.MANUFACTURER: samsung
LeakCanary version: 2.7
Stats: LruCache[maxSize=3000,hits=5750,misses=80700,hitRate=6%] RandomAccess[bytes=4089892,reads=80700,travel=60892211355,range=18101017,size=22 288421]
Heap dump reason: user request
Analysis duration: 29922 ms
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
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
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
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