location-samples icon indicating copy to clipboard operation
location-samples copied to clipboard

Google play service's v8.1.0's location listener is leaking the activity or service it is attached to

Open claywilkinson opened this issue 9 years ago • 54 comments

(Moving this issue from playgames to here on behalf of @youfacepalm)

I am not sure if this is the right place to report this since I could not find any common "Play Services" issue reporting forum anywhere.. If there is such an issue tracker, please give me the link. Thanks

The latest version of Google play service's (8.1.0 as of writing this report) LocationListener is leaking the activity or service it is attached to according to LeakCanary version 1.3.1.

Steps to reproduce

-Download the LocationUpdate Sample from google's repo (https://github.com/googlesamples/android-play-location/tree/master/LocationUpdates)

-Update the gradle to include the latest version of Google play services (8.1.0, the sample contains Google play services version 6.5.87 which had no issues during my testing)

Add a regular activity (name it "TestActivity" for example) to the project

from the MainActivity, after starting the location update, go to the newly added (startActivity()) TestActivity while finishing the MainActivity (it doesn't matter if you stop or did not stop the location updates)

after about 5 seconds you will see LeakCanary reporting that the MainActivity has leaked (if it is added to the project)

Device Info: Model: Samsung Galaxy S6 edge OS: Android Lollipop v5.1.1 API 22

Trace: D/LeakCanary﹕ In com.hammockstudio.myapplication:1.0:1.

com.hammockstudio.myapplication.MainActivity has leaked: GC ROOT com.google.android.gms.location.internal.zzd$1$1.zzaFg (anonymous class extends com.google.android.gms.location.internal.zzg$zza) references com.google.android.gms.location.internal.zzd$1.zzaFe (anonymous class extends com.google.android.gms.location.internal.zzd$zza) leaks com.hammockstudio.myapplication.MainActivity instance [ 10-02 15:32:05.124 20752:21895 D/LeakCanary ] Reference Key: c3aab681-4124-4c8c-ac2c-97d52fe63e4f Device: samsung samsung SM-G925I zeroltedv Android Version: 5.1.1 API: 22 LeakCanary: 1.3.1 Durations: watch=5016ms, gc=133ms, heap dump=2572ms, analysis=17078ms [ 10-02 15:32:05.124 20752:21895 D/LeakCanary ] Details: Instance of com.google.android.gms.location.internal.zzd$1$1 | zzaFg = com.google.android.gms.location.internal.zzd$1 [id=0x32e4f380] | mDescriptor = java.lang.String [id=0x32e563a0] | mObject = 547036342800 | mOwner = com.google.android.gms.location.internal.zzd$1$1 [id=0x32e56380] Instance of com.google.android.gms.location.internal.zzd$1 | zzaFd = com.google.android.gms.location.LocationRequest [id=0x32dedf40] | zzaFe = com.hammockstudio.myapplication.MainActivity [id=0x32c179f0] | zzaFf = com.google.android.gms.location.internal.zzd [id=0x32df8510] | zzZM = com.google.android.gms.common.api.Api$zzc [id=0x32df84d0] | zzabg = java.util.concurrent.atomic.AtomicReference [id=0x32e55150] | zzL = true | zzaaX = com.google.android.gms.common.api.Status [id=0x32cdaf20] | zzabh = java.lang.Object [id=0x32e55130] | zzabi = com.google.android.gms.internal.zzlc$zza [id=0x32e562a0] | zzabj = java.util.ArrayList [id=0x32e56240] | zzabk = null | zzabl = false | zzabm = false | zzabn = null | zzabo = null | zzabp = null | zzoS = java.util.concurrent.CountDownLatch [id=0x32e55140] Again, this leak did not happen in the Google Play Services version that came with the Location Updates sample!

claywilkinson avatar Oct 06 '15 14:10 claywilkinson

Thanks, this is invaluable feedback. Taking a look.

shailen avatar Oct 07 '15 23:10 shailen

Hey @shailen, any updates on the issue?

youfacepalm avatar Oct 11 '15 05:10 youfacepalm

@youfacepalm I've passed this on to the eng team and they're taking a look. I'll update this issue when I hear back from them.

shailen avatar Oct 20 '15 19:10 shailen

Hey @shailen if you have any updates can you please share it?

youfacepalm avatar Nov 06 '15 09:11 youfacepalm

The eng team is aware of the problem and is working on it. I'll update here when there's a fix.

shailen avatar Nov 19 '15 19:11 shailen

@shailen,

Just for reference, while using the most recent version of Google Play Services (8.3.0), LeakCanary is still reporting that the MainActivity is leaking memory.

APBrown avatar Dec 09 '15 14:12 APBrown

@shailen I checked on Google Play Services 8.4.0 as well. The problem still remains!

youfacepalm avatar Dec 31 '15 04:12 youfacepalm

Any update on this? OOM kept happening as a result in our end :(

thuytrinh avatar Jan 15 '16 07:01 thuytrinh

@thuytrinh As a workaround I moved the location listener inside a service which will broadcast the location once it is obtained, using an event bus. It still leaks the service but it is better than having the activity leaked with multiple views inside.

Would really appreciate an update on this as this issue was reported more than three months ago!

youfacepalm avatar Jan 18 '16 12:01 youfacepalm

Any updates? We faced a lot of OOM crashes because of this issue.

SudaNix avatar Feb 01 '16 11:02 SudaNix

In our case, unregister following callbacks would resolve the issue:

    googleApiClient.unregisterConnectionCallbacks(this);
    googleApiClient.unregisterConnectionFailedListener(this);

this is the fragment that implements the callbacks.

Also, remove the callback of location request if any:

      LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this);
      googleApiClient.disconnect();

thuytrinh avatar Feb 01 '16 16:02 thuytrinh

@youfacepalm et al: I have no concrete updates, unfortunately, except to say that the eng team is looking at this issue.

On Mon, Feb 1, 2016 at 8:21 AM, Thuy Trinh [email protected] wrote:

In our case, unregister following callbacks would resolve the issue:

googleApiClient.unregisterConnectionCallbacks(this);
googleApiClient.unregisterConnectionFailedListener(this);

this is the fragment that implements the callbacks.

Also, remove the callback of location request if any:

  LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this);
  googleApiClient.disconnect();

