Erholsamer API-Service


226

Ich möchte einen Dienst erstellen, mit dem ich eine webbasierte REST-API aufrufen kann.

Grundsätzlich möchte ich einen Dienst auf App Init starten, dann möchte ich diesen Dienst bitten können, eine URL anzufordern und die Ergebnisse zurückzugeben. In der Zwischenzeit möchte ich ein Fortschrittsfenster oder ähnliches anzeigen können.

Ich habe derzeit einen Dienst erstellt, der IDL verwendet. Ich habe irgendwo gelesen, dass Sie dies nur für die app-übergreifende Kommunikation benötigen. Denken Sie also, dass diese entfernt werden müssen, aber Sie wissen nicht, wie Sie Rückrufe ohne IDL durchführen sollen. Auch wenn ich auf die post(Config.getURL("login"), values)App drücke, scheint die App für eine Weile anzuhalten (scheint seltsam - dachte, die Idee hinter einem Dienst war, dass er auf einem anderen Thread läuft!)

Derzeit habe ich einen Dienst mit Post- und Get-http-Methoden, ein paar AIDL-Dateien (für die bidirektionale Kommunikation), einen ServiceManager, der sich mit dem Starten, Stoppen, Binden usw. des Dienstes befasst, und ich erstelle dynamisch einen Handler mit spezifischem Code für die Rückrufe nach Bedarf.

Ich möchte nicht, dass mir jemand eine vollständige Codebasis zur Verfügung stellt, an der ich arbeiten kann, aber einige Hinweise wären sehr dankbar.

Code in (meistens) vollständig:

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

Einige AIDL-Dateien:

package com.something.android

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

und

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

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

und der Service Manager:

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 und bind:

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

Service-Funktionsaufruf:

// 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
Dies kann sehr hilfreich sein, wenn Sie die Implementierung des Android REST-Clients lernen. Dobjanschis Präsentation wurde in ein PDF übertragen: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…
Kay Zed

Da viele Leute die Präsentation von Virgil Dobjanschi empfohlen haben und der Link zu IO 2010 jetzt unterbrochen ist, ist hier der direkte Link zum YT-Video: youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

Antworten:


283

Wenn Ihr Dienst Teil Ihrer Anwendung sein soll, machen Sie ihn viel komplexer als nötig. Da Sie einen einfachen Anwendungsfall zum Abrufen einiger Daten von einem RESTful-Webdienst haben, sollten Sie sich ResultReceiver und IntentService ansehen .

Dieses Service + ResultReceiver-Muster startet oder bindet den Dienst mit startService (), wenn Sie eine Aktion ausführen möchten. Sie können die auszuführende Operation angeben und in Ihrem ResultReceiver (der Aktivität) die Extras in der Absicht übergeben.

In dem Dienst implementieren Sie onHandleIntent , um die in der Absicht angegebene Operation auszuführen . Wenn der Vorgang abgeschlossen ist, verwenden Sie den in ResultReceiver übergebenen Befehl, um eine Nachricht an die Aktivität zurückzusenden, an der onReceiveResult aufgerufen wird.

Sie möchten beispielsweise einige Daten aus Ihrem Webdienst abrufen.

  1. Sie erstellen die Absicht und rufen startService auf.
  2. Die Operation im Dienst wird gestartet und der Aktivität wird eine Nachricht gesendet, die besagt, dass sie gestartet wurde
  3. Die Aktivität verarbeitet die Nachricht und zeigt einen Fortschritt an.
  4. Der Dienst beendet den Vorgang und sendet einige Daten an Ihre Aktivität zurück.
  5. Ihre Aktivität verarbeitet die Daten und fügt sie in eine Listenansicht ein
  6. Der Dienst sendet Ihnen eine Nachricht, dass dies erledigt ist und sich selbst tötet.
  7. Die Aktivität erhält die Abschlussmeldung und verbirgt den Fortschrittsdialog.

Ich weiß, dass Sie erwähnt haben, dass Sie keine Codebasis möchten, aber die Open Source- App Google I / O 2010 verwendet einen Dienst auf die von mir beschriebene Weise.

Aktualisiert, um Beispielcode hinzuzufügen:

Die Aktivität.

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

