In developing a location-aware application using JSR-179 on the Nokia 6110 I noticed some nasty problems with getLocation() leading to device lock-ups.
My app calls getLocation regularly, but infrequently (explanation below). Here are some of the problems I found:
(A) getLocation sometimes (apparently randomly) blocks forever regardless of the timeout value; the MIDlet can be terminated but does not close cleanly. In this state the CPU appears to be under high load - response in the UI is sluggish. Any app that attempts to access the GPS (eg. Navigator, or GPS Data) will hang. The phone must be power cycled to resume normal operations.
(B) In long-running apps, getLocation eventually stops working if no fix is available. It fails to wait for the timeout period but returns immediately. Other apps that attempt to connect to the GPS server return "No Connection". Eventually, other apps cannot be started with the phone giving "Memory full. Close some applications and try again". To me this suggests a resource leak in the handling of location exceptions.
This normally happens when the phone has had a GPS fix and lost it again.
Some background: My app needs to sample position infrequently, but must run continuously. JSR-179 offers two ways to access location information. You can do blocking (synchronous) calls to LocationProvider.getLocation which wait until a location is known or a timeout period expires. Alternatively, you can use LocationProvider.setLocationListener to register for a notification (asyncrhonous) when location information is available.
Recently nicholso reported a problem with the asynchronous API: when you remove the location listener and reset the provider, the GPS does not stop sampling if it already has a position fix. This happens on my 6110 - instead of entering a low-power mode it continues to consume about 120mA, giving a battery life of less than 8 hours. I don't want my users to have to recharge the phone more than once a day. I need at least 16 to 20 hours battery life, so I tried using a thread to periodically call getLocation, allowing the GPS to power down between samples. Unfortunately, getLocation is quite buggy and eventually the entire GPS system hangs along with the MIDlet.
I've included some source code to reproduce these problems. I have two handsets which behave identically: one has the original firmware (V03.51, RM-122 Nokia 6110 24.02), and one was upgraded to the latest version (V04.22, RM-122 Nokia 6110 24.01).
The MIDlet has two parameters: wait and timeout. In "synchronous" mode it calls getLocation(timeout), waiting for <wait> seconds between samples. In "asyncrhonous" mode it uses setLocationListener with <wait+timeout> as the interval.
To reproduce the problem:
- Start the MIDlet and set the timing parameters. The shorter the times, the less you have to wait. The app makes an initial reading (to get rid of the annoying permission request) which may fail.
- Select "Start Sync" to begin sampling using getLocation.
- Get a GPS fix, then lose it. For example: walk outside until you get a location, then back inside again until it is lost.
- Wait. If you have the right conditions the app will eventually hang - the display will stop updating. The phone will respond sluggishly and GPS apps will lock up. Alternatively, you may stop seeing the "getLocation" message because it fails to wait for the timeout period to expire. Try launching "GPS Data" and you will get either "No Connection" or "Memory Full".
With timeout=1 and wait=1 I find it normally takes 10 to 15 minutes for the problem to occur, though it may depend on location.
Based on my experience it seems impossible to implement a long-running GPS app using Nokia's JSR-179 due to a combination of bugs across the entire API. Has anyone managed to get a GPS MIDlet to run for more than a few hours on a single charge?
Can anyone confirm this behaviour on other devices?
Cheers,
Stewart
Code:import javax.microedition.lcdui.*; import javax.microedition.midlet.*; import javax.microedition.location.*; import java.util.Date; import java.io.PrintStream; public class TestMIDlet extends MIDlet implements CommandListener, LocationListener { private Command exitAppCommand = new Command("Exit", Command.EXIT, 1); private Command startSyncCommand = new Command("Start Sync", Command.SCREEN, 1); private Command startAsyncCommand = new Command("Start Async", Command.SCREEN, 1); private Command stopCommand = new Command("Stop", Command.SCREEN, 1); private Command resetCommand = new Command("Reset GPS", Command.SCREEN, 1); private Command exitOptionsCommand = new Command("Exit Options", Command.EXIT, 1); private Command optionsCommand = new Command("Options", Command.SCREEN, 1); private Form mainForm; private Form optionsForm; private TextField optTimeout; private TextField optWait; int timeout = 1; int wait=1; long startTime; long fixTime; private LocationProvider provider; private int gpsState = 0; TextField AppendNumericField(String name) { TextField f = new TextField(name, "", 6, TextField.NUMERIC); f.setLayout(Item.LAYOUT_SHRINK + Item.LAYOUT_RIGHT); optionsForm.append(f); return f; } void createOptionsForm() { optionsForm = new Form("Options"); optTimeout = AppendNumericField("Timeout"); optWait = AppendNumericField("Wait"); optionsForm.addCommand(exitOptionsCommand); optionsForm.setCommandListener(this); } void createMainForm() { mainForm = new Form("Test GPS"); mainForm.addCommand(startSyncCommand); mainForm.addCommand(startAsyncCommand); mainForm.addCommand(stopCommand); mainForm.addCommand(resetCommand); mainForm.addCommand(optionsCommand); mainForm.addCommand(exitAppCommand); mainForm.setCommandListener(this); } int ReadInteger(TextField t) throws Exception { return Integer.parseInt(t.getString()); } boolean readOptions() { try { int timeout1 = ReadInteger(optTimeout); int wait1 = ReadInteger(optWait); if (timeout1<=0) throw new Exception("Timeout must be greater than 0"); if (wait1<0) throw new Exception("Wait must not be negative"); timeout = timeout1; wait = wait1; return true; } catch (Exception e) { Display.getDisplay(this).setCurrent(new Alert("Error", "Exception " + e, null, AlertType.ERROR)); return false; } } void writeOptions() { optWait.setString(Integer.toString(wait)); optTimeout.setString(Integer.toString(timeout)); } void StartGPS() { startTime = System.currentTimeMillis(); try { if (provider == null) { provider = LocationProvider.getInstance(null); mainForm.append("Started GPS\n"); provider.getLocation(1); } } catch (Exception e) { mainForm.append("StartGPS: " + e + "\n"); } } void showTime() { long time = System.currentTimeMillis(); mainForm.deleteAll(); mainForm.append(new Date(time)+"\n"); mainForm.append("running: " + ((time-startTime)/1000) + "s\n"); mainForm.append("last fix: " + ((fixTime == 0) ? "none\n" : ((time-fixTime)/1000) + "s\n")); } void handleLocation(Location loc) { if (loc.isValid()) { fixTime = System.currentTimeMillis(); QualifiedCoordinates coord = loc.getQualifiedCoordinates(); mainForm.append("lat: " + coord.getLatitude() + "\n"); mainForm.append("lon: " + coord.getLongitude() + "\n"); } else { mainForm.append("Invalid location\n"); } } public void locationUpdated(LocationProvider p, final Location loc) { new Thread() { public void run() { showTime(); handleLocation(loc); } } .start(); } public void providerStateChanged(LocationProvider p, int state) {} class Timer extends Thread { void waitForDeadline(int period) { period *= 1000; mainForm.append("sleep("+period+")\n"); try { Thread.sleep(period); } catch (InterruptedException e) { } } void getLocation(int timeout) { try { mainForm.append("getLocation("+timeout+")...\n"); Location loc = provider.getLocation(timeout); showTime(); handleLocation(loc); } catch (Exception e) { showTime(); mainForm.append("Exception " + e + "\n"); return; } } public void run() { mainForm.append("Timer started\n"); while (true) { switch (gpsState) { case 0: // idle try { Thread.sleep(1000); } catch (InterruptedException e) {} break; case 1: // sampling waitForDeadline(wait); getLocation(timeout); break; } } } }; Timer timer; public TestMIDlet() { createMainForm(); createOptionsForm(); } void stop() { if (provider != null) provider.setLocationListener(null, -1, -1, -1); gpsState = 0; } public void commandAction(Command c, Displayable s) { if (c == exitAppCommand) { destroyApp(true); notifyDestroyed(); } else if (c==startSyncCommand) { stop(); gpsState = 1; } else if (c==startAsyncCommand) { stop(); if (provider != null) provider.setLocationListener(this, timeout + wait, timeout, -1); } else if (c==stopCommand) { stop(); } else if (c==optionsCommand) { writeOptions(); Display.getDisplay(this).setCurrent(optionsForm); } else if (c==resetCommand) { if (provider != null) { mainForm.append("Reset GPS\n"); provider.reset(); } } else if (c==exitOptionsCommand) { if (readOptions()) Display.getDisplay(this).setCurrent(mainForm); } } public void destroyApp(boolean unconditional) { } public void pauseApp() { } public void startApp() { Display.getDisplay(this).setCurrent(mainForm); if (timer == null) { timer = new Timer(); timer.start(); } StartGPS(); } }

Reply With Quote