— Reply to this email directly or view it on GitHub https://github.com/googlesamples/android-play-location/issues/26#issuecomment-178052351 .

shailen avatar Feb 01 '16 17:02 shailen

Got same problem. :( any updates?

xserxses avatar Feb 15 '16 10:02 xserxses

Same problem here. Please let me know when any updated

pjcs2 avatar Feb 22 '16 11:02 pjcs2

Sorry everyone, no definitive updates on when the fix will land. Will update once I know more.

shailen avatar Feb 22 '16 17:02 shailen

I'm having this issue as well, i tested it with 'com.google.android.gms:play-services-location:8.4.0' but issue is still there.

Even though i tried to release in any way that i can (below), still keeps getting into this.

  if (googleApiClient != null) {
            googleApiClient.unregisterConnectionCallbacks(this);
            googleApiClient.unregisterConnectionFailedListener(this);

            if (googleApiClient.isConnected()) {
                    LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this);
            }

            googleApiClient.disconnect();
            googleApiClient = null;
 }

I'll also provide you LeakCanary log so that you can pin point the issue, and hopefully we can get an update soon.

yayaa avatar Feb 25 '16 19:02 yayaa

Same problem here:

04-15 17:58:39.311 9221-9270/com.myapp I/art: hprof: heap dump "/storage/emulated/0/Download/leakcanary/suspected_leak_heapdump.hprof" starting...
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: In com.myapp:1.0:1.
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * com.myapp.activities.CommentActivity has leaked:
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * GC ROOT com.google.android.gms.location.internal.zzd$zzb.zzamC
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * references com.google.android.gms.location.internal.zzd$9.zzaOw (anonymous class extends com.google.android.gms.location.internal.zzd$zza)
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * leaks com.myapp.activities.CommentActivity instance
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * Reference Key: 631c670d-cf7f-4061-819d-914cc2d11620
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * Device: unknown generic Google Nexus 5 - 5.1.0 - API 22 - 1080x1920 vbox86p
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * Android Version: 5.1 API: 22 LeakCanary: 1.3.1
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * Durations: watch=5011ms, gc=132ms, heap dump=1049ms, analysis=3963ms
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * Details:
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * Instance of com.google.android.gms.location.internal.zzd$zzb
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzamC = com.google.android.gms.location.internal.zzd$9 [id=0x131f8830]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   mDescriptor = java.lang.String [id=0x13040b40]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   mObject = -704448000
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   mOwner = com.google.android.gms.location.internal.zzd$zzb [id=0x13202ca0]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: * Instance of com.google.android.gms.location.internal.zzd$9
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzaOw = com.myapp.activities.CommentActivity [id=0x13d58000]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzaOx = com.google.android.gms.location.internal.zzd [id=0x12e84a00]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzaeE = com.google.android.gms.common.api.Api$zzc [id=0x12e84760]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagH = java.util.concurrent.atomic.AtomicReference [id=0x131287a0]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzL = true
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagI = java.lang.Object [id=0x13128780]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagJ = com.google.android.gms.common.api.internal.zzb$zza [id=0x13202c40]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagK = java.lang.ref.WeakReference [id=0x13202c60]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagL = java.util.ArrayList [id=0x13202b40]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagM = null
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagN = false
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagO = false
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagP = false
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagQ = null
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagR = null
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagS = null
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzagy = com.google.android.gms.common.api.Status [id=0x12c26f00]
04-15 17:58:44.354 9221-10071/com.myapp D/LeakCanary: |   zzpJ = java.util.concurrent.CountDownLatch [id=0x13128790]

