Location Manager内存泄漏及启动页权限问题求助
Hey there! Let's tackle your two issues one by one—first the LocationManager memory leak detected by LeakCanary, then the permission request getting stuck in the background because your splash screen progresses too fast.
The root cause here is that your anonymous LocationListener classes hold a strong reference to your Splash Activity. Since the LocationManager is a system-level service, it can outlive your Activity's lifecycle. Even if you call removeUpdates, if the listener still has that strong reference, the Activity can't be garbage collected, leading to a leak.
Solution Steps:
Convert LocationListeners to static inner classes with WeakReference
Static inner classes don't hold an implicit reference to the outer Activity. We'll use aWeakReferenceto access the Activity only when it's still alive, so the GC can collect the Activity once it's destroyed.Double-check listener cleanup timing
Your existingstopLocationListenerlogic is solid, but the static listener+WeakRef combo adds an extra safety net even if cleanup is slightly delayed.
Modified Code Example:
First, rewrite your listeners as static inner classes:
private static class NetworkLocationListener implements LocationListener { private final WeakReference<SplashActivity> activityRef; public NetworkLocationListener(SplashActivity activity) { this.activityRef = new WeakReference<>(activity); } @Override public void onLocationChanged(Location location) { SplashActivity activity = activityRef.get(); if (activity != null) { activity.newLocationReceived(location); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) {} @Override public void onProviderEnabled(String provider) {} @Override public void onProviderDisabled(String provider) {} } private static class GPSLocationListener implements LocationListener { private final WeakReference<SplashActivity> activityRef; public GPSLocationListener(SplashActivity activity) { this.activityRef = new WeakReference<>(activity); } @Override public void onLocationChanged(Location location) { SplashActivity activity = activityRef.get(); if (activity != null) { activity.newLocationReceived(location); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) {} @Override public void onProviderEnabled(String provider) {} @Override public void onProviderDisabled(String provider) {} }
Then initialize them in your Activity's onCreate:
private LocationListener locationListenerNetwork; private LocationListener locationListenerGPS; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... other initialization code locationListenerNetwork = new NetworkLocationListener(this); locationListenerGPS = new GPSLocationListener(this); getLocation(); }
Your splash screen is probably navigating to the second Activity before the RxPermission request completes. When the navigation happens, the splash Activity moves to the background, so the permission dialog (tied to the splash Activity) also goes invisible to the user.
Solution Steps:
Delay navigation until permission handling and location retrieval are done
Don't start the second Activity until you've either successfully gotten the location, handled permission denial, or hit a reasonable timeout (to avoid hanging the splash screen forever).Add a location retrieval timeout
If GPS/network location takes too long (e.g., poor signal), you'll still proceed to the next Activity instead of waiting indefinitely.
Modified Code Example:
First, add a handler for the timeout:
private Handler locationTimeoutHandler; private static final long LOCATION_TIMEOUT_MS = 5000; // 5 seconds, adjust as needed @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... other initialization locationTimeoutHandler = new Handler(Looper.getMainLooper()); locationListenerNetwork = new NetworkLocationListener(this); locationListenerGPS = new GPSLocationListener(this); getLocation(); }
Update getLocation to trigger the timeout and only navigate after permission processing:
@SuppressLint("MissingPermission") private void getLocation() { rxPermissions .request(Manifest.permission.ACCESS_FINE_LOCATION) .subscribe(granted -> { if (granted) { boolean gpsEnabled, networkEnabled; locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (locationManager != null) { gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); if (gpsEnabled && locationListenerGPS != null) { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, locationListenerGPS); } if (networkEnabled && locationListenerNetwork != null) { locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, locationListenerNetwork); } // Start timeout to proceed even if location isn't received locationTimeoutHandler.postDelayed(this::navigateToNextActivity, LOCATION_TIMEOUT_MS); } } else { Timber.e("LOCATION PERMISSION DENIED"); // Handle denial: show a toast then navigate, or prompt user to enable permissions navigateToNextActivity(); } }); }
Update newLocationReceived to cancel the timeout and navigate once location is found:
private void newLocationReceived(Location location) { // Cancel the timeout since we got a valid location locationTimeoutHandler.removeCallbacksAndMessages(null); if (locationManager != null) { locationManager.removeUpdates(locationListenerNetwork); locationManager.removeUpdates(locationListenerGPS); } locationManager = null; this.location = location; // Navigate to next activity now that we have the location navigateToNextActivity(); }
Add a dedicated navigation method to avoid duplicate transitions:
private void navigateToNextActivity() { // Ensure we don't navigate if the Activity is already finishing/destroyed if (!isFinishing() && !isDestroyed()) { Intent intent = new Intent(this, SecondActivity.class); // Pass location data to the next Activity if needed if (location != null) { intent.putExtra("LOCATION", location); } startActivity(intent); finish(); } }
Finally, clean up the timeout handler in onDestroy to prevent extra leaks:
@Override protected void onDestroy() { super.onDestroy(); stopLocationListener(); locationTimeoutHandler.removeCallbacksAndMessages(null); }
内容的提问来源于stack exchange,提问作者user6683139

