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);
}
}
};
quelle
Antworten:
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.
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.
Der Service:
ResultReceiver-Erweiterung - bearbeitet, um MyResultReceiver.Receiver zu implementieren
quelle
stopSelf
wenn Sie eine Unterklasse haben,IntentService
da Sie sonst alle ausstehenden Anforderungen an dieselbe verlierenIntentService
.IntentService
wird sich selbst töten, wenn die Aufgabe abgeschlossen ist,this.stopSelf()
ist also unnötig.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
quelle
Nein, Sie müssen selbst einen Thread erstellen . Standardmäßig wird ein lokaler Dienst im UI-Thread ausgeführt.
quelle
Ich weiß, dass @Martyn keinen vollständigen Code möchte, aber ich denke, diese Anmerkung ist gut für diese Frage:
10 Open Source Android Apps, die jeder Android-Entwickler prüfen muss
Foursquared für Android ist Open Source und verfügt über ein interessantes Codemuster, das mit der foursquare REST-API interagiert.
quelle
Ich kann den REST-Client Retrofit nur empfehlen .
Ich fand diesen gut geschriebenen Blog-Beitrag äußerst hilfreich, er enthält auch einfachen Beispielcode. Der Autor verwendet Retrofit , um die Netzwerkanrufe zu tätigen, und Otto , um ein Datenbusmuster zu implementieren:
http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html
quelle
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.
quelle
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
quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle