Here is some example code for my work-around.
For the MIDlet:
Code:
package example;
import java.io.IOException;
import java.util.Date;
import javax.microedition.io.PushRegistry;
import javax.microedition.midlet.MIDlet;
import com.sun.lwuit.Command;
import com.sun.lwuit.Display;
import com.sun.lwuit.Form;;
import com.sun.lwuit.events.ActionListener;
import example.RunState;
import example.Engine;
public class MainMidlet extends MIDlet implements ActionListener
{
// STATICS-------------------------------------------------------
static public RunState runState = new RunState(); //default for first run
static public boolean FIRST_RUN = true;
static public boolean LAST_RUN = false; //!!!loop by default, is overridden when user exits or when a crash happens
static public int TIME_TO_LIVE = 7*60*1000; //in ms! (7')
static public int RESTART_DELAY = 5*1000; //5"
// DYNAMICS------------------------------------------------------
private Form current_form;
private Engine engine;
private Logger log = Logger.getInstance();
private Thread mainThread;
public void startApp()
{
try
{
//Initialize RunState:
checkPreviousRun();
//initialize & show GUI
//...
//do stuff:
engine.start(runState); //does the actual work (records and processes audio) and uses runState to initialize itself
}
catch(Exception e)
{
log.error(e.getMessage());
LAST_RUN = true; //the program crashed: don't restart
}
countDown(); //!!! program should end after TIME_TO_LIVE
}
public void actionPerformed(ActionEvent actionEvent) //event handler for GUI
{
if(cmdExit.equals(actionEvent.getCommand())) //exit command
{
LAST_RUN = true; //!!! user wants to exit, so don't restart
midlet.destroyApp(true);
}
//...
}
public void destroyApp(boolean unconditional)
{
engine.stop(); //!!! will update the runstate
if(LAST_RUN)
{
log.debug("Exit: User"); //(or crash)
undoRescheduling(); //user chose to exit (or there was a crash), so don't reschedule
synchronized(mainThread)
{
mainThread.interrupt(); //interrupt waiting thread so MIDlet can exit immediately
}
}
else //not the last run: reschedule & keep state
{
log.debug("Exit: Time-up");
log.debug("Writing runstate...");
runState.save(); //saves a small file to memory that contains run state information for the next execution
scheduleNextRun(); //!!!
}
notifyDestroyed(); //really exit
}
private void checkPreviousRun()
{
log.debug("Reading previous runstate...");
RunState previousState = RunState.load();
if(previousState != null)
log.debug("Previous runstate: " + previousState.prettyPrint());
else
log.debug("No previous runstate found");
if( previousState != null && //there IS a previous runstate (loaded from file)
previousState.getStopTime() > -1 && //the previous runstate has a valid stopTime
previousState.getStopTime() + MainMidlet.RESTART_DELAY + 30*1000 >= runState.getStartTime()) //previous state is too old, discard, treat this execution as a first run
{
FIRST_RUN = false;
runState = previousState;
runState.incrementRunCount();
}
log.debug("The current execution is" + (FIRST_RUN ? "" : " not") + " the first run");
}
public void countDown()
{
try
{
mainThread = Thread.currentThread();
synchronized(mainThread)
{
mainThread.wait(TIME_TO_LIVE);
}
}
catch(InterruptedException e)
{
log.debug("Waiting thread interruped");
return; //!!! skip code below
}
//Time up...
LAST_RUN = false; //(just to be sure)
destroyApp(true);
}
public void scheduleNextRun()
{
if(!LAST_RUN)
{
log.debug("Scheduling next run");
long previousRun = 0;
try
{
previousRun = PushRegistry.registerAlarm(this.getClass().getName(), (new Date()).getTime() + TIME_TO_LIVE + RESTART_DELAY); //5 seconds in between runs
//Note: new Date()).getTime() = current time as a long timestamp (in milliseconds)
}
catch(ClassNotFoundException cnf)
{
log.error("Error scheduling next run (class not found): " + cnf.getMessage());
}
catch(javax.microedition.io.ConnectionNotFoundException connnf)
{
log.error("Error scheduling next run (connection not found): " + connnf.getMessage());
}
if(previousRun > 0 && previousRun < System.currentTimeMillis())
{
log.debug("Time of previous run: " + StringUtils.formatDateTime(previousRun, "/", ":", " ")); //this does not always seem to be correct so I use my RunState class/file instead
}
}
}
public static RunState getRunState()
{
return runState;
}
}
Here is some example code for the RunState class which I created to pass state information between MIDlet executions:
Code:
package example;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import example.MainMidlet;
public class RunState
{
private static final String STATE_FILE_NAME = "running";
private static final char separator = '|';
private int runCount = 1;
private long startTime;
private long stopTime = -1; //running
//...add additional state fields here
public RunState() //default constructor, for first run
{
this.startTime = System.currentTimeMillis();
this.runCount = 1;
//initialize additional state fields here
}
public RunState(String state) //constructor that initializes from a String (contents of run state file)
{
String[] parts = StringUtils.split(state, separator); //uses a string splitting function I made
this.runCount = Integer.parseInt(parts[0]);
this.startTime = Long.parseLong(parts[1]);
this.stopTime = Long.parseLong(parts[2]);
//parse additional state fields here
}
public String toString() //serialize run state to write to file
{
return Integer.toString(runCount) + separator +
Long.toString(startTime) + separator +
Long.toString(stopTime)
//serialise additional state fields here, adding a separator in front of each additional one
;
}
public static RunState load()
{
RunState runState = null;
String filePath = MainMidlet.getInstance().getPreferences().getDataFolderPath() + STATE_FILE_NAME;
try
{
FileConnection fc = (FileConnection) Connector.open(filePath, Connector.READ_WRITE);
if(fc.exists())
{
InputStreamReader reader = new InputStreamReader(fc.openInputStream());
StringBuffer bff = new StringBuffer();
for(int c = reader.read(); c != -1; c = reader.read())
bff.append((char) c);
if(bff.length() > 0)
runState = new RunState(bff.toString()); //initialize runstate
reader.close();
fc.delete(); //delete the file! (so it is not accidently re-used)
fc.close();
}
}
catch(Exception e)
{
(Logger.getInstance()).debug("Could not load runstate: " + e.getMessage());
}
return runState;
}
public void save() //shorthand for static method
{
RunState.save(this);
}
public static void save(RunState runState)
{
String filePath = MainMidlet.getInstance().getPreferences().getDataFolderPath() + STATE_FILE_NAME;
try
{
FileConnection fc = (FileConnection) Connector.open(filePath, Connector.READ_WRITE);
if(fc.exists())
fc.truncate(0); //empty out the existing file
else
fc.create(); //create file
OutputStreamWriter writer = new OutputStreamWriter(fc.openOutputStream());
writer.write(runState.toString()); //serialize
writer.flush();
writer.close();
}
catch(Exception e)
{
(Logger.getInstance()).debug("Could not save run state: " + e.getMessage());
}
}
public int getRunCount()
{
return runCount;
}
public void incrementRunCount()
{
this.runCount++;
}
public long getStopTime()
{
return stopTime;
}
public void setStopTime(long stopTime)
{
this.stopTime = stopTime;
}
public long getStartTime()
{
return startTime;
}
//add getters & setters for additional state fields here
}
Good luck!