4
votes

I am starting a service based on Alarm Manager in every 20 seconds which sends GPS data to my server. The problem is my heap and allocated heap size goes on increasing. When I analysed the heap dump, I found the number of service instances is equal to the number of calls to startService(). How to avoid this issue?

public class SystemBootListener extends BroadcastReceiver {

// Restart service every 30 seconds
private static final long REPEAT_TIME = 1000 * 10;

    @Override
    public void onReceive(Context context, Intent intent) {

        Intent i = new Intent(context, StartLocationServiceAfterReboot.class);
        PendingIntent pending = PendingIntent.getBroadcast(context, 0, PendingIntent.FLAG_UPDATE_CURRENT);

        // Start 20 seconds after boot completed - so that all providers are initialized by then
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.SECOND, 20);

        // Trigger every 10 seconds
        // InexactRepeating allows Android to optimize the energy consumption
        AlarmManager service = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        service.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), REPEAT_TIME, pending);
    }
} 
public class StartLocationServiceAfterReboot extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        if(AppSettings.isRouteConfigured(context)){
            AppSettings.setServiceRunning(context, Boolean.TRUE);
            Intent service = new Intent(context, GPSComputationService.class);
            context.startService(service);
        }
    }
} 
public class GPSComputationService extends Service {

    private static final int MAX_TIME_TO_FETCH_NEW_LOCATION = 8000;

    private final IBinder mBinder = new ServiceBinder();

    private Timer timerToFetchLocInfoFromProviders = null;

    private LocationManager locationManager = null;

    private boolean gpsProviderEnabled=false;

    private boolean networkProviderEnabled=false;

    private int numberOfSatellites = 0;

    private GPSData bestKnownLocation = new GPSData();


    private TCPWriter tcpWriter  ;


    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        tcpWriter= new TCPWriter(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        /*tcpWriter= new TCPWriter(this);*/
        computeBestLocation();
        return Service.START_STICKY;
    }

    private void stopGPSComputationService(){
        stopSelf();
    }


    @Override
    public IBinder onBind(Intent arg0) {
        return mBinder;
    }

    public class ServiceBinder extends Binder {
        public GPSComputationService getService() {
            return GPSComputationService.this;
        }
    }


    public GPSData getBestKnownLocation() {
        return bestKnownLocation;
    }

    public void publishBestKnownLocation(GPSData bestKnownLocation) {
        this.bestKnownLocation = bestKnownLocation;
        sendBestKnownLocationToNMEAServer();

    }


    public void sendBestKnownLocationToNMEAServer(){

        if(getBestKnownLocation() == null){
            stopGPSComputationService();
            return;
        }

        TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
        telephonyManager.getDeviceId();

        NMEAData dataPacketToWrite = new NMEAData(
              telephonyManager.getDeviceId(),
              getBestKnownLocation().getLatitude(),
              getBestKnownLocation().getLongitude(),
              getBestKnownLocation().getTimeStamp(),
              getBestKnownLocation().getSpeed(),
              getBestKnownLocation().getNumberOfSatellites()
              );

        tcpWriter.sendMessage(NMEAServerTypes.MVT600, 
              dataPacketToWrite);
        stopGPSComputationService();
    }



    public GPSData computeBestLocation()   {
        Log.d("#############GPSComputation Status", "Running.......");

        try{
                 if(locationManager==null)
                  locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

                 //Add status listener for satellite count
                 locationManager.addGpsStatusListener(gpsStatusListener);


                 Criteria criteria = new Criteria();
                 criteria.setSpeedRequired(true);
                 criteria.setBearingRequired(true);
                 List<String> providers = locationManager.getProviders(criteria, false);


                //Capture if the GPS/Network providers have been disabled.
                try{
                    gpsProviderEnabled=providers.contains(LocationManager.GPS_PROVIDER) && 
                            locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
                }catch(Exception e){

                }

                try{
                    networkProviderEnabled=providers.contains(LocationManager.NETWORK_PROVIDER) && 
                            locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
                }catch(Exception e){

                }

                if(!gpsProviderEnabled && !networkProviderEnabled)
                    return null;

                if(gpsProviderEnabled)
                    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);

                if(networkProviderEnabled)
                    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);

                timerToFetchLocInfoFromProviders=new Timer();
                timerToFetchLocInfoFromProviders.schedule(new GetLastKnownGoodLocation(), MAX_TIME_TO_FETCH_NEW_LOCATION);
                locationManager.removeGpsStatusListener(gpsStatusListener);

