web-dev-qa-db-fra.com

Service API reposant

Je cherche à créer un service que je peux utiliser pour appeler une API REST sur le Web.

En gros, je veux démarrer un service sur app init puis je veux pouvoir demander à ce service de demander une URL et de renvoyer les résultats. En attendant, je veux pouvoir afficher une fenêtre de progression ou quelque chose de similaire.

J'ai créé un service qui utilise actuellement IDL. J'ai lu quelque part que vous n'en aviez vraiment besoin que pour la communication inter-applications. Pensez donc à les éliminer, mais vous ne savez pas comment faire des rappels sans cela. De plus, lorsque je clique sur la post(Config.getURL("login"), values), l'application semble s'interrompre un moment (cela semble bizarre - je pensais que l'idée derrière un service était de fonctionner sur un autre thread!)

Actuellement, j'ai un service avec post et j'utilise des méthodes http, quelques fichiers AIDL (pour la communication bidirectionnelle), un ServiceManager qui gère le démarrage, l'arrêt, la liaison, etc. au service et je crée dynamiquement un gestionnaire avec un code spécifique. pour les rappels au besoin.

Je ne veux pas que quiconque me donne une base de code complète sur laquelle travailler, mais quelques indications seraient grandement appréciées.

Code en (presque) complet:

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
    return binder;
}
public void onCreate() {
    super.onCreate();
}
public void onDestroy() {
    super.onDestroy();
    mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
    public void doLogin(String username, String password) {

        Message msg = new Message();
        Bundle data = new Bundle();
        HashMap<String, String> values = new HashMap<String, String>();
        values.put("username", username);
        values.put("password", password);
        String result = post(Config.getURL("login"), values);
        data.putString("response", result);
        msg.setData(data);
        msg.what = Config.ACTION_LOGIN;
        mHandler.sendMessage(msg);
    }

    public void registerCallback(IRemoteServiceCallback cb) {
        if (cb != null)
            mCallbacks.register(cb);
    }
};

private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        // Broadcast to all clients the new value.
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                switch (msg.what) {
                case Config.ACTION_LOGIN:
                    mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
                    break;
                default:
                    super.handleMessage(msg);
                    return;

                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }
    public String post(String url, HashMap<String, String> namePairs) {...}
    public String get(String url) {...}
};

Quelques fichiers AIDL:

package com.something.Android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

et

package com.something.Android
import com.something.Android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

et le responsable du service:

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    public IRestfulService restfulService;
    private RestfulServiceConnection conn;
    private boolean started = false;
    private Context context;

    public ServiceManager(Context context) {
        this.context = context;
    }

    public void startService() {
        if (started) {
            Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.Android", "com.something.Android.RestfulAPIService");
            context.startService(i);
            started = true;
        }
    }

    public void stopService() {
        if (!started) {
            Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.Android", "com.something.Android.RestfulAPIService");
            context.stopService(i);
            started = false;
        }
    }

    public void bindService() {
        if (conn == null) {
            conn = new RestfulServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.something.Android", "com.something.Android.RestfulAPIService");
            context.bindService(i, conn, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

    protected void destroy() {
        releaseService();
    }

    private void releaseService() {
        if (conn != null) {
            context.unbindService(conn);
            conn = null;
            Log.d(LOG_TAG, "unbindService()");
        } else {
            Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

    class RestfulServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className, IBinder boundService) {
            restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
            try {
            restfulService.registerCallback(mCallback);
            } catch (RemoteException e) {}
        }

        public void onServiceDisconnected(ComponentName className) {
            restfulService = null;
        }
    };

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        public void userLogIn(String result) throws RemoteException {
            mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));

        }
    };

    private Handler mHandler;

    public void setHandler(Handler handler) {
        mHandler = handler;
    }
}

Service init et bind:

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

appel de fonction de service:

// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();

application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);

try {
    servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
    e.printStackTrace();
}

...later in the same file...

Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        switch (msg.what) {
        case Config.ACTION_LOGIN:

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            try {
                ...process login results...
                }
            } catch (JSONException e) {
                Log.e("JSON", "There was an error parsing the JSON", e);
            }
            break;
        default:
            super.handleMessage(msg);
        }

    }

};
219
Martyn

