AlarmManager & onStartCommand
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 mlocListen开发者_如何学GoerGPS;
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*/
}
I can see a number of issues here.
First, onStart(Intent intent, int startId) is deprecated, you don't need to implement this method (especially if all you do is super.onStart(intent, startId);
).
Second, you don't need to cancel the repeating alarm before you set it, as the alarm manager will do that for you.
Most importantly, you should only acquire the wake lock for the duration of your onStartCommand method. Acquire it at the beginning, then release it at the end - ideally inside a finally
block so that in the event of an exception the lock is still released. At the moment the lock is acquired the first time an intent is received, and then held until the service is terminated, preventing the CPU from hibernating when it has nothing to do - this will affect battery life.
As for repeated delivery of the intent - if your service variables are being re-initialised then it sounds like your service is being restarted by Android because it died. Have you checked in the logcat (when running connected) for any stack traces? Maybe add the process ID to your log output so you can spot when this is happening.
It is possible that Android is killing your service, if it thinks it has become unresponsive. You've put quite a bit of processing in, you might want to think about decomposing this into separate classes - then it will be easier to comprehend what the service is doing and where it is going wrong.
精彩评论