                //Finally store the data in backend Service
                return getBestKnownLocation() ;

        }catch(Exception e){

            return null;
        }
    }

    LocationListener locationListenerGps = new LocationListener() {
        public void onLocationChanged(Location location) {
            timerToFetchLocInfoFromProviders.cancel();
            publishBestKnownLocation(extractAllGeoInfFromLocation(location));
            locationManager.removeUpdates(this);
            locationManager.removeUpdates(locationListenerNetwork);
            locationManager.removeGpsStatusListener(gpsStatusListener);
            gpsStatusListener = null;
        }

        public void onProviderDisabled(String provider) {

        }
        public void onProviderEnabled(String provider) {

        }
        public void onStatusChanged(String provider, int status, Bundle extras) {

        }
    };


    //listen for gps status changes to capture number of satellites.
    GpsStatus.Listener gpsStatusListener = new GpsStatus.Listener() {
        @Override
        public void onGpsStatusChanged(int event) {
            if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS || event == GpsStatus.GPS_EVENT_FIRST_FIX) {
                GpsStatus status = locationManager.getGpsStatus(null);
                Iterable<GpsSatellite> sats = status.getSatellites();
                // Check number of satellites in list to determine fix state
                int tempNumberOfSatellites = 0;
                for (GpsSatellite sat : sats) {
                    if(sat.usedInFix())
                        tempNumberOfSatellites++;
                }
                numberOfSatellites = tempNumberOfSatellites;

            }
        }
    };


    LocationListener locationListenerNetwork = new LocationListener() {
        public void onLocationChanged(Location location) {


            timerToFetchLocInfoFromProviders.cancel();
            publishBestKnownLocation(extractAllGeoInfFromLocation(location));
            locationManager.removeUpdates(this);
            locationManager.removeUpdates(locationListenerGps);
        }

        public void onProviderDisabled(String provider) {

        }
        public void onProviderEnabled(String provider) {

        }
        public void onStatusChanged(String provider, int status, Bundle extras) {

        }
    };

    class GetLastKnownGoodLocation extends TimerTask {
        @Override
        public void run() {
             locationManager.removeUpdates(locationListenerGps);
             locationManager.removeUpdates(locationListenerNetwork);

             Location bestKnownNetworkLocation = null, bestKnownGPSLocation=null;

             if(gpsProviderEnabled)
                 bestKnownGPSLocation=locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
             if(networkProviderEnabled)
                 bestKnownNetworkLocation=locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

             if(bestKnownGPSLocation!=null && bestKnownNetworkLocation!=null){
                 if(bestKnownGPSLocation.getTime()>bestKnownNetworkLocation.getTime())
                     publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownGPSLocation));
                 else
                     publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownNetworkLocation));
                 return;
             }

             if(bestKnownGPSLocation!=null){
                 publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownGPSLocation));
                 return;
             }
             if(bestKnownNetworkLocation!=null){
                 publishBestKnownLocation(extractAllGeoInfFromLocation(bestKnownNetworkLocation));
                 return;
             }
             AppLog.logWarningMsg("Bad luck-NO BEST LOCATION AVAILABLE");
             publishBestKnownLocation(null);
        }
    }

    private GPSData extractAllGeoInfFromLocation(Location location){
        bestKnownLocation = new GPSData();
        bestKnownLocation.setLatitude(location.getLatitude());  
        bestKnownLocation.setLongitude(location.getLongitude());    
        bestKnownLocation.setTimeStamp(location.getTime());
        bestKnownLocation.setSpeed(location.getSpeed()*3.8);
        bestKnownLocation.setNumberOfSatellites(numberOfSatellites);
        return bestKnownLocation;
    }
}
3
Can you share us the code that you think is problematic?K.C.
hi can you go through my code?bikash_binay
Your problem is a memory leak. The garbage collector cannot reclaim the Service instances, because they still have live objects. You call addGpsStatusListener() and requestLocationUpdates() and perhaps there are code paths where your Service is stopped, but you haven't removed these listeners. You can either scan the heap dump for love objects or you can try unregistering all your listeners in Service.onDestroy().David Wasser

3 Answers

1
votes

There is only one instance of service. As per the document

Multiple calls to Context.startService() do result in multiple corresponding calls to onStartCommand()), But only one service instance can exist.

On startService(), the Android system calls the service's onStartCommand() method. If the service is not already running, the system first calls onCreate(), then calls onStartCommand().

0
votes

The only thing that can create this kind of scenario is that you have some kind of memory leak. Your service did it's work and stopped but didn't garbage collected. it probably happen few times and that is why you see many instances of it.

It's hard to find memory leaks but i would suggest you to start from the listeners. check if you unregistered them at the right time.

This link can help you to detect the leak: https://developer.android.com/studio/profile/am-memory.html

0
votes

Some tips to improve and simplify your code:

  1. You want to use one shot service to report GPS coordinates. For this purpose IntentService is much better and it runs in a background thread by design. https://developer.android.com/training/run-background-service/create-service

  2. Use PendingIntent.getService() instead of call StartLocationServiceAfterReboot which just launch other Android component. You can do that immediately. You save one step. https://developer.android.com/reference/android/app/PendingIntent.html#getService(android.content.Context,%2520int,%2520android.content.Intent,%2520int)

  3. Every time you use a resource (like GPS, Sensor, etc...) you have to also write the release part. As I see, you register listeners to GPS Service but never release (unregister) them.

  4. What does AppSettings.setServiceRunning(context, Boolean.TRUE);? My guess you save this into SharedPreference. This can be compromised when app force-stopped or device restarted or suddenly shut-down. Maybe better way this https://stackoverflow.com/a/5921190/5823014

  5. Avoid to use static on Context, Activity, Service, BroadcastReceiver, Application instance. I'm not see in your code snippet, just a general advice to prevent memory-leaks.