Most of the common suggestions is, as it seems leaks are coming from context, use application context and detach location call from activity lifecycle. I did both previous thing and also later. But leak still existing. Here is my implementation with viewmodel,
public class LocationViewModel extends ViewModel {
private final MutableLiveData<Location> _locationLive = new MutableLiveData<>();
public LiveData<Location> locationLive = _locationLive;
private final static String TAG = "LocationViewModel";
private final FusedLocationProviderClient mFusedLocationClient;
private LocationRequest locationRequest;
private WeakReference<LocationCallback> locationCallback;
private static final long UPDATE_INTERVAL = 5000, FASTEST_INTERVAL = 3000;
public LocationViewModel() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(MyApp.getApplication());
prepareGpsAccess();
}
private void prepareGpsAccess() {
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(UPDATE_INTERVAL); // 10 seconds
locationRequest.setFastestInterval(FASTEST_INTERVAL); // 5 seconds
locationCallback = new WeakReference<>(new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
for (Location location : locationResult.getLocations()) {
_locationLive.postValue(location);
stopLocationUpdates();
}
}
});
}
public void stopLocationUpdates() {
if (mFusedLocationClient != null && locationCallback.get() != null) {
try {
final Task<Void> voidTask = mFusedLocationClient.removeLocationUpdates(locationCallback.get());
voidTask.addOnCompleteListener(task -> {
Log.e(TAG, "StopLocation addOnCompleteListener: " + task.isComplete());
if (task.isSuccessful()) {
Log.d(TAG, "StopLocation updates successful! ");
} else {
Log.d(TAG, "StopLocation updates unsuccessful! " + voidTask.toString());
}
});
voidTask.addOnSuccessListener(aVoid -> Log.e(TAG, "StopLocation addOnSuccessListener: "));
voidTask.addOnFailureListener(e -> Log.e(TAG, "StopLocation addOnFailureListener: " + e.toString()));
} catch (SecurityException exp) {
Log.d(TAG, "StopLocation Security exception while removeLocationUpdates");
}
}
}
public void getLocation() {
if (ActivityCompat.checkSelfPermission(MyApp.getApplication(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(MyApp.getApplication(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
mFusedLocationClient.requestLocationUpdates(locationRequest, locationCallback.get(), Looper.myLooper());
}
}
Here is the LeakCanary output,
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
549 bytes retained by leaking objects
Signature: 567aad2e1a38902d91202892cd70b65cb7df4f3
┬───
│ GC Root: Global variable in native code
│
├─ com.google.android.gms.location.zzam instance
│ Leaking: UNKNOWN
│ Retaining 1.5 kB in 31 objects
│ ↓ zzam.zza
│ ~~~
├─ com.google.android.gms.location.zzx instance
│ Leaking: UNKNOWN
│ Retaining 807 B in 23 objects
│ ↓ zzx.zzc
│ ~~~
├─ com.package.LocationViewModel$1 instance
│ Leaking: UNKNOWN
│ Retaining 561 B in 17 objects
│ Anonymous subclass of com.google.android.gms.location.LocationCallback
│ ↓ LocationViewModel$1.this$0
│ ~~~~~~
╰→ com.package.LocationViewModel instance
Leaking: YES (ObjectWatcher was watching this because com.package.location.LocationViewModel received
ViewModel#onCleared() callback)
Retaining 549 B in 16 objects
key = f23437cd-aafa-4153-866b-2b8961646172
watchDurationMillis = 14454
retainedDurationMillis = 9452
I have tried numerous thing, still this leak exist. Please share if you know another way to avoid this leak.
There is a relevant issue still open here, https://github.com/android/location-samples/issues/100