web-dev-qa-db-fra.com

Envoi d'un message à un gestionnaire sur un thread mort lors de l'obtention d'un emplacement à partir d'un IntentService

Mon application a besoin de corrections de position sur une base régulière, même lorsque le téléphone n'est pas réveillé. Pour ce faire, j'utilise IntentService avec le modèle généreusement fourni par Commonsware. https://github.com/commonsguy/cwac-wakeful

Pour obtenir des correctifs de localisation, je me fie au code suivant fourni par un membre appelé Fedor. Quelle est la façon la plus simple et la plus robuste d'obtenir l'emplacement actuel de l'utilisateur sur Android? . Je l'ai légèrement modifié pour retourner null au lieu d'obtenir le dernier emplacement connu

Ils fonctionnent tous les deux parfaitement lorsqu'ils ne sont pas combinés: je peux obtenir un correctif d'emplacement de la classe de Fedor dans une activité, et je peux faire des trucs de base (comme enregistrer quelque chose) en utilisant la classe Commonsware.

Le problème se produit lorsque j'essaye d'obtenir des correctifs d'emplacement à partir de la sous-classe WakefulIntentService de Commonsware. Les LocationListeners ne réagissent pas aux locationUpdates et j'obtiens ces avertissements (je n'ai pas encore les subtilités des threads, des gestionnaires, ...). Quelqu'un peut-il m'aider à comprendre quel est le problème? Merci et je vous souhaite à tous une très bonne année :)

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)

Voici ma sous-classe 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);
}

Et voici une copie de la classe de Fedor (légèrement modifiée: pas besoin de GPS ni dernier emplacement connu)

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);

}

}

34
znat

Vous ne pouvez pas enregistrer en toute sécurité des écouteurs à partir d'un IntentService, car le IntentService disparaît dès que onHandleIntent() (a.k.a., doWakefulWork()) se termine. Au lieu de cela, vous devrez utiliser un service régulier, ainsi que gérer des détails tels que les délais d'attente (par exemple, l'utilisateur est dans un sous-sol et ne peut pas recevoir de signal GPS).

34
CommonsWare

J'ai eu une situation similaire et j'ai utilisé la fonction Android Looper à bon escient: http://developer.Android.com/reference/Android/os/ Looper.html

concrètement, juste après avoir appelé la fonction mettant en place l'auditeur, j'appelle:

Looper.loop();

Cela bloque le thread actuel afin qu'il ne meure pas, mais lui permet en même temps de traiter les événements, vous aurez donc la possibilité d'être averti lorsque votre écouteur sera appelé. Une simple boucle vous empêcherait d'être averti lorsque l'auditeur est appelé.

Et quand l'auditeur est appelé et que j'ai fini de traiter ses données, je mets:

Looper.myLooper().quit();
12
Emmanuel Touzery

Pour d'autres comme moi: j'ai eu un problème similaire lié à AsyncTask. Si je comprends bien, cette classe suppose qu'elle est initialisée sur le thread d'interface utilisateur (principal).

Une solution de contournement (parmi plusieurs possibles) consiste à placer ce qui suit dans la classe 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();
}

(n'oubliez pas de le mentionner dans AndroidManifest.xml:

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

)

6