Winghin2517 avatar Apr 15 '16 17:04 Winghin2517

Hello all

I followed yourfacepalm advice and did a workaround this morning:

package com.myapp.service;

import android.Manifest;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.LocalBroadcastManager;

import com.myapp.Constants.Constants;
import com.myapp.Utils.LocationUtil;
import com.myapp.activities.R;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.places.Places;

import timber.log.Timber;

public class LocationIntentService extends IntentService implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        LocationListener {

    GoogleApiClient mGoogleApiClient;

    public LocationIntentService(String name) {
        super(name);
    }

    public LocationIntentService() {
        super(LocationIntentService.class.getName());
    }

    @Override
    protected void onHandleIntent(Intent workIntent) {
        mGoogleApiClient = new GoogleApiClient.Builder(getApplicationContext())
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .addApi(Places.GEO_DATA_API)
                .addApi(Places.PLACE_DETECTION_API)
                .build();
        mGoogleApiClient.connect();
        Timber.e("locationintentservice start");
    }

    @Override
    public void onLocationChanged(Location location) {
        Timber.e("location changed");
        if (location != null) {
            Timber.e("location received");
            SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.user_shared_preferences), Context.MODE_PRIVATE);
            LocationUtil.storeLocation(sharedPreferences, location);
            Timber.e(String.valueOf(location.getLatitude()) + " Longitude: " +
                    String.valueOf(location.getLongitude()));
        } else {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                    != PackageManager.PERMISSION_GRANTED &&
                    ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
                            != PackageManager.PERMISSION_GRANTED) {
                Intent intent = new Intent(Constants.COMMENT_LOC_RESULTS);
                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
            } else {
                Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.user_shared_preferences), Context.MODE_PRIVATE);
                LocationUtil.storeLocation(sharedPreferences, lastLocation);
            }
        }
        if (mGoogleApiClient.isConnected()) {
            mGoogleApiClient.unregisterConnectionCallbacks(this);
            mGoogleApiClient.unregisterConnectionFailedListener(this);
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            mGoogleApiClient.disconnect();
        }
        mGoogleApiClient = null;
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Timber.e("GAC connected");
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            Intent intent = new Intent(Constants.COMMENT_LOC_RESULTS);
            LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
        } else {
            Timber.e("fetching location");
            LocationRequest mLocationRequest = LocationRequest.create();
            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            mLocationRequest.setInterval(1000 * 30);
            mLocationRequest.setFastestInterval(1000 * 5);
            if (ActivityCompat.checkSelfPermission(LocationIntentService.this,
                    Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                    && ActivityCompat.checkSelfPermission(LocationIntentService.this,
                    Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                Intent intent = new Intent(Constants.COMMENT_LOC_RESULTS);
                LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
                return;
            } else {
                Timber.e("connected and access granted");
                LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
                        mLocationRequest,
                        LocationIntentService.this);
            }
        }
    }

    @Override
    public void onConnectionSuspended(int i) {
        Timber.e("GAC suspended");
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            Intent intent = new Intent(Constants.COMMENT_LOC_RESULTS);
            LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
        } else {
            Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.user_shared_preferences), Context.MODE_PRIVATE);
            LocationUtil.storeLocation(sharedPreferences, location);
        }
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Timber.e("GAC failed");
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            Intent intent = new Intent(Constants.COMMENT_LOC_RESULTS);
            LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
        } else {
            Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.user_shared_preferences), Context.MODE_PRIVATE);
            LocationUtil.storeLocation(sharedPreferences, location);
        }
    }

}

