Enviar mensaje a un controlador sobre un hilo muerto al obtener una ubicación de un servicio de intendencia

Mi aplicación necesita correcciones de ubicación regularmente, incluso cuando el teléfono no está activo. Para hacer esto, estoy usando IntentService con el patrón proporcionado generosamente por Commonsware. https://github.com/commonsguy/cwac-wakeful

Para obtener soluciones de ubicación, confío en el siguiente código proporcionado por un miembro llamado Fedor. ¿Cuál es la forma más simple y sólida de obtener la ubicación actual del usuario en Android? . Lo modifiqué ligeramente para devolver nulo en lugar de obtener la última ubicación conocida

Ambos funcionan perfectamente bien cuando no están combinados: Puedo obtener una corrección de ubicación de la clase de Fedor en una Actividad, y puedo hacer algunas cosas básicas (como iniciar sesión en algo) usando la clase de Commonsware.

El problema ocurre cuando bash obtener soluciones de ubicación de la subclase WakefulIntentService de Commonsware. Los LocationListeners no reactjsn a LocationUpdates y recibo estas advertencias (todavía no conozco las sutilezas de los hilos, los manipuladores, …). ¿Alguien puede ayudarme a entender cuál es el problema? Gracias y les deseo a todos un muy feliz año nuevo 🙂

12-31 14:09:33.664: W/MessageQueue(3264): Handler (android.location.LocationManager$ListenerTransport$1) {41403330} sending message to a Handler on a dead thread 12-31 14:09:33.664: W/MessageQueue(3264): java.lang.RuntimeException: Handler (android.location.LocationManager$ListenerTransport$1) {41403330} sending message to a Handler on a dead thread 12-31 14:09:33.664: W/MessageQueue(3264): at android.os.MessageQueue.enqueueMessage(MessageQueue.java:196) 12-31 14:09:33.664: W/MessageQueue(3264): at android.os.Handler.sendMessageAtTime(Handler.java:473) 12-31 14:09:33.664: W/MessageQueue(3264): at android.os.Handler.sendMessageDelayed(Handler.java:446) 12-31 14:09:33.664: W/MessageQueue(3264): at android.os.Handler.sendMessage(Handler.java:383) 12-31 14:09:33.664: W/MessageQueue(3264): at android.location.LocationManager$ListenerTransport.onLocationChanged(LocationManager.java:193) 12-31 14:09:33.664: W/MessageQueue(3264): at android.location.ILocationListener$Stub.onTransact(ILocationListener.java:58) 12-31 14:09:33.664: W/MessageQueue(3264): at android.os.Binder.execTransact(Binder.java:338) 12-31 14:09:33.664: W/MessageQueue(3264): at dalvik.system.NativeStart.run(Native Method) 

Esta es mi subclase WakefulIntentService:

 public class AppService extends WakefulIntentService { public static final String TAG = "AppService"; public AppService() { super("AppService"); } public LocationResult locationResult = new LocationResult() { @Override public void gotLocation(final Location location) { if (location == null) Log.d(TAG, "location could not be retrieved"); else sendMessage(location); } }; @Override protected void doWakefulWork(Intent intent) { PhoneLocation myLocation; myLocation = new PhoneLocation(); myLocation.getLocation(getApplicationContext(), locationResult); } 

Y aquí una copia de la clase de Fedor (ligeramente modificada: sin necesidad de GPS ni última ubicación conocida)

 public class PhoneLocation { public static String TAG = "MyLocation"; Timer timer1; LocationManager lm; LocationResult locationResult; boolean gps_enabled=false; boolean network_enabled=false; public boolean getLocation(Context context, LocationResult result) { //I use LocationResult callback class to pass location value from MyLocation to user code. locationResult=result; if(lm==null) lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); //exceptions will be thrown if provider is not permitted. try{gps_enabled=lm.isProviderEnabled(LocationManager.GPS_PROVIDER);}catch(Exception ex){} try{network_enabled=lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);}catch(Exception ex){} //don't start listeners if no provider is enabled if(!gps_enabled && !network_enabled) return false; if(network_enabled) lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork); timer1=new Timer(); timer1.schedule(new ReturnNullLocation(), 20000); return true; } LocationListener locationListenerNetwork = new LocationListener() { public void onLocationChanged(Location location) { timer1.cancel(); locationResult.gotLocation(location); lm.removeUpdates(this); } public void onProviderDisabled(String provider) {} public void onProviderEnabled(String provider) {} public void onStatusChanged(String provider, int status, Bundle extras) {} }; class ReturnNullLocation extends TimerTask { @Override public void run() { lm.removeUpdates(locationListenerNetwork); locationResult.gotLocation(null); } } public static abstract class LocationResult{ public abstract void gotLocation(Location location); } 

}

No puede registrar a los oyentes de manera segura desde un servicio de IntentService , ya que IntentService desaparece tan pronto como se completa onHandleIntent() (aka, doWakefulWork() ). En cambio, necesitará usar un servicio regular, además de manejar detalles como tiempos de espera (por ejemplo, el usuario está en un sótano y no puede obtener una señal de GPS).

Tuve una situación similar y utilicé las instalaciones de Android Looper con buenos resultados: http://developer.android.com/reference/android/os/Looper.html

En concreto, justo después de llamar a la función que configura el oyente, llamo:

 Looper.loop(); 

Eso bloquea el hilo actual para que no muera, pero al mismo tiempo le permite procesar eventos, por lo que tendrá la oportunidad de recibir notificaciones cuando se llame a su oyente. Un bucle simple evitaría que recibas notificaciones cuando se llame al oyente.

Y cuando se llama al oyente y termino de procesar sus datos, puse:

 Looper.myLooper().quit(); 

Para otros como yo: tuve un problema similar relacionado con AsyncTask . Según entiendo, esa clase asume que se inicializa en el hilo de la interfaz de usuario (principal).

Una solución alternativa (una de varias posibles) es colocar lo siguiente en la clase MyApplication :

 @Override public void onCreate() { // workaround for http://code.google.com/p/android/issues/detail?id=20915 try { Class.forName("android.os.AsyncTask"); } catch (ClassNotFoundException e) {} super.onCreate(); } 

(no olvides mencionarlo en AndroidManifest.xml :

  

)