Search code examples
androidandroid-locationjava.util.concurrent

Location finder in service blocked by semaphore?


I have a service that needs to know the phone's location. The main activities of the service are carried out in a thread as follows (with processing stuff removed:

Semaphore locationAcquired = new Semaphore(1);
LocationFinder finder;

...
public void run() {
            delaySeconds = 60;
            Looper.prepare();
            while (true) {
                try {

                    finder.StartFinder();

                locationAcquired.acquire();
                // do some stuff...
                } catch (InterruptedException e) {
                    if (isDestroy) {
                        Log.d(TAG, "Closing Monitor Thread");
                        break;
                    } // else just wake up and process the location 
                } catch (Exception e) {
                    e.printStackTrace();

                }
            } // end while
        } // end run

The LocationFinder class implementation (again, slightly simplified):

package com.ksdagile.opengate;

import...

public class LocationFinder {

public static final int ONE_SECOND = 1000;

LocationListener locationListener;
String provider = LocationManager.PASSIVE_PROVIDER; // passive by default
LocationManager locationManager;
public Location currentLocation;
long updateSeconds;
private boolean isLooking = false;
OpenGateService openGateService;

public LocationFinder(LocationManager _lm, OpenGateService _openGateService) {

    openGateService = _openGateService;
    locationManager = _lm;
     // initialize with whatever location might be available
    currentLocation = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER); 


    locationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            // A new location update is received.  Do something useful with it.  In this case,
            // we're sending the update to a handler which then updates the UI with the new
            // location.
            currentLocation = location;
            String newLoc = String.format("Found new Location lat:%.2f long:%.2f", currentLocation.getLatitude(), currentLocation.getLongitude());
            Log.d(getClass().getName(), newLoc);
            openGateService.locationAcquired.release();
        }
                    // simple implementations of onProvider<> etc.
    };

}

public void SetProvider(boolean isActive) {
    if (isActive)
        provider = LocationManager.GPS_PROVIDER;
    else
        provider = LocationManager.PASSIVE_PROVIDER;
}

public void SetFrequency(long delay) {
    updateSeconds = delay;
}

public void StartFinder() {
    if (!isLooking) {
        isLooking = true;
        locationManager.requestLocationUpdates(provider, updateSeconds*ONE_SECOND, 10, locationListener);
        Log.d(getClass().getName(), String.format("Request location from %s provider, every %d sec.", provider, updateSeconds));            
    } else
        Log.d(getClass().getName(), "Location request running");
}

public void StopFinder() {
    locationManager.removeUpdates(locationListener);
    isLooking = false;
}

public boolean IsLocating() {
    return isLooking;
}
}

My problem is that the onLocationChanged routine is not called, even though I know there are new readings. For example, when configured to read in Passive Mode, I run Waze and see myself moving. Is it possible that the call to onLocationChanged being blocked by the semaphore? If so, how do I get around this? I want to be able to change the parameters for requestLocationUpdate dynamically.


Solution

  • I think getting a tumbleweed badge is a very dubious honor, but here is the answer: Yes, the .acquire() call blocks the thread, and the onLocationChanged() method is not called.

    The solution is to have a handler run a runnable, such as:

    public void onLocationChanged(Location location) {
                xxxService.locHandler.post(xxxService.locationRun);
            }
    

    where

    Runnable locationRun = new Runnable() {
    
        @Override
        public void run() {
            // handle stuff for new location
        }
    };
    

    The general principle is that you send and handle events, rather than running a polling loop. If you need to handle a non-event, e.g. no new location read, then you set up a countdown timer.