I develop a GPS Tracking app on android. I use AlarmManager to wake up the device every 5 minutes. I developed a log file that shows that the application works great until a moment when I receieve public int onStartCommand(Intent intent, int flags, int startId) with startId = 1 again .... in this case I noticed that all variables are reset so I initialize all variables again .. the issue is that once this happens I keep getting same event with startID = 1 again and after few calls the app just stops until i open the ActivityForm and bind with service again !!!
the log is here for the bad events:
@Jun 17, 2011 3:29:31 AM onStartCommand:flags:0:startId:3:intent:Intent { cmp=com.usegps2_1/.LocationService }
@Jun 17, 2011 3:34:10 AM onStartCommand:flags:0:startId:1:intent:Intent { cmp=com.usegps2_1/.LocationService }
@Jun 17, 2011 3:34:10 AM StartService with mbServiceStarted=TRUE
@Jun 17, 2011 3:34:11 AM call InitGPS
@Jun 17, 2011 3:34:11 AM lastKnownLocation: Location[mProvider=gps,mTime=1308274198000,mLatitude=30.10493179906883,mLongitude=31.379563305705755,mHasAltitude=true,mAltitude=85.0,mHasSpeed=true,mSpeed=0.0,mHasBearing=true,mBearing=313.8908,mHasAccuracy=true,mAccuracy=10.0,mExtras=Bundle[mParcelledData.dataSize=4]]
@Jun 17, 2011 4:48:17 AM onStartCommand:flags:0:startId:1:intent:Intent { cmp=com.usegps2_1/.LocationService }
@Jun 17, 2011 4:48:17 AM StartService with mbServiceStarted=TRUE
@Jun 17, 2011 4:48:17 AM call InitGPS
@Jun 17, 2011 4:48:17 AM lastKnownLocation: Location[mProvider=gps,mTime=1308274198000,mLatitude=30.10493179906883,mLongitude=31.379563305705755,mHasAltitude=true,mAltitude=85.0,mHasSpeed=true,mSpeed=0.0,mHasBearing=true,mBearing=313.8908,mHasAccuracy=true,mAccuracy=10.0,mExtras=Bundle[mParcelledData.dataSize=4]]
GPS Service code is here:
import java.text.DateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.location.LocationManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.Toast;
import android.media.*;
public class LocationService extends Service {
/* ------------------------------------- Attributes */
int NOTIFICATION_ID = 11283;
private NotificationManager mNotificationManager;
public MainActivity mMainActivity = null;
protected PowerManager.WakeLock mWakeLock;
protected AlarmManager mAlarMgr;
protected PendingIntent AlarmIntent ;
protected Notification mNotification;
protected PendingIntent mContentIntent;
protected static final double d2r = Math.PI / 180.0;
// Location Variables
protected static final int mAlarmRepeatRate = 5 * 60 * 1000;
protected static final int mLocationTimeMinUpdate = 20000; // minimum time for wait between signals.
protected static final int mLocationDistanceMinUpdate = 25; // minimum meters before call back
protected static final int TWO_MINUTES = 2 * 60 * 1000; // used to define old readings
// TODO : This should be a function in speed. with min & max value.
protected static final int MIN_MapRefreshRate= 10000 ; // rate used to send to webservice.
protected long mLocationNextSentTime;
public String mDeviceID=null;
public String mBatteryLevel=null;
protected String mMobileServiceURL = "http://locationbook.net/Tracking.asmx";
public Boolean mUseGPSOnly;
public Boolean mEnableBeeps ;
public Boolean mEnableDebug;
public String mSpeedText="0";
public boolean mbBeepStarted = false;
public boolean mbStarted = false;
public boolean mbServiceStarted = false;
public Location mLastLocation =null;
protected LocationManager mlocManager ;
protected MainGpsListener mMainGpsListener;
protected MainLocationListener mlocListenerGPS;
protected MainLocationListener mlocListenerNW;
protected ToneGenerator mTG;
protected float mSpeed;
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
/* EOF Attributes */
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
LocationService getService() {
return LocationService.this;
}
}
/**
* @see android.app.Service#onBind(Intent)
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Put your code here
return mBinder;
}
/**
* @see android.app.Service#onCreate()
*/
@Override
public void onCreate() {
super.onCreate();
// TODO: Input this here to re-create when alarm calls.
mbServiceStarted = false;
com.usegps2_1.Logger.Log(getBaseContext(), "onCreate");
}
/**
* @see android.app.Service#onStart(Intent,int)
*/
@Override
public void onStart(Intent intent, int startId) {
// TODO Put your code here
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
if (mEnableDebug)Logger.Log(getBaseContext(), "onDestroy");
mAlarMgr.cancel(AlarmIntent);
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_0,1500);
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_2,2500);
// Cancel the persistent notification.
mNotificationManager.cancel(NOTIFICATION_ID); // Remove Notification
if (mbStarted == true)
{
mlocListenerNW.mLocationService=null;
mlocListenerGPS.mLocationService=null;
mMainGpsListener.mLocationService=null;
mlocManager.removeUpdates(mlocListenerNW);
mlocManager.removeUpdates(mlocListenerGPS);
mlocManager=null;
}
/** I assume that this function will not be called except when I want to close
* as mWakeLock prevent it from closing. so we can allow closing the screen here.
*/
mWakeLock.release();
// Tell the user we stopped.
Toast.makeText(this, "Service Exit", Toast.LENGTH_SHORT).show();
super.onDestroy();
}
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
/**
* http://android-developers.blogspot.com/2010/02/service-api-changes-starting-with.html
* 1- An application calls startService().
* 2- That service gets onCreate(), onStart(), and then spawns a background thread to do some work.
* 3- The system is tight on memory, so has to kill the currently running service.
* 4- Later when memory is free, the service is restarted, and gets onCreate() called but not onStart()
* because there has not been another call to startService() with a new Intent command to send it.
* START_STICKY is basically the same as the previous behavior,
*
* where the service is left "started" and will later be restarted by the system.
* The only difference from previous versions of the platform is that
* it if it gets restarted because its process is killed,
* onStartCommand() will be called on the next instance of the service with a null Intent instead of not being called at all.
* Services that use this mode should always check for this case and deal with it appropriately.
*
* from Android Book.
* [2] The flag parameter can be used to discover how the Service was started. In particular you can use the
* code snippet shown in Listing 9-2 to determine if either of the following cases is true:
* ? START_FLAG_REDELIVERY Indicates that the Intent parameter is a redelivery caused by the
* system run time’s having terminated the Service before it was explicitly stopped by a call to stopSelf.
* ? START_FLAG_RETRY Indicates that the Service has been restarted after an abnormal termination.
* Passed in when the Service was previously set to START_STICKY.
*/
com.usegps2_1.Logger.Log(getBaseContext(), "onStartCommand:flags:" + String.valueOf(flags) + ":startId:" + String.valueOf(startId)+ ":intent:" + intent.toString());
if (intent == null)
{
// TODO If it’s a restart, do something.
updateNotification("Restarted");
if (mTG != null)
{
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_5,2000);
}
}
//mAlarMgr.cancel(null);
// see [2]
if ((flags & START_FLAG_RETRY) == 0) {
// FROM DEBUGGING the above condition is wrong it should be != 0.
// PLease check and review before writing code here.
}
else {
// TODO Alternative background process.
}
StartService();
return START_STICKY;
}
/**
* Read preference settings and apply them.
*/
public void ApplyPreferenceSttings()
{
mMobileServiceURL = CustomPreferenceManager.GetServiceURL(getApplication());
mUseGPSOnly= CustomPreferenceManager.GetGPSOnly(getApplication());
mEnableBeeps= CustomPreferenceManager.GetBeepsEnabled(getApplication()) ;
mEnableDebug = CustomPreferenceManager.GetDebugEnabled(getApplication());
}
/**
* Called from outside to start service.
*/
public void StartService ()
{
if (mbServiceStarted == false)
{
mbServiceStarted = true;
// TODO: please remember to use PARTIAL_WAKE_LOCK instead of .SCREEN_DIM_WAKE_LOCK
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "My Tag");
mWakeLock.acquire();
ApplyPreferenceSttings();
if (mEnableDebug)Logger.Log(getBaseContext(), "StartService re-initialize=TRUE");
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
mDeviceID = telephonyManager.getDeviceId();
showNotification();
mTG = new ToneGenerator (1,80);
if (mAlarMgr != null)
if (mEnableDebug)Logger.Log(getBaseContext(), "mAlarMgr is true");
mAlarMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intentToFire = new Intent (GPSBroadcastReceiver.ReceiverName);
AlarmIntent = PendingIntent.getBroadcast(this, 0, intentToFire, 0);
mAlarMgr.cancel(AlarmIntent); //Remove any alarms with a matching Intent. [BUG] avoid creating many alarms ... caused multipl alarm call with mbServiceStarted=False when check debugging files.
mAlarMgr.setRepeating(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime() + mAlarmRepeatRate , mAlarmRepeatRate, AlarmIntent);
// Register in Battery
this.registerReceiver(this.mBatInfoReceiver,new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
StartLocation();
}
else
{
//updateNotification("called again while running");
}
// */
}
protected void showNotification ()
{
mNotificationManager = (NotificationManager) getSystemService (Context.NOTIFICATION_SERVICE);
// Define Notification
mNotification = new Notification(R.drawable.step, "Tracking on", System.currentTimeMillis());
mNotification.flags = Notification.FLAG_ONGOING_EVENT;
// Define Notification Action
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
mContentIntent = PendingIntent.getActivity(LocationService.this, 0,intent, PendingIntent.FLAG_CANCEL_CURRENT);
// Set Notification
mNotification.setLatestEventInfo(LocationService.this,getText(R.string.app_name),"click to display main screen",mContentIntent);
// Add it
mNotificationManager.notify(NOTIFICATION_ID, mNotification);
}
protected void updateNotification (CharSequence Description)
{
if (mNotification ==null) return ;
mNotification.when=System.currentTimeMillis();
// Set Notification
mNotification.setLatestEventInfo(LocationService.this,getText(R.string.app_name),Description,mContentIntent);
// Add it
mNotificationManager.notify(NOTIFICATION_ID, mNotification);
}
/*-------------------------------------------------GPS Methods*/
protected void StartLocation ()
{
if (mbStarted == true)
{
Toast.makeText( getApplicationContext(),"already running",Toast.LENGTH_SHORT).show();
return ;
}
if (mEnableDebug)Logger.Log(getBaseContext(), "InitGPS true");
mbStarted = true;
mLocationNextSentTime = 0 ; // Send once u get data
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_1,1000);
// Use the LocationManager class to obtain GPS locations
mlocManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Toast.makeText( getApplicationContext(),"Started",Toast.LENGTH_SHORT).show();
Location loc= mlocManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
// try to read current position
if(loc != null){
mLastLocation=loc;
if (mEnableDebug)Logger.Log(getBaseContext(), "lastKnownLocation: " + loc.toString());
}
else
{
updateNotification ("getting location");
}
mlocListenerGPS = new MainLocationListener(this);
mlocListenerNW = new MainLocationListener(this);
mlocListenerGPS.mProvider="GPS";
mlocListenerNW.mProvider="NW";
mMainGpsListener = new MainGpsListener(this);
// Define a listener that responds to location updates
mlocManager.addGpsStatusListener(mMainGpsListener);
mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, mLocationTimeMinUpdate, mLocationDistanceMinUpdate, mlocListenerGPS);
mlocManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, mLocationTimeMinUpdate, mLocationDistanceMinUpdate, mlocListenerNW);
if (mEnableDebug)Logger.Log(getBaseContext(), "GPS reinitialized");
// */
}
public void UpdateLocation (Location loc, String Provider)
{
try
{
if (mUseGPSOnly && (Provider != "GPS")) return ;
if (mbStarted == false)
{
updateNotification("false");
}
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_9,200);
String E = loc.toString();
if (mEnableDebug)Logger.Log(getBaseContext(), "New Location");
if (mEnableDebug)Logger.Log(getBaseContext(), loc.toString());
if (isBetterLocation (loc,mLastLocation)==false)
{
updateNotification ("location ignored");
return ;
}
mLastLocation = loc;
mMainActivity.mtxtTime.setText(DateFormat.getDateInstance().format(new Date()));
mMainActivity.mtxtLatitude.setText(loc.getLatitude() + " lat");
mMainActivity.mtxtLongitude.setText(loc.getLongitude() + " lng");
float speed = loc.getSpeed(); // value if set by GetSpeed that is called insite isBetterLocation
mSpeedText=Float.toString(speed) ;
mMainActivity.mtxtSpeed.setText(mSpeedText + " km/s");
if ((loc.getTime() > mLocationNextSentTime))
{
mLocationNextSentTime = (long) (loc.getTime() + MIN_MapRefreshRate * ( 140 - speed)/ 140);
if (mEnableDebug)Logger.Log(getBaseContext(), "Next HTTP: " + String.valueOf(mLocationNextSentTime));
WebMethodProxy client = new WebMethodProxy(mMobileServiceURL + "/UpdateLocation");
client.AddParam("guid", mDeviceID);
client.AddParam("latitude",Double.toString(loc.getLatitude()));
client.AddParam("longitude", Double.toString(loc.getLongitude()));
client.AddParam("speed", mSpeedText);
client.AddParam("battery",mBatteryLevel);
//if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_5,500);
try {
client.Execute(WebMethodProxy.RequestMethod.POST_JSON);
} catch (Exception e) {
e.printStackTrace();
// TODO : error message cannot connect to server
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_9,2500);
String err = (e.getMessage() == null)?"GPS Error":e.getMessage();
if (mEnableDebug)Logger.Log(getBaseContext(), "Failed: " + err);
Log.e("TrackGPS",err);
mMainActivity.mtxtMessage.setText("Cannot reach Internet to update location.");
return ;
}
String response = client.getResponse();
response = (response ==null)?"no web reply":response;
if (mEnableDebug)Logger.Log(getBaseContext(), "HTTP DONE: " + response );
Toast.makeText( getApplicationContext(),"Updated[" + Provider + "]:" + response,Toast.LENGTH_SHORT).show();
updateNotification ("location updated");
}
else
{
updateNotification ("location ignored.");
}
}
catch (Exception e)
{
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_2,2500);
String err = (e.getMessage() == null)?"GPS Error":e.getMessage();
Log.e("TrackGPS",err);
updateNotification ("DEBUG1: " + e.getMessage());
return ;
}
}
/**
* Returns distance in meters between two points.
* @param NewLocation
* @param CurrentLocation
* @return
*/
protected float CalculateSpeed (Location NewLocation, Location CurrentLocation) {
try
{
double dlong = (NewLocation.getLongitude() - CurrentLocation.getLongitude()) * d2r;
double dlat = (NewLocation.getLatitude() - CurrentLocation.getLatitude()) * d2r;
double a = Math.pow(Math.sin(dlat/2.0), 2) + Math.cos(CurrentLocation.getLatitude()*d2r) * Math.cos(NewLocation.getLatitude()*d2r) * Math.pow(Math.sin(dlong/2.0), 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double d = 6367000 * c;
double TimeDelta = NewLocation.getTime() - CurrentLocation.getTime();
return (float) (d/TimeDelta) * 3600;
}
catch (Exception e)
{
if (mEnableBeeps) mTG.startTone(android.media.ToneGenerator.TONE_DTMF_2,2500);
Log.e("TrackGPS",e.getMessage());
updateNotification ("DEBUG2: " + e.getMessage());
return 0;
}
}
/** Determines whether one Location reading is better than the current Location fix
* Logic:
* if too old return FALSE.
* if too new return TRUE anyway as the current is too old.
* if more accurate then return TRUE
* if newer and same or more accurate then return TRUE.
* if newer and less accurate but same provider return TRUE.
* ------------------------------------------------------
* Time Accuracy Same Provider Return
* ------------------------------------------------------
* Too Old x x FALSE
* Too New x x TRUE
* Older Plus x TRUE
* Newer Plus x TRUE
* Newer Same x TRUE
* Newer Less TRUE TRUE
* ======================================================
* @param location The new Location that you want to evaluate
* @param currentBestLocation The current Location fix, to which you want to compare the new one
*/
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
try
{
location.setSpeed(0); // preset
if (currentBestLocation == null) {
// A new location is always better than no location
if (mEnableDebug)Logger.Log(getBaseContext(), "Accepted: first location");
return true;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
if (mEnableDebug)Logger.Log(getBaseContext(), "Accepted: isSignificantlyNewer");
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
if (mEnableDebug)Logger.Log(getBaseContext(), "Rejected: isSignificantlyOlder");
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
location.setSpeed(CalculateSpeed (location,currentBestLocation));
if (mEnableDebug)Logger.Log(getBaseContext(), "Accepted: isMoreAccurate");
return true;
} else if (isNewer && !isLessAccurate) {
location.setSpeed(CalculateSpeed (location,currentBestLocation));
if (mEnableDebug)Logger.Log(getBaseContext(), "Accepted: isNewer and not isLessAccurate");
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
location.setSpeed(CalculateSpeed (location,currentBestLocation));
if (mEnableDebug)Logger.Log(getBaseContext(), "Accepted: isNewer and LessAccurate but from same provider");
return true;
}
if (mEnableDebug)Logger.Log(getBaseContext(), "Rejected: ???");
return false;
}
catch (Exception e)
{
updateNotification ("Debug3: " + e.getMessage());
return false;
}
}
/** Checks whether two providers are the same */
protected boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
/*GPS Methods:EOF*/
/*-------------------------------------------------BAT Methods*/
protected BroadcastReceiver mBatInfoReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context arg0, Intent intent) {
int level = intent.getIntExtra("level", 0);
double voltage= intent.getIntExtra("voltage", 0) ;
double batteryTemperature = intent.getIntExtra("temperature", 0);
// update battery level.
mBatteryLevel = String.valueOf(level);
if (level < 31)
{
mMainActivity.mtxtBatteryInfo.setText("level: " + Double.toString(level) + " pls connect to charger.");
mMainActivity.mtxtBatteryInfo.setTextColor(0xffff0033);
}
else
{
mMainActivity.mtxtBatteryInfo.setText("level: " + Double.toString(level) + "% voltage: " + String.valueOf(Double.toString(voltage / 1000.0)) + " Temp: " + Double.toString(batteryTemperature /10.0) + " c");
mMainActivity.mtxtBatteryInfo.setTextColor(0xff99ff33);
}
}
};
/*BAT Methods:EOF*/
}