On Time, On Point, On Budget!

GPS development issues on Android

Nowadays most of the mobile devices have built-in GPS functionality. Android-based devices are one of the list. In this article we’ll try to describe the GPS functional insides as developers see it.

Contemporary mobile GPS receivers have quite significant restrictions, such as:

  1. it takes a plenty of time to detect the coordinates from the cold start;
  2. position refreshing rate and provider data need to be set manually;
  3. high power consumption if high refreshing rate is set.

All these drawbacks are to be solved by complicated GPS software architecture. Existing Android SDK is not sufficient, and the standard API for GPS programming is far from enough.

On the other side, every application needs its own tuning and parameters setting, this includes provider (GPS, AGPS, wireless networks), refreshing rate, application behavior (reaction to coordinates change or receiving the current position).

In order to ensure smooth application functioning developers need to write additional classes specially responsible for GPS. The packets for every application are tailor-made regarding customer’s requirements, so it’s nearly impossible to re-use them.

Nevertheless, there is a common part of GPS software packets structure, providing the basic functionality: acquiring the last known coordinates, selecting the provider, coordinates changing alert etc. This part is very good to be separated to a special library, which can be re-used in different applications. This library will allow developers to concentrate on creating the application, without wasting the effort on basic GPS logics and tuning.

GPS library functional requirements

To avoid developers’ logical mistakes and to ensure the easier developing workflow we should point out some requirements to guideline our future library:

  1. Developer shouldn’t distract on GPS coordinates changing alerts subscriptions. The frequent case is when subscription is required only if the form is active, deactivating the form (stop, pause, back, destroy) requires stopping the subscription, and activating requires creating a new listener and subscribing it to alerts. This approach lacks a lot of points. The simplest is that you can forget to alert about form closing, and the toughest is when there are a lot of listeners, they can desynchronize and diverse the coordinates, resulting in numerous equal objects overloading memory, CPU and battery. From this comes the second requirement;
  2. Only one LocationListener object (non-singleton type) must be used. In Android OS every object item is destroyed alongside with the parent Activity, so global access objects should be created in Application instead of Activity. It should be avoided to create a new LocationListener on every GPS request, the garbage collector destroys the unsubscribed listeners by itself, but excessive objects temporarily consume memory, CPU and battery. If declaring LocationListener in Application, all the workflow goes through one single object, resulting in much easier debugging. The frequent need in working with GPS device is to set the fake coordinates. The standard approach requires manifest changing, adding new permissions, creating a class for coordinates setting, therefore adding unneeded difficulties. It’s much easier to set up a fixed coordinates value for a single LocationListener;
  3. Fast coordinates estimation and approximation. It’s usual when it’s needed to approximate the client’s position on application starting (e.g. for contents downloading or application localization), so it’s needed to get the approximate coordinates quickly, without wasting time for exact positioning. The approximate coordinates can be acquired in different ways: using the last known coordinates or using the wireless networking provider if the previous coordinates are unknown (this method works much faster than pure GPS);
  4. The coordinates changing must be alerted outside the current application context. Although the LocationListener exists in only one item, the coordinates changing alert is to be transferred to a lot of logical elements (further referred as listeners). The Explorer pattern should not be used because the data exchange process is not always inside a single program context. The direct exchange is impossible when the GPS module is a Service, and the addressee logics is in the Application or in the outside third-party application;
  5. There should be a possibility to unsubscribe all the listeners from the GPS simultaneously and to re-subscribe them again. This is a must if transferring the coordinates to a server. The client side must stay inert to changes while the server side is acquiring and processing the coordinates, otherwise the server response can be inadequate or outdated;
  6. The most accurate provider should be auto-fetched. There are a lot of ways to acquire the coordinates, and always only the most accurate way should be used. Nevertheless, the logics mustn’t seek for the new provider availability or reconnect automatically. This is to be done only by GPS module.

GPS module implementation

The basic principles of GPS development in Android, best provider detection and last known coordinates fetching are described thoroughly in the corresponding article.

Now we should look through the implementation of abovementioned requirements:

1-2) Because of the above-pointed problem (erasing the objects with the Activity item) there’s no way to use the Singleton pattern. So all the objects required in the single must be declared in Application instead of Activity. LocationListener (class required for GPS coordinates changing) also must be in single and therefore must be declared in Application. It’s important to delete all the links on closing the application in order to prevent the Memory leak error. The application may be forced to close, and this case should also be processed with correct deletion of links. The Application class contains onTerminate() method, which works even on forced closing. So it must be used for calling the LocationListener.unregister() method to delete the object’s link from the Location Manager system class. This approach guarantees the uniqueness of the LocationListener and the free access through context, which exists in any View or Activity.

3) For fetching the initial (approximate) coordinates required for application starting the last known coordinates may be used. The last known coordinates are always stored in the phone and available through standard system APIs.

LocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);

