Servizio API riposante


226

Sto cercando di creare un servizio che posso usare per effettuare chiamate a un'API REST basata sul web.

Fondamentalmente voglio avviare un servizio su init app quindi voglio essere in grado di chiedere a quel servizio di richiedere un URL e restituire i risultati. Nel frattempo, voglio essere in grado di visualizzare una finestra di avanzamento o qualcosa di simile.

Al momento ho creato un servizio che utilizza IDL, ho letto da qualche parte che hai davvero bisogno di questo solo per la comunicazione tra app, quindi pensa che questi debbano essere eliminati ma non sei sicuro di come fare callback senza di essa. Anche quando colpisco l' post(Config.getURL("login"), values)app sembra fermarsi per un po '(sembra strano - pensavo che l'idea alla base di un servizio fosse che funziona su un thread diverso!)

Attualmente ho un servizio con posta e ottengo metodi http all'interno, un paio di file AIDL (per la comunicazione bidirezionale), un ServiceManager che si occupa di avviare, arrestare, associare ecc al servizio e sto creando dinamicamente un gestore con codice specifico per i callback, se necessario.

Non voglio che nessuno mi dia una base di codice completa su cui lavorare, ma alcuni suggerimenti sarebbero molto apprezzati.

Codice (per lo più) completo:

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) {...}
};

Un paio di file AIDL:

package com.something.android

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

e

package com.something.android
import com.something.android.IRemoteServiceCallback;

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

e il responsabile del servizio:

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

Servizio init e bind:

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

chiamata di funzione di servizio:

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

    }

};

5
Questo potrebbe essere molto utile per le persone che imparano l'implementazione del client REST Android. La presentazione di Dobjanschi trascritta in un PDF: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…
Kay Zed,

Poiché molte persone hanno raccomandato la presentazione di Virgil Dobjanschi e il collegamento a IO 2010 ora è interrotto, ecco il link diretto al video YT: youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

Risposte:


283

Se il tuo servizio farà parte della tua applicazione, lo stai rendendo molto più complesso di quanto debba essere. Poiché hai un semplice caso d'uso per ottenere alcuni dati da un servizio Web RESTful, dovresti esaminare ResultReceiver e IntentService .

Questo modello Service + ResultReceiver funziona avviando o associando al servizio con startService () quando si desidera eseguire alcune azioni. È possibile specificare l'operazione da eseguire e passare in ResultReceiver (l'attività) attraverso gli extra nell'Intento.

Nel servizio implementato onHandleIntent per eseguire l'operazione specificata nell'Intent . Al termine dell'operazione si utilizza il ResultReceiver passato per inviare un messaggio all'attività a quel punto verrà chiamato onReceiveResult .

Quindi, ad esempio, vuoi estrarre alcuni dati dal tuo servizio Web.

  1. Si crea l'intento e si chiama startService.
  2. L'operazione nel servizio inizia e invia all'attività un messaggio che dice che è iniziata
  3. L'attività elabora il messaggio e mostra un progresso.
  4. Il servizio termina l'operazione e invia alcuni dati alla tua attività.
  5. La tua attività elabora i dati e li inserisce in una vista elenco
  6. Il servizio ti invia un messaggio dicendo che è fatto e si uccide da solo.
  7. L'attività riceve il messaggio di fine e nasconde la finestra di avanzamento.

So che hai detto che non volevi una base di codice, ma l' app Google I / O 2010 open source utilizza un servizio in questo modo che sto descrivendo.

Aggiornato per aggiungere il codice di esempio:

L'attività.

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

Il servizio:

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

Estensione ResultReceiver - modificata per implementare 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);
        }
    }
}

1
Eccellente grazie! Ho finito con l'applicazione Google iosched e wow ... è piuttosto complesso ma ho qualcosa che funziona, ora devo solo capire perché funziona! Ma sì, questo è il modello base che sto lavorando. Grazie mille.
Martyn,

29
Una piccola aggiunta alla risposta: mentre fai mReceiver.setReceiver (null); nel metodo onPause, dovresti eseguire mReceiver.setReceiver (questo); nel metodo onResume. Altrimenti potresti non ricevere gli eventi se la tua attività viene ripresa senza essere ricreata
Vincent Mimoun-Prat

7
I documenti non dicono che non devi chiamare stopSelf, poiché IntentService lo fa per te?
Mikael Ohlson,

2
@MikaelOhlson corretta, è necessario non chiamare stopSelfse si sottoclasse IntentServiceperché se lo fai, perderai eventuali richieste in sospeso per lo stesso IntentService.
quietmint,

1
IntentServicesi ucciderà al termine dell'attività, quindi this.stopSelf()non è necessario.
Euporie,

17

Lo sviluppo di applicazioni client Android REST è stata una risorsa fantastica per me. L'altoparlante non mostra alcun codice, si limita a considerare le considerazioni di progettazione e le tecniche nel mettere insieme un solido Api Rest in Android. Se sei un podcast un po 'una persona o no, ti consiglio di dare a questo almeno un ascolto ma, personalmente l'ho ascoltato come 4 o cinque volte finora e probabilmente lo ascolterò di nuovo.

Sviluppo di applicazioni client REST per Android
Autore: Virgil Dobjanschi
Descrizione:

