Как исправить проблему с утечкой памяти во фрагменте FusedLocationApi?

Я использую Fuse Location API, чтобы найти текущее местоположение во фрагменте, иногда получая утечку памяти.

Как решить эту проблему?

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
    ====================================

Вот мой код фрагмента:

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
    }
}

person Hari Shankar S    schedule 27.04.2021    source источник
comment
Мб, вы должны переместить свой метод fusedLocationClient.removeLocationUpdates(locationCallback) в onStop()   -  person Rinat Diushenov    schedule 30.04.2021
comment
пытался все еще получить утечку памяти   -  person Hari Shankar S    schedule 04.05.2021


Ответы (3)


у меня такая же проблема, я исправил

следуйте этому коду 100% идеальное решение

Обновление местоположения с помощью LiveData

https://www.droidcon.com/news-detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/news/android-news/Android%20Tutorial%20On%20Location%20Update%20With%20LiveData

полный исходный код: https://github.com/mayowa-egbewunmi/LocationUpdateWithLiveData

person fairy    schedule 11.05.2021

Вам следует рассмотреть возможность отделения вашего пользовательского интерфейса от логики местоположения. Вы можете обернуть прослушиватель местоположения с помощью LiveData, чтобы использовать все преимущества обратных вызовов жизненного цикла вместо того, чтобы обрабатывать их вручную.
Вот пример проекта, демонстрирующий эту технику: https://github.com/Vicrisbeka/LocationMVVM

person sdex    schedule 05.05.2021

    ├─ 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
    │                                                    ~~~~~~

Утечка происходит из-за Anonymous subclass of LocationCallback, который присутствует в методе proceedAfterPermissionLocation.

Но вы удаляете обратный вызов fusedLocationClient.removeLocationUpdates(locationCallback), которого должно быть достаточно.

Так почему же происходит утечка?

Есть вероятность, что вы зарегистрировали обратный вызов, но фрагмент уничтожается до того, как будет получен результат местоположения, поэтому код для удаления обратного вызова не был бы вызван, и он приведет к утечке фрагмента.

Как этого избежать?

Переместите код removeCallback на onPause или onStop во фрагменте.

Также есть случай, когда вы можете сделать несколько requestCallbacks, если пользователь нажмет кнопку несколько раз. Вы можете избежать этого, используя какой-то флаг.

person Shivam Pokhriyal    schedule 06.05.2021