Der 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-Erweiterung - bearbeitet, um MyResultReceiver.Receiver zu implementieren

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
Ausgezeichnet, vielen Dank! Am Ende habe ich die Google iosched-Anwendung durchgesehen und wow ... es ist ziemlich komplex, aber ich habe etwas, das funktioniert. Jetzt muss ich nur noch herausfinden, warum es funktioniert! Aber ja, das ist das Grundmuster, mit dem ich arbeite. vielen Dank.
Martyn

29
Eine kleine Ergänzung zur Antwort: wie Sie den mReceiver.setReceiver (null) tun; In der onPause-Methode sollten Sie den mReceiver.setReceiver (this) ausführen. in der onResume-Methode. Andernfalls erhalten Sie die Ereignisse möglicherweise nicht, wenn Ihre Aktivität wieder aufgenommen wird, ohne neu erstellt zu werden
Vincent Mimoun-Prat

7
Sagen die Dokumente nicht, dass Sie stopSelf nicht aufrufen müssen, da IntentService dies für Sie erledigt?
Mikael Ohlson

2
@MikaelOhlson Richtig, Sie sollten nicht anrufen, stopSelfwenn Sie eine Unterklasse haben, IntentServiceda Sie sonst alle ausstehenden Anforderungen an dieselbe verlieren IntentService.
quietmint

1
IntentServicewird sich selbst töten, wenn die Aufgabe abgeschlossen ist, this.stopSelf()ist also unnötig.
Euporie

17

Die Entwicklung von Android REST-Clientanwendungen war für mich eine großartige Ressource. Der Sprecher zeigt keinen Code an, er geht nur auf Designüberlegungen und Techniken ein, um eine solide Rest-API in Android zusammenzustellen. Wenn Sie ein Podcast sind oder nicht, würde ich empfehlen, diesem mindestens einmal zuzuhören, aber ich persönlich habe es bisher vier- oder fünfmal angehört und werde es wahrscheinlich wieder anhören.

Entwicklung von Android REST-Client-Anwendungen
Autor: Virgil Dobjanschi
Beschreibung:

In dieser Sitzung werden architektonische Überlegungen zur Entwicklung von RESTful-Anwendungen auf der Android-Plattform vorgestellt. Es konzentriert sich auf Entwurfsmuster, Plattformintegration und Leistungsprobleme, die für die Android-Plattform spezifisch sind.

Und es gibt so viele Überlegungen, die ich in der ersten Version meiner API wirklich nicht gemacht hatte, dass ich sie umgestalten musste


4
+1 Dies enthält die Art von Überlegungen, an die Sie zu Beginn niemals denken würden.
Thomas Ahle

Ja, mein erster Versuch, einen Rest-Client zu entwickeln, war fast genau seine Beschreibung dessen, was genau nicht zu tun ist (Zum Glück wurde mir klar, dass vieles davon falsch war, bevor ich mir das ansah). Ich habe ein bisschen darüber nachgedacht.
Terrance

Ich habe dieses Video mehr als einmal gesehen und implementiere das zweite Muster. Mein Problem ist, dass ich Transaktionen in meinem komplexen Datenbankmodell verwenden muss, um lokale Daten von frischen Daten zu aktualisieren, die vom Server stammen, und die ContentProvider-Oberfläche bietet mir keine Möglichkeit, dies zu tun. Hast du einen Vorschlag, Terrance?
Flávio Faria

2
NB: Kommentare von Dobjanschi zu HttpClient gelten nicht mehr. Siehe stackoverflow.com/a/15524143/939250
Donal Lafferty

Ja, HttpURLConnection wird jetzt bevorzugt. Mit der Veröffentlichung von Android 6.0 wurde die Unterstützung für den Apache HTTP Client offiziell entfernt .
RonR

16

Auch wenn ich auf den Beitrag klicke (Config.getURL ("login"), values), scheint die App eine Weile zu pausieren (scheint seltsam - dachte, die Idee hinter einem Dienst war, dass er auf einem anderen Thread läuft!)

Nein, Sie müssen selbst einen Thread erstellen . Standardmäßig wird ein lokaler Dienst im UI-Thread ausgeführt.




5

Ich wollte Sie alle nur in Richtung einer eigenständigen Klasse weisen, die ich gerollt habe und die alle Funktionen enthält.

http://github.com/StlTenny/RestService

Es führt die Anforderung als nicht blockierend aus und gibt die Ergebnisse in einem einfach zu implementierenden Handler zurück. Kommt sogar mit einer Beispielimplementierung.


4