Try using this service class to get your location information and save the lat and lng into your shareprefs.

The localbroadcastmanager is only there to request for permission in the activity class:

    private BroadcastReceiver locationResultsReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            ActivityCompat.requestPermissions(CommentActivity.this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    LocationUtil.MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    };

The onrequestpermissionresult method in the activity class:

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case LocationUtil.MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                if (grantResults.length > 0) {
                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        Intent intent = new Intent(this, LocationIntentService.class);
                        startService(intent);
                    } else {
                        // Should we show an explanation?
                        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                                Manifest.permission.ACCESS_FINE_LOCATION)) {
                            //Show permission explanation dialog...
                            AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle);
                            builder.setTitle(Constants.LOCATION_PERM);
                            builder.setCancelable(false);
                            builder.setMessage(Constants.LOCATION_PERM_SUBTEXT);
                            builder.setPositiveButton(getString(R.string.ok), new AlertDialog.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    dialog.dismiss();
                                    ActivityCompat.requestPermissions(CommentActivity.this,
                                            new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                                            LocationUtil.MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
                                }
                            });
                            builder.show();
                        } else {
                            AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle);
                            builder.setTitle(Constants.LOCATION_PERM);
                            builder.setCancelable(false);
                            builder.setMessage(Constants.LOCATION_PERM_REREQUEST_SUBTEXT);
                            builder.setPositiveButton(getString(R.string.settings), new AlertDialog.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    dialog.dismiss();
                                    Intent intent = new Intent();
                                    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                    Uri uri = Uri.fromParts("package", CommentActivity.this.getPackageName(), null);
                                    intent.setData(uri);
                                    startActivity(intent);
                                    finish();
                                }
                            });
                            builder.show();
                        }
                    }
                }
                break;
            }

LeakCanary will still dump but it will now say NO LEAK FOUND:

04-16 12:13:54.465 8120-15176/com.myapp D/LeakCanary: In com.myapp:1.0:1.
                                                        * NO LEAK FOUND.

                                                        * Reference Key: 718959be-c178-4237-a24f-8d74972c9cc0
                                                        * Device: unknown generic Google Nexus 5 - 5.1.0 - API 22 - 1080x1920 vbox86p
                                                        * Android Version: 5.1 API: 22 LeakCanary: 1.3.1
                                                        * Durations: watch=5059ms, gc=135ms, heap dump=1186ms, analysis=5174ms

Winghin2517 avatar Apr 16 '16 11:04 Winghin2517

Hey @shailen any updates on this? It's quite surprising that such a big issue in play services, which is used in many apps, is not fixed after 10 months.