Si votre service doit faire partie de votre application, vous le rendez beaucoup plus complexe que nécessaire. Étant donné que vous avez un cas d'utilisation simple consistant à obtenir des données d'un service Web RESTful, vous devez examiner ResultReceiver et IntentService .

Ce modèle Service + ResultReceiver fonctionne en démarrant le service ou en le liant avec startService () lorsque vous souhaitez effectuer une action. Vous pouvez spécifier l'opération à effectuer et transmettre votre ResultReceiver (l'activité) via les extras de l'intention.

Dans le service que vous implémentez onHandleIntent pour effectuer l'opération spécifiée dans l'objectif. Lorsque l'opération est terminée, vous utilisez le ResultReceiver transmis à send , un message renvoyant à l'activité à quel moment onReceiveResult sera appelé.

Par exemple, vous souhaitez extraire certaines données de votre service Web.

  1. Vous créez l'intention et appelez startService.
  2. L'opération dans le service commence et envoie à l'activité un message l'informant du début.
  3. L'activité traite le message et affiche une progression.
  4. Le service termine l'opération et renvoie des données à votre activité.
  5. Votre activité traite les données et les insère dans une liste
  6. Le service vous envoie un message disant que c'est fait et qu'il se tue.
  7. L'activité obtient le message de fin et masque la boîte de dialogue de progression.

Je sais que vous avez indiqué que vous ne souhaitiez pas une base de code, mais l’application open source Google I/O 201 utilise un service de cette manière que je décris.

Mise à jour pour ajouter un exemple de code:

L'activité.

public class HomeActivity extends Activity implements MyResultReceiver.Receiver {

    public MyResultReceiver mReceiver;

    public void onCreate(Bundle savedInstanceState) {
        mReceiver = new MyResultReceiver(new Handler());
        mReceiver.setReceiver(this);
        ...
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("command", "query");
        startService(intent);
    }

    public void onPause() {
        mReceiver.setReceiver(null); // clear receiver so no leaks.
    }

    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case RUNNING:
            //show progress
            break;
        case FINISHED:
            List results = resultData.getParcelableList("results");
            // do something interesting
            // hide progress
            break;
        case ERROR:
            // handle the error;
            break;
    }
}

Le service:

public class QueryService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        String command = intent.getStringExtra("command");
        Bundle b = new Bundle();
        if(command.equals("query") {
            receiver.send(STATUS_RUNNING, Bundle.EMPTY);
            try {
                // get some data or something           
                b.putParcelableArrayList("results", results);
                receiver.send(STATUS_FINISHED, b)
            } catch(Exception e) {
                b.putString(Intent.EXTRA_TEXT, e.toString());
                receiver.send(STATUS_ERROR, b);
            }    
        }
    }
}

ResultReceiver extension - édition sur le point d'implémenter MyResultReceiver.Receiver

public class MyResultReceiver implements ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}
283
Robby Pond

Développer Android REST applications clientes a été une formidable ressource pour moi. L'orateur ne montre aucun code, il passe simplement en revue les considérations de conception et les techniques de montage d'un Api Rest solide comme un roc dans Android. Si vous êtes un podcast ou non, je vous recommanderais de lui accorder au moins une écoute, mais personnellement, je l'ai écouté 4 ou 5 fois jusqu'à présent et je vais probablement l'écouter à nouveau.

Développement de Android REST applications clientes
Auteur: Virgil Dobjanschi
La description:

Cette session présentera les considérations architecturales pour développer des applications RESTful sur la plate-forme Android. Il se concentre sur les modèles de conception, l'intégration de la plate-forme et les problèmes de performances spécifiques à la plate-forme Android.

Et il y a tellement de considérations que je n'avais pas vraiment faites dans la première version de mon API, que j'ai dû refactoriser

17
Terrance