Nehmen wir an, ich möchte den Dienst für ein Ereignis starten - onItemClicked () einer Schaltfläche. Der Empfängermechanismus würde in diesem Fall nicht funktionieren, weil: -
a) ich den Empfänger von onItemClicked () an den Dienst übergeben habe (wie in Intent extra)
b) Aktivität in den Hintergrund tritt. In onPause () habe ich die Empfängerreferenz im ResultReceiver auf null gesetzt, um ein Auslaufen der Aktivität zu vermeiden.
c) Aktivität wird zerstört.
d) Aktivität wird erneut erstellt. Zu diesem Zeitpunkt kann der Dienst jedoch keinen Rückruf zur Aktivität tätigen, da diese Empfängerreferenz verloren geht.
Der Mechanismus einer eingeschränkten Sendung oder eines PendingIntent scheint in solchen Szenarien nützlicher zu sein - siehe Benachrichtigen von Aktivitäten vom Dienst


1
Es gibt ein Problem mit dem, was Sie sagen. Das heißt, wenn die Aktivität in den Hintergrund verschoben wird, wird sie nicht zerstört. Der Empfänger existiert also weiterhin und der Aktivitätskontext auch.
DArkO

@DArkO Wenn eine Aktivität angehalten oder gestoppt wird, kann sie vom Android-System in Situationen mit wenig Arbeitsspeicher beendet werden. Siehe Aktivitätslebenszyklus .
jk7

4

Beachten Sie, dass die Lösung von Robby Pond irgendwie fehlt: Auf diese Weise können Sie jeweils nur einen API-Aufruf ausführen, da der IntentService jeweils nur eine Absicht verarbeitet. Oft möchten Sie parallele API-Aufrufe ausführen. Wenn Sie dies tun möchten, müssen Sie Service anstelle von IntentService erweitern und einen eigenen Thread erstellen.


1
Sie können den IntentService weiterhin mehrfach aufrufen, indem Sie die Webservice-API-Aufrufe an einen Executor-Thread-Service delegieren, der als Mitgliedsvariable Ihrer von IntentService abgeleiteten Klasse vorhanden ist
Viren

2

Auch wenn ich auf den Beitrag klicke (Config.getURL ("login"), values), scheint die App eine Weile zu pausieren (scheint seltsam - dachte, die Idee hinter einem Dienst war, dass er auf einem anderen Thread läuft!)

In diesem Fall ist es besser, asynctask zu verwenden, das auf einem anderen Thread ausgeführt wird und das Ergebnis nach Abschluss an den UI-Thread zurückgibt.


2

Hier gibt es einen anderen Ansatz, der Ihnen im Grunde hilft, die gesamte Verwaltung der Anforderungen zu vergessen. Es basiert auf einer asynchronen Warteschlangenmethode und einer aufrufbaren / rückrufbasierten Antwort. Der Hauptvorteil ist, dass Sie mit dieser Methode den gesamten Prozess (Anfordern, Abrufen und Analysieren der Antwort, sabe to db) für Sie vollständig transparent machen können. Sobald Sie den Antwortcode erhalten haben, ist die Arbeit bereits erledigt. Danach müssen Sie nur noch Ihre Datenbank anrufen und fertig. Es hilft auch bei der Problematik, was passiert, wenn Ihre Aktivität nicht aktiv ist. Was hier passieren wird, ist, dass Sie alle Ihre Daten in Ihrer lokalen Datenbank gespeichert haben, die Antwort jedoch nicht von Ihrer Aktivität verarbeitet wird. Dies ist der ideale Weg.

Ich schrieb hier über einen allgemeinen Ansatz http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

Ich werde in den kommenden Beiträgen einen bestimmten Beispielcode einfügen. Ich hoffe, es hilft, zögern Sie nicht, mich zu kontaktieren, um den Ansatz zu teilen und mögliche Zweifel oder Probleme zu lösen.


Toter Link. Die Domain ist abgelaufen.
jk7

1

Robby gibt eine großartige Antwort, obwohl ich sehe, dass Sie immer noch nach weiteren Informationen suchen. Ich habe REST-API-Aufrufe einfach ABER falsch implementiert. Erst als ich mir dieses Google I / O-Video angesehen habe , habe ich verstanden, wo ich falsch gelaufen bin. Es ist nicht so einfach, eine AsyncTask mit einem HttpUrlConnection-Get / Put-Aufruf zusammenzustellen.


Toter Link. Hier ist die aktualisierte - Google I / O 2010 - Android REST-Client-Anwendungen
zim
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.