droidster avatar Sep 08 '16 18:09 droidster

I've had good luck setting the actual listener that is leaking (such as the Activity) in a WeakReference and just forwarding the callback from a wrapper class:

public class WeakLocationListener implements LocationListener {

    private final WeakReference<LocationListener> locationListenerRef;

    public WeakLocationListener(@NonNull LocationListener locationListener) {
        locationListenerRef = new WeakReference<>(locationListener);
    }

    @Override
    public void onLocationChanged(android.location.Location location) {
        if (locationListenerRef.get() == null) {
            return;
        }
        locationListenerRef.get().onLocationChanged(location);
    }

}

That way the instance of the wrapper class might leak, but the Activity does not.

byencho avatar Sep 19 '16 19:09 byencho

In v9.8.0 it is still leaking. Please can someone tell the Google Play Services team about the weak references usage? Maybe this way they will fix this issue much faster.

stanmots avatar Nov 11 '16 11:11 stanmots

@shailen any updates? I have also reported the issue here: https://code.google.com/p/android/issues/detail?id=227856

fernandospr avatar Nov 16 '16 20:11 fernandospr

I have created a project that uses SupportMapFragment and shows the user location using two approaches:

  1. LocationServices.FusedLocationApi.requestLocationUpdates
  2. googleMap.setOnMyLocationChangeListener

I've discovered that the second approach does not leak (though it is deprecated). This would be a workaround for some people that just need the user location to show it on a map.

fernandospr avatar Nov 17 '16 19:11 fernandospr

@fernandospr Try to switch your GoogleApiClient instance to automanage, it will fix the leak.

carlosjs23 avatar Dec 11 '16 04:12 carlosjs23

@carlosjs23 Hi there Carlos, could you provide a snippet of how to use automanage to access the FusedLocationApi ? I know it should be the same as the other APIs but I'm struggling with it, thanks in advance

alejofila avatar Dec 12 '16 21:12 alejofila

Passing application context to GoogleApiClient.Builder should solve the leak.

mGoogleApiClient = new GoogleApiClient.Builder(getContext().getApplicationContext())

sealskej avatar Dec 14 '16 10:12 sealskej

Sorry, I meant: use the "enableautomanage" method of the GoogleApiClient, ex:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this) .enableAutoManage(this /* FragmentActivity /, this / OnConnectionFailedListener */)

El 12 dic. 2016 16:12, "Jose Agudelo" [email protected] escribió:

@carlosjs23 https://github.com/carlosjs23 Hi there Carlos, could you provide a snippet of how to use automanage to access the FusedLocationApi ? I know it should be the same as the other APIs but I'm struggling with it, thanks in advance

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/googlesamples/android-play-location/issues/26#issuecomment-266554191, or mute the thread https://github.com/notifications/unsubscribe-auth/AJ_QL086ScFODN2tYiB9iNbI-q1YNam3ks5rHbjFgaJpZM4GJ0Km .

carlosjs23 avatar Dec 14 '16 10:12 carlosjs23

@sealskej Using new GoogleApiClient.Builder(getApplicationContext()) still leaks.

@carlosjs23 Using the following still leaks:

mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this, this)
                .addConnectionCallbacks(this)
                .addApi(LocationServices.API)
                .build();

fernandospr avatar Dec 14 '16 19:12 fernandospr

I'm using @byencho approach and is not leaking anymore, @fernandospr try that

alejofila avatar Dec 14 '16 20:12 alejofila

Thanks @thuytrinh removing the callbacks solved for me

Naibeck avatar Feb 17 '17 19:02 Naibeck

Has this been fixed yet? I'm also experiencing the same issue.

ryanthon avatar Apr 04 '17 08:04 ryanthon

@ryanthon no fixes from Google Play S. team, but you can use @byencho approach.

carlosjs23 avatar Apr 04 '17 13:04 carlosjs23

Just set up @byencho's approach on my project. I noticed that it didn't fix the issue if I only used the weak reference for the LocationListener. It seems like the issue is actually caused by the connection callbacks listeners (ConnectionCallbacks and OnConnectionFailedListener). Once I set up a weak reference for those as well, the leak went away.

