2010-03-17 3 views
208

Ich schreibe meine erste Android-Anwendung und versuche, die Kommunikation zwischen Diensten und Aktivitäten zu verstehen. Ich habe einen Dienst, der im Hintergrund läuft und einige GPS- und zeitbasierte Logging-Funktionen ausführt. Ich werde eine Aktivität haben, die zum Starten und Stoppen des Dienstes verwendet wird.Wie Android Service mit der Aktivität kommunizieren kann

Zuerst muss ich herausfinden können, ob der Dienst ausgeführt wird, wenn die Aktivität gestartet wird. Es gibt einige andere Fragen dazu, also denke ich, dass ich das herausfinden kann (aber zögern Sie nicht, mir Ratschläge zu geben).

Mein echtes Problem: Wenn die Aktivität ausgeführt wird und der Dienst gestartet wird, brauche ich eine Möglichkeit für den Dienst, Nachrichten an die Aktivität zu senden. Einfache Strings und Integer an dieser Stelle - meist Statusmeldungen. Die Nachrichten werden nicht regelmäßig passieren, daher denke ich nicht, dass das Abrufen des Dienstes ein guter Weg ist, wenn es einen anderen Weg gibt. Ich möchte diese Kommunikation nur, wenn die Aktivität vom Benutzer gestartet wurde - ich möchte die Aktivität nicht vom Dienst aus starten. Mit anderen Worten, wenn Sie die Aktivität starten und der Dienst ausgeführt wird, werden einige Statusmeldungen in der Aktivitätsbenutzeroberfläche angezeigt, wenn etwas Interessantes passiert. Wenn Sie die Aktivität nicht starten, werden Sie diese Nachrichten nicht sehen (sie sind nicht so interessant).

Es scheint, als ob ich in der Lage sein sollte festzustellen, ob der Dienst ausgeführt wird, und wenn dies der Fall ist, fügen Sie die Aktivität als Listener hinzu. Entfernen Sie dann die Aktivität als Listener, wenn die Aktivität angehalten oder beendet wird. Ist das überhaupt möglich? Die einzige Möglichkeit, dies herauszufinden, ist, dass die Activity Parcelable implementiert und eine AIDL-Datei erstellt, damit ich sie über die Remote-Schnittstelle des Service weitergeben kann. Das scheint jedoch Overkill zu sein, und ich habe keine Ahnung, wie die Aktivität writeToParcel()/readFromParcel() implementieren sollte.

Gibt es einen leichteren oder besseren Weg? Danke für jede Hilfe.

EDIT:

Für alle, die später daran interessiert ist, gibt es Beispielcode von Google dies in dem Beispielverzeichnis über AIDL zur Handhabung: /apis/app/RemoteService.java

Antwort

72

Es sind drei offensichtliche Möglichkeiten, um mit Dienstleistungen zu kommunizieren:

  1. mit Intents
  2. mit AIDL
  3. Mit dem Dienstobjekt selbst (als Singletons)

In Ihrem Fall würde ich mit der Option gehen 3. auf den Dienst es sich von selbst eine statische Referenzmarke und es in onCreate bevölkern():

void onCreate(Intent i) { 
    sInstance = this; 
} 

Machen Sie eine statische Funktion MyService getInstance(), die die statische sInstance zurückgibt.

Dann in Activity.onCreate() starten Sie den Dienst, asynchron warten, bis der Dienst tatsächlich gestartet wird (Sie könnten Ihren Dienst benachrichtigt werden, indem Sie eine Absicht an die Aktivität senden.) Und seine Instanz abrufen. Wenn Sie über die Instanz verfügen, registrieren Sie Ihr Dienst-Listener-Objekt für Ihren Dienst, und Sie sind eingerichtet. HINWEIS: Wenn Sie Ansichten in der Aktivität bearbeiten, die Sie im Benutzeroberflächenthread ändern müssen, wird der Dienst wahrscheinlich seinen eigenen Thread ausführen. Sie müssen also Activity.runOnUiThread() aufrufen.

Das letzte, was Sie tun müssen, ist den Verweis auf Ihr Listener-Objekt in Activity.onPause() zu entfernen, andernfalls eine Instanz Ihres Aktivitätskontextes wird undicht, nicht gut.

