0

I am using fused location api to find the current location in the fragment, sometimes getting a memory leak

How to fix this issue?

com.android.zigmaster.ui.home.FragmentSearch instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.android.zigmaster.ui.home.FragmentSearch received
    ​     Fragment#onDestroy() callback and Fragment#mFragmentManager is null)



  ====================================
    HEAP ANALYSIS RESULT
    ====================================
    2 APPLICATION LEAKS
    
    References underlined with "~~~" are likely causes.
    Learn more at https://squ.re/leaks.
    
    4982 bytes retained by leaking objects
    Signature: e3580ed78ace0bf62b73fb0e3e2c66f15be575a
    ┬───
    │ GC Root: Global variable in native code
    │
    ├─ com.google.android.gms.location.zzam instance
    │    Leaking: UNKNOWN
    │    Retaining 756 B in 13 objects
    │    ↓ zzam.zza
    │           ~~~
    ├─ com.google.android.gms.location.zzx instance
    │    Leaking: UNKNOWN
    │    Retaining 153 B in 7 objects
    │    ↓ zzx.zzc
    │          ~~~
    ├─ com.android.zigmaster.ui.home.HomeFragment$proceedAfterPermissionLocation$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 12 B in 1 objects
    │    Anonymous subclass of com.google.android.gms.location.LocationCallback
    │    ↓ HomeFragment$proceedAfterPermissionLocation$1.this$0
    │                                                    ~~~~~~
    ╰→ com.android.zigmaster.ui.home.HomeFragment instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.android.zigmaster.ui.home.HomeFragment received
    ​     Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
    ​     Retaining 5.0 kB in 151 objects
    ​     key = f6ba5269-d905-4614-ac2b-4ff353b6f154
    ​     watchDurationMillis = 5518
    ​     retainedDurationMillis = 518
    
    455760 bytes retained by leaking objects
    Signature: 9dd9e366fbcb994c88d457524161a4dca4407a85
    ┬───
    │ GC Root: Global variable in native code
    │
    ├─ com.google.android.gms.location.zzam instance
    │    Leaking: UNKNOWN
    │    Retaining 456.5 kB in 7825 objects
    │    ↓ zzam.zza
    │           ~~~
    ├─ com.google.android.gms.location.zzx instance
    │    Leaking: UNKNOWN
    │    Retaining 455.9 kB in 7819 objects
    │    ↓ zzx.zzc
    │          ~~~
    ├─ com.android.zigmaster.ui.home.FragmentSearch$proceedAfterPermissionLocation$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 455.8 kB in 7813 objects
    │    Anonymous subclass of com.google.android.gms.location.LocationCallback
    │    ↓ FragmentSearch$proceedAfterPermissionLocation$1.this$0
    │                                                      ~~~~~~
    ╰→ com.android.zigmaster.ui.home.FragmentSearch instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.android.zigmaster.ui.home.FragmentSearch received
    ​     Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
    ​     Retaining 455.8 kB in 7812 objects
    ​     key = 48799cd7-6335-4938-a6b2-71fde55e3507
    ​     watchDurationMillis = 12318
    ​     retainedDurationMillis = 7276
    ====================================
    0 LIBRARY LEAKS
    
    A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
    See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
    ====================================
    0 UNREACHABLE OBJECTS
    
    An unreachable object is still in memory but LeakCanary could not find a strong reference path
    from GC roots.
    ====================================
    METADATA
    
    Please include this in bug reports and Stack Overflow questions.
    
    Build.VERSION.SDK_INT: 29
    Build.MANUFACTURER: samsung
    LeakCanary version: 2.7
    App process name: com.android.zigmaster
    Stats: LruCache[maxSize=3000,hits=3853,misses=55804,hitRate=6%]
    RandomAccess[bytes=2861728,reads=55804,travel=19994971106,range=16391680,size=20725210]
    Heap dump reason: 10 retained objects, app is visible
    Analysis duration: 34210 ms
    Heap dump file path: /data/user/0/com.android.zigmaster/cache/leakcanary/2021-04-27_12-22-47_274.hprof
    Heap dump timestamp: 1619540608205
    Heap dump duration: 6203 ms
    ====================================

Here is my fragment code:

package com.android.zigmaster.ui.home



class FragmentSearch : Fragment() {
    private var binding : FragmentSearchBinding ?=null

    private lateinit var locationCallback: LocationCallback
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    val locationRequestApi = LocationRequest.create()
    var gpsLatitude: String = "0.0"
    var gpsLongitute: String = "0.0"



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