ryanthon avatar Apr 04 '17 18:04 ryanthon

Thanks @thuytrinh unregister the callbacks solved the problem for me.

kaininggu avatar Jun 15 '17 22:06 kaininggu

Hi all, I've been struggling with the same problem for the the last day or so until I found this post. However in my case it was not enough to pass connection and location listeners as weak references as it LeakCanary was still reporting activity leaked. Which was my anonymous class passed to

LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, mLocationCallback).then(new ResultTransform {...} )

to receive callback when the location listener has been removed. So I ended up not attaching this callback at the end. After all those three changes there were no more leaks reported. Hope this helps anyone in any way.

aidanas avatar Jun 23 '17 21:06 aidanas

Has anyone tried the newer FusedLocationProviderClient instead? I tried the new sample for location updates, added Leak Canary, and again I still see a leak through LocationCallback. Even the onComplete callback for removeLocationUpdates isn't getting called in the sample.

GEllickson avatar Sep 29 '17 20:09 GEllickson

@GEllickson yup, I migrated to FusedLocationProviderClient as well. & it is still leaking

xserxses avatar Oct 16 '17 13:10 xserxses

I also have the same leak through LocationCallback using FusedLocationProviderClient.

Alkisum avatar Oct 17 '17 06:10 Alkisum

We were able to solved it by @byencho approach combined with @sealskej approach. Also unregister callbacks after removeLocationUpdates thanks ...

rajeevinimekumar008 avatar Oct 24 '17 10:10 rajeevinimekumar008

Using @sealskej solution worked for Me.

refaelsh avatar Nov 11 '17 19:11 refaelsh

With 'com.google.android.gms:play-services:11.0.1' I also get the problem about LocationCallback. But it can be fixed by WeakReference. my old code:

    LocationCallback mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                // updateLocation();
            }
        };

use mLocationCallback:

    mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                               mLocationCallback, Looper.myLooper());

my fix code:

    private static class LocationCallbackReference extends LocationCallback {

        private WeakReference<LocationCallback> locationCallbackRef;

        LocationCallbackReference(LocationCallback locationCallback) {
            locationCallbackRef = new WeakReference<LocationCallback>(locationCallback);
        }

        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            if (locationCallbackRef != null && locationCallbackRef.get() != null) {
                locationCallbackRef.get().onLocationResult(locationResult);
            }
        }
    }
    LocationCallback locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                updateLocation();
            }
        };
    mLocationCallback = new LocationCallbackReference(locationCallback);
    mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                               mLocationCallback, Looper.myLooper());

I just wrap the LocationCallback with WeakReference. The 'mFusedLocationClient' is constructed in this way: mFusedLocationClient = LocationServices.getFusedLocationProviderClient(activity); rather than "LocationServices.FusedLocationApi".

HOLDfoot avatar Jan 09 '18 09:01 HOLDfoot

With 'com.google.android.gms:play-services:11.0.1' I also get the problem about LocationCallback. But it can be fixed by WeakReference. my old code:

    LocationCallback mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                // updateLocation();
            }
        };

use mLocationCallback:

    mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                               mLocationCallback, Looper.myLooper());

my fix code:

    private static class LocationCallbackReference extends LocationCallback {

        private WeakReference<LocationCallback> locationCallbackRef;

        LocationCallbackReference(LocationCallback locationCallback) {
            locationCallbackRef = new WeakReference<LocationCallback>(locationCallback);
        }

        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            if (locationCallbackRef != null && locationCallbackRef.get() != null) {
                locationCallbackRef.get().onLocationResult(locationResult);
            }
        }
    }
    LocationCallback locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                updateLocation();
            }
        };
    mLocationCallback = new LocationCallbackReference(locationCallback);
    mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                               mLocationCallback, Looper.myLooper());

