android gps location alarmmanager

android - AlarmManager y onStartCommand



gps location (1)

Desarrollo una aplicación de seguimiento GPS en Android. Uso AlarmManager para reactivar el dispositivo cada 5 minutos. Desarrollé un archivo de registro que muestra que la aplicación funciona muy bien hasta que recibo public int onStartCommand (intención intención, int flags, int startId) con startId = 1 otra vez ... en este caso noté que todas las variables se reinician así que inicializo todas las variables otra vez ... ¡el problema es que una vez que esto sucede, sigo obteniendo el mismo evento con startID = 1 nuevamente y después de algunas llamadas la aplicación simplemente se detiene hasta que abro el ActivityForm y vuelvo a enlazar con el servicio!

el registro está aquí para los eventos malos:

@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]]

El código del servicio GPS está aquí:

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*/ }


Puedo ver una serie de problemas aquí.

En primer lugar, onStart (Intent intent, int startId) está en desuso, no es necesario que implemente este método (especialmente si todo lo que hace es super.onStart(intent, startId); ).

En segundo lugar, no necesita cancelar la alarma que se repite antes de configurarla, ya que el administrador de alarmas lo hará por usted.

Lo más importante es que solo debe adquirir el bloqueo de activación durante la duración de su método onStartCommand. Adquiéralo al principio, luego suéltalo al final, idealmente dentro de un bloque finally para que, en caso de una excepción, el bloqueo se libere. En este momento, se adquiere el bloqueo la primera vez que se recibe un intento, y luego se retiene hasta que finaliza el servicio, lo que impide que la CPU hiberne cuando no tiene nada que hacer, lo que afectará la vida útil de la batería.

En cuanto a la repetida entrega de la intención, si las variables de servicio se reinicializan, parece que su servicio se reiniciará con Android porque se ha perdido. ¿Ha comprobado el logcat (cuando se ejecuta conectado) para cualquier rastro de pila? Tal vez agregue el ID del proceso a su salida de registro para que pueda ver cuándo sucede esto.

Es posible que Android esté matando a su servicio, si cree que ha dejado de responder. Ha procesado un poco, tal vez quiera pensar en descomponer esto en clases separadas, entonces será más fácil comprender qué está haciendo el servicio y dónde está yendo mal.