        binding!!.featureCurrentLocation.setOnClickListener {
            val lm = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
            if (LocationManagerCompat.isLocationEnabled(lm)) {
                // check permission first
                if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                    // request the permission
                    requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001)
                } else {
                    proceedAfterPermissionLocation()  // has the permission.
                }
            }
            else {

                // enable GPS
                try{
                    //https://stackoverflow.com/questions/25175522/how-to-enable-location-access-programmatically-in-android
                    val locationRequest = LocationRequest.create()
                        .setInterval(30000)
                        .setFastestInterval(15000)
                        .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                    val builder = LocationSettingsRequest.Builder()
                        .addLocationRequest(locationRequest)
                    LocationServices
                        .getSettingsClient(requireContext())
                        .checkLocationSettings(builder.build())
                        .addOnSuccessListener(requireActivity()) { response: LocationSettingsResponse? -> }
                        .addOnFailureListener(requireActivity()) { ex ->
                            if (ex is ResolvableApiException) {
                                // Location settings are NOT satisfied,  but this can be fixed  by showing the user a dialog.
                                try {
                                    // Show the dialog by calling startResolutionForResult(),  and check the result in onActivityResult().
                                    val resolvable = ex as ResolvableApiException
                                    resolvable.startResolutionForResult(requireActivity(), 1002)
                                } catch (sendEx: SendIntentException) {
                                    // Ignore the error.
                                }
                            }
                        }
                }
                catch (e: Exception){
                    Log.d("tag06", "setting page catch " + e.message)
                }
            }

        }


        //mView =binding!!.root
        return binding!!.root
    }


    private fun proceedAfterPermissionLocation() {
        //.......................................start location callback
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                locationResult ?: return
                for (location in locationResult.locations) {
                    val currentLocation = locationResult.lastLocation
                    gpsLatitude = currentLocation.latitude.toString()
                    gpsLongitute = currentLocation.longitude.toString()
                    Log.d("danger04", "..............$gpsLatitude, $gpsLongitute")

                    try{
                        fusedLocationClient.removeLocationUpdates(locationCallback)
                    }catch (e: Exception){ }
                }
            }
        }
        locationRequestApi.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        locationRequestApi.interval = 10000
        locationRequestApi.fastestInterval = 5000
        //mLocationRequest!!.smallestDisplacement = 10f // 170 m = 0.1 mile => get accuracy whil travel
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext().applicationContext)
        if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            fusedLocationClient.requestLocationUpdates(locationRequestApi, locationCallback, null)
        }
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
        if (1002 == requestCode) {
            if (Activity.RESULT_OK == resultCode) {
                //user clicked OK, you can startUpdatingLocation(...);
                proceedAfterPermissionLocation()
            } else {
                //user clicked cancel: informUserImportanceOfLocationAndPresentRequestAgain();
            }
        }
    }
    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<String>, grantResults: IntArray) {
        Log.d("calendar", "...........onRequestPermissionsResult code : $requestCode")
        when (requestCode) {

            1001 -> {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission was granted.
                    proceedAfterPermissionLocation() // permission was granted.
                    Log.d("location", "...........onRequestPermissionsResult : granted")
                } else {
                    // permission denied.
                    Log.d("location", "...........onRequestPermissionsResult : denied")
                }
                return
            }

        }
    }



    override fun onDestroyView() {
        super.onDestroyView()
        //.......................................stop location
        try{
            fusedLocationClient.removeLocationUpdates(locationCallback)
        }catch (e: Exception){ }

        binding=null
    }
}
Bolt UIX
  • 5,988
  • 6
  • 31
  • 58

2 Answers2

0

You should consider decoupling your UI from location logic. You might wrap the location listener with LiveData to use all benefits of lifecycle callbacks instead of handling it manually.
Here is a sample project that demonstrates this technic: https://github.com/Vicrisbeka/LocationMVVM

sdex
  • 3,169
  • 15
  • 28
0
    ├─ com.android.zigmaster.ui.home.HomeFragment$proceedAfterPermissionLocation$1 instance
    │    Leaking: UNKNOWN
    │    Retaining 12 B in 1 objects
    │    Anonymous subclass of com.google.android.gms.location.LocationCallback
    │    ↓ HomeFragment$proceedAfterPermissionLocation$1.this$0
    │                                                    ~~~~~~

The leak is happening because of Anonymous subclass of LocationCallback which is present in proceedAfterPermissionLocation method.

But you are removing the callback fusedLocationClient.removeLocationUpdates(locationCallback) which should have been enough.

So why is the leak happening?

There's a possibility that you've registered the callback, but the fragment is destroyed before the location result is received, so the code to remove the callback wouldn't have been called and it'll leak the fragment.

How to avoid it?

Move the removeCallback code to onPause or onStop in fragment.

There's also a case where you might end up making multiple requestCallbacks if the user presses the button multiple times. You can avoid that using some sort of flag maybe.

Shivam Pokhriyal
  • 1,044
  • 11
  • 26