Questa sessione presenterà considerazioni sull'architettura per lo sviluppo di applicazioni RESTful sulla piattaforma Android. Si concentra su modelli di progettazione, integrazione della piattaforma e problemi di prestazioni specifici della piattaforma Android.

E ci sono così tante considerazioni che non avevo davvero fatto nella prima versione della mia API che ho dovuto rifattare


4
+1 Questo contiene i tipi di considerazioni che non avresti mai pensato quando inizi.
Thomas Ahle,

Sì, la mia prima incursione nello sviluppo di un client Rest è stata quasi esattamente la sua descrizione di cosa esattamente non fare (per fortuna ho capito che molto di questo era sbagliato prima di guardarlo). Ci ho pensato un po '.
Terrance,

Ho visto questo video più di una volta e sto implementando il secondo modello. Il mio problema è che ho bisogno di usare le transazioni nel mio modello di database complesso per aggiornare i dati locali da nuovi dati che provengono dal server e l'interfaccia ContentProvider non mi fornisce un modo per farlo. Hai qualche suggerimento, Terrance?
Flávio Faria,

2
NB: i commenti di Dobjanschi su HttpClient non sono più validi. Vedi stackoverflow.com/a/15524143/939250
Donal Lafferty

Sì, ora è preferito HttpURLConnection . Inoltre, con il rilascio di Android 6.0, il supporto per il client HTTP Apache è stato ufficialmente rimosso .
RonR,

16

Anche quando premo il post (Config.getURL ("login"), i valori) l'app sembra fermarsi per un po '(sembra strano - pensavo che l'idea alla base di un servizio fosse che funziona su un thread diverso!)

No, devi creare tu stesso un thread, per impostazione predefinita un servizio locale viene eseguito nel thread dell'interfaccia utente.




5

Volevo solo indirizzarvi tutti nella direzione di una classe autonoma che ho lanciato che incorpora tutte le funzionalità.

http://github.com/StlTenny/RestService

Esegue la richiesta come non bloccante e restituisce i risultati in un gestore facile da implementare. Viene fornito anche con un'implementazione di esempio.


4

Diciamo che voglio avviare il servizio su un evento - onItemClicked () di un pulsante. In tal caso il meccanismo del ricevitore non funzionerebbe perché: -
a) Ho passato il ricevitore al servizio (come in Intent extra) da onItemClicked ()
b) L'attività si sposta in background. In onPause () ho impostato il riferimento del ricevitore all'interno di ResultReceiver su null per evitare di perdere l'attività.
c) L'attività viene distrutta.
d) L'attività viene nuovamente creata. Tuttavia, a questo punto, il Servizio non sarà in grado di richiamare l'Attività in quanto il riferimento del destinatario viene perso.
Il meccanismo di una trasmissione limitata o di un PendingIntent sembra essere più utile in tali scenari - fare riferimento a Notifica attività dal servizio


1
c'è un problema con quello che stai dicendo. cioè che quando l'attività si sposta in background non viene distrutta ... quindi il ricevitore esiste ancora e anche il contesto dell'attività.
DArkO,

@DArkO Quando un'attività viene messa in pausa o interrotta, può essere uccisa dal sistema Android in situazioni di memoria insufficiente. Fare riferimento a Ciclo di vita delle attività .
jk7

4

Si noti che la soluzione di Robby Pond è in qualche modo carente: in questo modo si consente di effettuare solo una chiamata API alla volta poiché IntentService gestisce solo un intento alla volta. Spesso si desidera eseguire chiamate API parallele. Se vuoi farlo, devi estendere il servizio anziché IntentService e creare il tuo thread.


1
Puoi ancora effettuare più chiamate su IntentService delegando le chiamate API webservice a un servizio thread-esecutore, presente come variabile membro della classe derivata da IntentService
Viren,

2

Anche quando premo il post (Config.getURL ("login"), i valori) l'app sembra fermarsi per un po '(sembra strano - pensavo che l'idea alla base di un servizio fosse che funziona su un thread diverso!)

In questo caso è meglio usare asynctask, che gira su un thread diverso e restituisce il risultato al thread dell'interfaccia utente al completamento.


2

C'è un altro approccio qui che sostanzialmente ti aiuta a dimenticare l'intera gestione delle richieste. Si basa su un metodo della coda asincrona e su una risposta richiamabile / richiamata. Il vantaggio principale è che usando questo metodo sarai in grado di rendere l'intero processo (richiesta, ottenere e analizzare la risposta, sabe to db) completamente trasparente per te. Una volta ottenuto il codice di risposta, il lavoro è già terminato. Dopodiché devi solo effettuare una chiamata al tuo db e il gioco è fatto. Aiuta anche con la problematica di ciò che accade quando la tua attività non è attiva. Quello che succederà qui è che avrai tutti i tuoi dati salvati nel tuo database locale ma la risposta non verrà elaborata dalla tua attività, questo è il modo ideale.

Ho scritto di un approccio generale qui http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

Inserirò un codice di esempio specifico nei prossimi post. Spero che ti aiuti, sentiti libero di contattarmi per condividere l'approccio e risolvere potenziali dubbi o problemi.


Collegamento morto. Il dominio è scaduto
jk7

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.