De plus, lorsque je clique sur le message (Config.getURL ("login"), valeurs), l'application semble s'interrompre un moment (cela semble bizarre - je pensais que l'idée derrière un service était de fonctionner sur un autre thread!)

Non, vous devez créer un thread vous-même, un service local s'exécute par défaut dans le thread d'interface utilisateur.

16
Soumya Simanta

Je sais que @Martyn ne veut pas de code complet, mais je pense que cette annotation est bonne pour cette question:

10 Open Source Android Applications que chaque Android développeur doit examiner

Foursquared pour Android est open-source et possède un motif de code intéressant qui interagit avec l'API foursquare REST .

11
panchicore

Je recommande vivement le client REST Retrofit .

J'ai trouvé cet article de blog bien écrit extrêmement utile, il contient également un exemple de code simple. L'auteur utilise Retrofit pour passer les appels réseau et Otto pour implémenter un modèle de bus de données:

http://www.mdswanson.com/blog/2014/04/07/durable-Android-rest-clients.html

6
Pete

Je voulais juste vous diriger tous vers la classe autonome que j'ai lancée et qui intègre toutes les fonctionnalités.

http://github.com/StlTenny/RestService

Il exécute la demande de manière non bloquante et renvoie les résultats dans un gestionnaire facile à mettre en œuvre. Même vient avec un exemple de mise en œuvre.

5
StlTenny

Disons que je veux démarrer le service sur un événement - onItemClicked () d'un bouton. Le mécanisme du destinataire ne fonctionnerait pas dans ce cas car: -
a) J'ai transmis le récepteur au service (comme dans Intention extra) de onItemClicked ()
b) L'activité passe à l'arrière-plan. Dans onPause (), je règle la référence du destinataire dans ResultReceiver sur null pour éviter la fuite de l'activité.
c) L'activité est détruite.
d) L'activité est créée à nouveau. Cependant, à ce stade, le service ne sera pas en mesure de rappeler l’activité, car la référence du destinataire est perdue.
Le mécanisme d'une émission limitée ou d'un PendingIntent semble être plus utile dans de tels scénarios - voir notifier l'activité du service

4
Nikhil_Katre

Notez que la solution de Robby Pond manque en quelque sorte: de cette manière, vous ne permettez que de faire un appel api à la fois puisque IntentService ne traite qu’une intention à la fois. Souvent, vous souhaitez effectuer des appels API parallèles. Si vous voulez faire cela, vous devez étendre le service au lieu d’IntentService et créer votre propre thread.

4
TjerkW

De plus, lorsque je clique sur le message (Config.getURL ("login"), valeurs), l'application semble s'interrompre un moment (cela semble bizarre - je pensais que l'idée derrière un service était de fonctionner sur un autre thread!)

Dans ce cas, il est préférable d'utiliser asynctask, qui s'exécute sur un autre thread et renvoie le résultat au thread ui à la fin.

2
Aakash

Il existe une autre approche qui vous permet essentiellement d’oublier toute la gestion des demandes. Il est basé sur une méthode de file d'attente asynchrone et une réponse basée sur appelable/rappel. Le principal avantage est qu’en utilisant cette méthode, vous serez en mesure de rendre tout le processus transparent (demander, obtenir et analyser la réponse, comme décrit ci-dessus) complètement transparent pour vous. Une fois le code de réponse obtenu, le travail est déjà fait. Après cela, il vous suffit d'appeler votre base de données et vous avez terminé. Cela aide aussi avec la problématique de ce qui se passe lorsque votre activité n'est pas active. Dans ce cas, toutes vos données seront enregistrées dans votre base de données locale mais la réponse ne sera pas traitée par votre activité, c’est la méthode idéale.

J'ai écrit sur une approche générale ici http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-Android/

Je mettrai un exemple de code spécifique dans les prochains messages. J'espère que cela vous aidera, n'hésitez pas à me contacter pour partager l'approche et résoudre d'éventuels doutes ou problèmes.

2
Jose L Ugia

Robby fournit une excellente réponse, même si je vois que vous recherchez toujours plus d'informations. J'ai implémenté REST api appelle la méthode facile MAIS dans le mauvais sens. Ce n'est que lorsque j'ai regardé ceci vidéo Google I/O que j'ai compris où je me suis trompé. Ce n'est pas aussi simple que de constituer une AsyncTask avec un appel get/put HttpUrlConnection.

1
Andrew Halloran