2011-12-31 10 views
34

Mi aplicación necesita soluciones de ubicación de manera regular, 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-wakefulEnviando un mensaje a un controlador sobre un hilo inactivo al obtener una ubicación de un servicio de intención

Para obtener soluciones de ubicación, confío en el siguiente código proporcionado por un miembro llamado Fedor. What is the simplest and most robust way to get the user's current location on Android?. Lo modifiqué ligeramente para que devuelva nulo en lugar de obtener la última ubicación conocida

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

El problema se produce cuando intento solucionar problemas de ubicación de la subclase WakefulIntentService de Commonsware. Los LocationListeners no reaccionan 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) 

Ésta es mi WakefulIntentService subclase:

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 modificado: no hay necesidad de GPS ni el último 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); 

} 

}

Respuesta

33

no es posible registrar de manera segura los oyentes de una IntentService, ya que el IntentService desaparece tan pronto como se completa onHandleIntent() (a.k.a., doWakefulWork()). En su lugar, deberá 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).

+1

Funciona como un encanto! Muchas gracias! He configurado la alarma en mi subclase de aplicación y he creado los métodos startLocationPoll() y stopLocationPoll() allí. ¡Feliz año nuevo! – znat

+0

cuando uso el ejemplo en el enlace ... tengo ... "E/LocationPoller (442): Invalid Intent - no tiene proveedor" ... ¿alguna pista, por favor? – user836026

+1

@ user836026: No proporcionó el 'LocationPoller.EXTRA_PROVIDER' adicional en el' Intent', indicando qué proveedor usar. Si tiene preguntas adicionales sobre este componente, utilice el botón "Preguntar", en lugar de publicar comentarios aquí. – CommonsWare

6

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 (una de varias posibles) es colocar la siguiente en MyApplication clase:

@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 se olvide de mencionar que en AndroidManifest.xml:

<application 
    android:icon="@drawable/ic_launcher" 
    android:label="@string/app_name" 
    android:name="MyApplication" 
    > 

)

12

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

concretamente, justo después de llamar a la configuración del oyente la función, que llamo:

Looper.loop(); 

que bloquea el flujo actual por lo que no muere, pero al mismo tiempo permite que los eventos del proceso, por lo que Tendrás la oportunidad de recibir una notificación cuando se llame a tu oyente. Un bucle simple evitaría que recibas notificaciones cuando se llame al oyente.

Y cuando el oyente se llama, y ​​he terminado el procesamiento de sus datos, puse:

Looper.myLooper().quit(); 
+1

Hago lo mismo. Pero agrego if (Looper.myLooper() == null) Looper.prepare(); antes de Looper.loop(); –

+0

También ejecuto un hilo separado para el tiempo de espera después de un tiempo específico (5-15 segundos para la red, 45-120 segundos para gps). Porque de lo contrario Looper.loop() mantendrá el dispositivo despierto para siempre. –

Cuestiones relacionadas