HINWEIS: Diese Methode ist nur nützlich, wenn Ihre Anwendung/Aktivität/Aufgabe der einzige Prozess ist, der auf Ihren Dienst zugreift. Ist dies nicht der Fall ist, müssen Sie die Option 1. oder 2.

+2

Wie kann ich die Aktivität "busy-warte"? Kannst du bitte erklären ? – iTurki

+1

Also entweder nach onCreate oder nach onStartCommand in Ihrer Serviceklasse setzen Sie eine öffentliche statische Variable, nennen Sie es isConnected oder etwas zu true.Dann in Ihrer Aktivität tun Sie dies: Absicht intent = neue Absicht (act, YourService.class); \t startService (Absicht); while (! YourService.isConnected) { Schlaf (300); } Nach dieser Schleife Ihren Dienst läuft und Sie können die Kommunikation damit. Meiner Erfahrung nach gibt es definitiv einige Einschränkungen der Interaktion mit einem Dienst wie diesem. –

+0

Warum kann es nur in 'Activity.onCreate()' gestartet werden? – skywall

227

Der Fragesteller hat vermutlich diese Vergangenheit längst bewegt hat zu verwenden, aber falls jemand anderes sucht diese ...

Es gibt eine andere Art und Weise zu handhaben das, was ich denke, könnte das einfachste sein.

Fügen Sie Ihrer Aktivität einen BroadcastReceiver hinzu. Registrieren Sie es, um eine benutzerdefinierte Absicht in onResume zu erhalten und die Registrierung in onPause aufzuheben. Dann senden Sie diese Absicht von Ihrem Dienst aus, wenn Sie Ihre Status-Updates versenden möchten oder was Sie haben.

Stellen Sie sicher, dass Sie nicht unglücklich wären, wenn eine andere App auf Ihre Absicht zugehört hätte (könnte jemand etwas Böswilliges tun?), Aber darüber hinaus sollten Sie in Ordnung sein.

Codebeispiel angefordert wurde:

In meinem Dienst, ich habe: (. RefreshTask.REFRESH_DATA_INTENT ist nur eine konstante String)

// Do stuff that alters the content of my local SQLite Database 
sendBroadcast(new Intent(RefreshTask.REFRESH_DATA_INTENT)); 

In meiner Zuhören Aktivität definiere ich meine BroadcastReceiver:

private class DataUpdateReceiver extends BroadcastReceiver { 
    @Override 
    public void onReceive(Context context, Intent intent) { 
     if (intent.getAction().equals(RefreshTask.REFRESH_DATA_INTENT)) { 
      // Do stuff - maybe update my view based on the changed DB contents 
     } 
    } 
} 

Ich deklariere meinen Empfänger an der Spitze o f die Klasse:

private DataUpdateReceiver dataUpdateReceiver; 

I onResume außer Kraft setzen hinzuzufügen:

if (dataUpdateReceiver == null) dataUpdateReceiver = new DataUpdateReceiver(); 
IntentFilter intentFilter = new IntentFilter(RefreshTask.REFRESH_DATA_INTENT); 
registerReceiver(dataUpdateReceiver, intentFilter); 

Und ich onPause hinzuzufügen außer Kraft setzen:

if (dataUpdateReceiver != null) unregisterReceiver(dataUpdateReceiver); 

Nun meine Tätigkeit hört für meinen Dienst zu sagen: „Hey Geh und aktualisiere dich selbst. " Ich könnte Daten im Intent übergeben, anstatt die Datenbanktabellen zu aktualisieren und dann zurückzugehen, um die Änderungen in meiner Aktivität zu finden, aber da die Änderungen trotzdem bestehen bleiben sollen, ist es sinnvoll, die Daten über db zu übergeben.

+4

Wie? Was Sie beschreiben, ist genau das, was ich brauche. Aber mehr als das brauche ich vollständigen Beispielcode. Danke im Voraus. Zu guter Letzt, das letzte Mal, als ich versucht habe, ein Widget zu schreiben, dauerte der Nachrichtenteil zwei Monate. Lachen Sie, wenn Sie wollen, aber Sie Experten müssen mehr veröffentlichen, seit IMHO Android ist komplizierter als iOS! –

+1