They are not exact (e.g. the user may have switched off the phone and kept on moving) but the approximation is enough for application starting. But if the user hasn’t started GPS since the phone switched on, the last known coordinates are not available. In this case NETWORK_PROVIDER should be used instead of GPS_PROVIDER since NETWORK_PROVIDER allows much faster coordinates fetching (although they are also not exact). It’s important to remember that LocationManager approximation error can be quite large when using NETWORK_PROVIDER, and there’s no guarantee that user is inside the detected coordinates round (and the round radius is less than the error). The reason is that the provider detects only the closest existing network node, but not the user’s location. This also appears if the device connects to the Internet through Wi-Fi and doesn’t have the sim-card.

4) Data transfer between Context applications implementation(e.g. between 2 unlinked applications, or between application and service). To implement the data transfer to another application context It’s optimal to use the Android’s internal message system. The system message can contain data or just an event code. The completed event code is transferred to all active applications, but only the applications with subsequent IntentFilter setting process the code. To make our application receive the coordinates changing alert we can enhance the IntentFilter with our message type – MessageManager.LOCATION_CHANGED. Therefore LocationListener must send the required system messages, so the Intent with message is to be created on coordinates changing, and every listener decides an action based on logics. With this approach only the behavior remains in logics, while all the other workflow (signing, provider, exactness) is moved to GPS module.

System message sending method:

Intent intent = new Intent(MessageManager.PLAYER_LOCATION_CHANGED);
context.sendBroadcast(intent);

5)Feature allowing to unsubscribe all the listeners from GPS simultaneously and re-subscribe them again. In our architecture this requirement is implemented automatically. Since the LocationListener exists only in single, it stops sending coordinates changing messages on unregister(). Therefore the listeners will not receive messages any more, but will stay subscribed to LocationListener, and will receive the messages again on performing the register() method. On the other side, listener unsubscribing does not affect the LocationListener working process, and it’s possible to restore the listeners’ subscription since LocationListener is single and available through context.

6) Automatic reconnection to the best available provider. The exactness of the current GPS provider can change on user’s moving (e.g. if the user enters the metal shelter, all the GPS satellites disappear and the exactness diminishes, since the internet provider exactness remains the same). Or if the user used internet and then enabled GPS, it’s best to use GPS. LocationListener can detect the most accurate provider automatically. To do this it must probe all the providers for availability and exactness. Provider refreshing time is set corresponding to the application’s task. This approach is described thoroughly in the Android documentation.

All the abovementioned can be implemented in one single class

 

import java.util.Timer;
import java.util.TimerTask;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;

/**
* GPS Location Listener.
*/
public class LocationGpsOnlyService implements LocationListener,
LocationService {

private static final int BEST_PROVIDER_SHCEDULE = 5000;

private LocationManager locationManager;

private Location gpsLocation;

private Location firstLocation;

private Context context;

private TimerTask locationCheckingTask;

private Timer t = new Timer();

long updatedGpsTime = System.currentTimeMillis();

public LocationGpsOnlyService(Application application)
{         context = application.getApplicationContext();
locationManager = (LocationManager) application.getApplicationContext()
.getSystemService(Context.LOCATION_SERVICE);
enableLocationListening();     }

public void cancelChecking() {
if (locationCheckingTask != null)
{             locationCheckingTask.cancel();         }

}

@Override
public void disableLocationListening()
{         locationManager.removeUpdates(this);
cancelChecking();     }

public void enableChecking() {
locationCheckingTask = new TimerTask() {
@Override
public void run() {
if (gpsLocation != null)
{                         gpsLocation = null;
notifyAboutUpdatedLocation();
}

}
};
t.schedule(locationCheckingTask, BEST_PROVIDER_SHCEDULE, BEST_PROVIDER_SHCEDULE);
}

@Override
public void enableLocationListening()
{         initBestProvider();
enableChecking();     }

@Override
public Location getInaccurateLocation()
{         return firstLocation;     }

@Override
public Location getLatestLocation()
{         return gpsLocation;     }

@Override
public void onLocationChanged(Location location) {
updatedGpsTime = System.currentTimeMillis();
gpsLocation = location;
if (firstLocation == null)
{             firstLocation = gpsLocation;         }

notifyAboutUpdatedLocation();
}

@Override
public void onProviderDisabled(String provider) {
if (provider.equalsIgnoreCase(LocationManager.GPS_PROVIDER))
{             initBestProvider();         }

}

@Override
public void onProviderEnabled(String provider) {
if (provider.equalsIgnoreCase(LocationManager.GPS_PROVIDER))
{            initBestProvider();        }

}

@Override
public void onStatusChanged(String provider, int status, Bundle extras)
{         // do nothing     }

private void initBestProvider() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0,
0 /* m */, this);
if (firstLocation == null) {
gpsLocation = locationManager
.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (gpsLocation != null)
{                 notifyAboutUpdatedLocation();             }

firstLocation = gpsLocation;
}
}

private void notifyAboutUpdatedLocation()
{         Intent intent = new Intent(MessageManager.PLAYER_LOCATION_CHANGED);
context.sendBroadcast(intent);     }

}

Want to benefit by our experience in mobile application development for Android? Start with your project estimation right now!

This entry was posted on Thursday, October 27th, 2011 at 8:24 am and is filed under Android.