I just wrap the LocationCallback with WeakReference. The 'mFusedLocationClient' is constructed in this way: mFusedLocationClient = LocationServices.getFusedLocationProviderClient(activity); rather than "LocationServices.FusedLocationApi".

Oops!Not work for me!

IrvingRyan avatar Mar 01 '19 01:03 IrvingRyan

I tried using WeakReference but still leaking, very disappointed 😔

nerrydanna avatar May 13 '19 22:05 nerrydanna

gms play service 17.0.0, LocationCallback instance still leaking. workaround: use WeakReference in LocationCallback. image

ghost avatar Jul 12 '19 09:07 ghost

Still leaking for me in Leak Canary despite callback being removeLocationUpdates and locationCallback being set to nil. Does anyone have an example in kotlin for WeakReference?

paulsUsername avatar Aug 03 '19 22:08 paulsUsername

I've used weak reference, still leaking:

mFusedLocationClient?.requestLocationUpdates(locationRequest, WeakReference(locationCallback).get(), null)

paulsUsername avatar Aug 03 '19 22:08 paulsUsername

I have to say, I can't see how this is expected behaviour. It is a silly problem that should be solved by now!

paulsUsername avatar Aug 04 '19 20:08 paulsUsername

I've used weak reference, still leaking:

mFusedLocationClient?.requestLocationUpdates(locationRequest, WeakReference(locationCallback).get(), null)

You got wrong with using java WeakReference, You have to create a LocationCallback subclass which weak reference to your locationCallback instance, and your locationCallback itself must be strong referenced by your lifecycle container.

ghost avatar Aug 05 '19 01:08 ghost

Reading an issue from 2015 in 2019 and seeing it's still not fixed and that too by Google, ridiculous! I see everyone suffering here and I am also the victim.

Anyways, I am here to tell how I solved the issue. @sealskej approach worked for me. I am using LocationListener through a third-party library so all I needed to do was to pass applicationContext in the library instead of activity context.

sidhuparas avatar Aug 05 '19 11:08 sidhuparas

I've used weak reference, still leaking: mFusedLocationClient?.requestLocationUpdates(locationRequest, WeakReference(locationCallback).get(), null)

You got wrong with using java WeakReference, You have to create a LocationCallback subclass which weak reference to your locationCallback instance, and your locationCallback itself must be strong referenced by your lifecycle container.

You have just won my noble Paul's prize. It comes with no perks but lots of thanks!! Thank you, leak sorted! Maybe I should read more, complain later. Thanks @1001101

paulsUsername avatar Aug 06 '19 13:08 paulsUsername

The anonymous class (listener) has an implicit reference to the Activity. This is not Google's code causing the problem. The listener you register needs to be a concretely defined, static class so it won't contain an implicit reference to MainActivity.

Specifically this... https://github.com/android/location-samples/blob/master/LocationUpdates/app/src/main/java/com/google/android/gms/location/sample/locationupdates/MainActivity.java#L330

raderto avatar Nov 06 '19 21:11 raderto

I also have this issue, for quite some time i was digging all the layers that compose my activity + my code

Experimented with a lot of approaches to make sure the listener was not keeping my activity in memory, profiled the app process a lot of times.

I have found an instance in the app heap which has the following fields image

As you can see, we have a field called callbackIdMap, which is the one retaining the listeners the code was table to remove from the fusedlocationprovider client, but for some reason the references remain in the callbackIdMap as keys

As the google library code is offuscated, i am unable to know to that class these fields belong to. We have a constant string named LOCATION_PROVIDER_NAME also that has the string value "fused" which is not offuscated but i am unable to find any results by searching by this currently.

The fix would be to remove the listeners from this map when FusedLocationproviderClient.removeLocationUpdates is called, simple as that :see_no_evil:

This ticket is open for almost 6 years surprisingly :older_adult:

ruieduardosoares avatar Jun 30 '21 16:06 ruieduardosoares

Confirmed fixed in 20.0.0. Good news, but did this really have to take 7 years and 12 major releases?

bubenheimer avatar Jun 11 '22 07:06 bubenheimer