aaaaaaa, Der Leitfaden für fortgeschrittene Programmierer von Busy Coders bietet ein hervorragendes Kapitel über Widgets. Es ist die einzige Ressource, die mich durch dieses Durcheinander gebracht hat. CommonsWare, der Autor, hat 115k Ruf auf StackOverflow, und ich empfehle das Buch. http://commonsware.com/AdvAndroid/ – MaximumGoat

+2

Ich denke, dass dies der richtige Weg ist, um dafür zu gehen! –

18

Mit einem Messenger ist eine weitere einfache Art und Weise zwischen einem Dienst und einer Aktivität zu kommunizieren .

Erstellen Sie in der Aktivität einen Handler mit einem entsprechenden Messenger. Dies wird Nachrichten von Ihrem Service behandeln.

class ResponseHandler extends Handler { 
    @Override public void handleMessage(Message message) { 
      Toast.makeText(this, "message from service", 
        Toast.LENGTH_SHORT).show(); 
    } 
} 
Messenger messenger = new Messenger(new ResponseHandler()); 

Der Messenger kann es zu einer Nachricht, indem es an den Dienst übergeben werden:

Message message = Message.obtain(null, MyService.ADD_RESPONSE_HANDLER); 
message.replyTo = messenger; 
try { 
    myService.send(message); 
catch (RemoteException e) { 
    e.printStackTrace(); 
} 

Ein vollständiges Beispiel in den API-Demos finden: MessengerService and MessengerServiceActivity. Siehe das vollständige Beispiel für die Funktionsweise von MyService.

2

Ein anderer Weg könnte die Verwendung von Beobachtern mit einer falschen Modellklasse über die Aktivität und den Dienst selbst sein, die eine MVC-Mustervariation implementieren. Ich weiß nicht, ob es der beste Weg ist, dies zu erreichen, aber es ist die Art und Weise, die für mich funktioniert hat. Wenn Sie ein Beispiel brauchen, fragen Sie danach und ich poste etwas.

+0

Ich kämpfe mit ähnlichen denken. Ich habe Aktivität und 2 Dienste, die das gleiche Modell haben. Das Modell kann entweder in einer Aktivität oder in einem dieser Dienste aktualisiert werden, also habe ich über die Verwendung von Observern nachgedacht, kann es aber nicht herausfinden. Können Sie ein Beispiel veröffentlichen, um Ihre Idee zu zeigen? – pzo

2

Um @MrSnowflake zu verfolgen, antworten Sie mit einem Codebeispiel. This is the XABBER now open source Application class. Die Klasse Application zentralisiert und koordiniert Listeners und ManagerInterfaces und mehr. Manager aller Art werden dynamisch geladen. Activity´s gestartet im Xabber wird in welcher Art von Listener sie sind. Und wenn ein Service starten, melden Sie sich in der Application Klasse als gestartet. Jetzt, um eine Nachricht an eine Activity senden alles, was Sie tun müssen, ist Ihre Activity zu einem listener von welcher Art Sie benötigen. In der OnStart()OnPause() register/unreg. Die Service kann die Application Klasse für genau das listener fragen, es zu sprechen, und wenn es dort ist, dann ist die Aktivität bereit zu empfangen.

Durch die Application Klasse gehen Sie sehen, es gibt eine Beute mehr los als diese.

17

Ich bin überrascht, dass niemand Bezug auf Otto Ereignis Bus Bibliothek

http://square.github.io/otto/

ich dies habe mit meiner Android Apps gegeben hat und es funktioniert nahtlos.

+5

Alternativ gibt es die greenrobot-Bibliothek [EventBus] (http://greenrobot.github.io/EventBus/). – Jazzer

+10

Aber denken Sie daran, dass EventBus und Otto immer in einem Prozess sind. Wenn Sie einen Dienst verwenden, der sich in seinem eigenen Prozess befindet (mit der Einstellung "android: process =": ​​mein_service "begonnen wurde, können sie nicht kommunizieren. – Ron

6

Die andere Methode, die in den anderen Kommentaren nicht erwähnt wird, besteht darin, mithilfe von bindService() eine Bindung an den Service aus der Aktivität herzustellen und eine Instanz des Service im ServiceConnection-Callback abzurufen. Wie hier beschrieben http://developer.android.com/guide/components/bound-services.html

+2

Dies ist der richtige Weg, um eine Referenz der Serviceinstanz zu erhalten. –