Is it just み?

Tag: Java

[Android] Service automatisch mit dem Gerät starten

by on Sep.30, 2011, under Sonstiges

Nachdem ich beschrieben habe, wie man auch einfache Weise zwischen einem Service und einer Activity kommunizieren kann, möchte ich natürlich den Service auch als solchen nutzen, nämlich als stillen Wächter im Hintergrund der (fast) immer aktiv ist.

Dazu muss ich den Service irgendwie automatisch starten. Wie man das macht findet man relativ schnell – nun will ich es hier einmal am oben genannten Beispiel zeigen.

Um einen Service zu starten, wenn etwas bestimmtes passiert, müssen wir drei Dinge tun:

  1. Unseren Service auf die gewünschten Ereignisse “lauschen” lassen.
  2. Uns die Erlaubnis holen diese Ereignisse zu bedienen.
  3. Den Service starten sobald das Ereignis eintrifft.

Um das zu bewerkstelligen muss man sogenannte Intents nutzen. Dafür gibt es Intents selber, Intent Categories, Broadcastreceiver usw. und das ist IMHO nicht sonderlich intuitiv – daher habe ich darüber einen eigenen Artikel geschrieben.

“Lauschen” kann man mit Hilfe eines so genannten BroadcastReceivers, das ist einfach eine Klasse die die Klasse BroadcastReceiver erweitert und deren Methode onReceive() automatisch aufgerufen wird sobald ein registriertes Broadcast-Event eintritt.

In meinem Fall füge ich die folgende Klasse “ServiceBroadcastReceiver” einfach neue Klasse meinem Service hinzu:

    public class ServiceBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action != null) {
                if (
                    action.equals(Intent.ACTION_BOOT_COMPLETED)
                    || action.equals(Intent.ACTION_USER_PRESENT)
                ) {
                    Intent startServiceIntent = new Intent(context, MyService.class);
                    context.startService(startServiceIntent);
                }
            }
        }
    }

Wichtig: Man kann den Receiver augenscheinlich nicht als Nested Class innerhalb einer anderen Klasse wie z.B. dem Service deklarieren, wenn man ihn über das Manifest registrieren möchte.

Das obige Codebeispiel sorgt dafür, dass im Falle der Ereignisse “Gerät wurde gestartet” (Intent.ACTION_BOOT_COMPLETED) und “Bildschirm wurde entsperrt” (Intent.ACTION_USER_PRESENT) der Service “MyService” gestartet wird. Falls der Service schon läuft passiert nichts.

Damit lauscht der Receiver allerdings noch nicht, er muss noch in der AndroidManifest.xml als Receiver eingetragen werden, damit das System weiß, dass dort überhaupt ein Receiver ist.

Ein kleiner Hinweis zum Namen: Wenn ich einen Klassennamen in das Manifest eintrage, kann ich das auf mehrere Arten tun:

  1. Vollständiger Name. Sowas wie “com.hell.the.what.android.MyClass”
  2. Name im festgelegten “default” package. Sowas wie “.MyClass” – bedeutet, dass “MyClass” in dem package gesucht wird, dass im Manifest als attribut “package” festgelegt wurde.
  3. Nur der Name. Sowas wie “MyClass” – sollte im Zweifelsfall genauso funktionieren wie 2, ist aber AFAIK nicht spezifiziert.

(Spezialfall: Der Vollständige Name einer “Nested Class” wird als Name der umgebenden Klasse, dahinter ein “$” und dahinter der Name der verschachtelten Klasse geschrieben: “.MyClass$NestedClass”  – Aber wie oben bereits gesagt, funktioniert das augenscheinlich nicht bei Receivern.)

In diesem Fall fügt man folgendes in das AndroidManifest ein:

    <receiver android:name=".ServiceBroadcastReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            <action android:name="android.intent.action.USER_PRESENT"></action>
            <category android:name="android.intent.category.DEFAULT"></category>
        </intent-filter>
    </receiver>

Damit sagt man nichts anderes als “Im Falle von BOOT_COMPLETED oder USER_PRESENT, rufe meinen Receiver auf”. Die Default Category muss hinzugefügt werden, damit der Matcher auf die beiden Ereignisse anspricht – ansonsten passiert einfach nichts.

Comments Off on [Android] Service automatisch mit dem Gerät starten :, more...

[Android] Das Problem mit Intents

by on Sep.30, 2011, under Programmierung

Bei Android dreht sich alles um Intents. Wenn man eine einfache Applikation in Eclipse entwickelt muss man sich damit nicht unbedingt auseinandersetzen, weil die korrekten Daten für einen eingetragen werden, aber spätestens wenn man einen Service implementiert oder auf externe Ereignisse reagieren will muss man sich mit Intents beschäftigen.

Da ich Probleme damit hatte das System zu verstehen, will ich hier einmal das was ich gelernt habe zusammenfassen, schon alleine weil ein Aufschreiben immer dabei hilft das eigene Wissen in geordnete Bahnen zu lenken und Lücken zu finden.

Was sind Intents

Intents sind erst einmal nur eines: Nachrichten.

Google hat in seinem Android-Betriebssystem quasi eine Architektur gebaut, in der alle möglichen Komponenten getrennt voneinander erstellt werden können und über diese Nachrichten miteinander kommunizieren, sich aufrufen und aufeinander reagieren können. Intents sind quasi IPC, Hooks, Systemintegration und Prozess-Aufrufe in einem.

Intents können für folgende Aufgaben genutzt werden:

  1. Eine Aktivity oder einen Service starten
  2. Das Programm im System verankern
  3. Auf ein Ereignis reagieren

Dafür gibt es zwei Arten von Intents (die im Quelltext beide ein “Intent” sind), die sich durch die enthaltene Information unterscheiden:

  • explizite Intents
  • implizite Intents

Explizite Intents enthalten einen absoluten Komponentennamen, z.B. den genauen Namen des Service der gestartet werden soll. Android kümmert sich darum, dass diese Intents genau bei dieser Komponente ankommen.

Implizite Intents enthalten quasi “nur” Informationen darüber was passiert – wer/was darauf reagiert hängt davon ab welche Applikationen sich im System mit einem passenden Intent-Filter registriert haben. Passt der Implizite Intent auf den registrierten Filter einer Applikation wird die Komponente zu der der Filter definiert wurde aufgerufen.

Eine Aktivity oder einen Service starten

Der einfachste und offensichtlichste Fall. Genau dafür gibt es folgende Methoden der Klasse Context: startActivity und startService – denen übergibt man einen Intent der den genauen Namen der Activity bzw. des Service (also ein expliziter Intent) enthält (man kann darüber hinaus nahezu beliebige zusätzliche Informationen in den Intent packen und diese dann beim Starten des Service oder der Activity abfragen). Das macht man im Code einer Activity dann z.B. so:

    startService(new Intent(this, MyService.class));

Hier wird ein neuer Intent erstellt, mit der aufrufenden Activity als Context und dem zu startenden Service als Komponentenname  direkt an die Methode startService der Activity übergeben wird. Es handelt sich hier um einen expliziten Intent, da die zu startende Komponente eindeutig spezifiziert ist (nämlich durch den Wert von MyService.class).

Das Programm im System verankern

Dieser Fall ist deutlich weniger intuitiv als der vorherige. Ich will das ganze mal an einem in wohl 95% aller Applikationen vorhandenem Beispiel veranschaulichen:

Um meine Applikation in der Liste der vorhandenen Applikationen aufzulisten muss ich diese im System registrieren – soweit alles wie üblich. Allerdings muss ich bei Android nicht die Applikation, sondern z.B. eine meiner Activities registrieren, und zwar für den Intent der für die Applikationsliste “gesendet” wird. Genauer: Im Manifest meiner Applikation muss stehen, dass meine Activity auf den Intent mit der Action “android.intent.action.MAIN” in der (Intent-)Category “android.intent.category.LAUNCHER” registriert ist.

Im Manifest sieht das dann z.B. so aus:

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"></action>
                <category android:name="android.intent.category.LAUNCHER"></category>
            </intent-filter>
        </activity>
    </application>

Eclipse erstellt diesen Intent-Filter sogar automatisch.

Auf ein Ereignis reagieren

Wenn ich es richtig verstehe ist dies eigentlich nur eine andere Form des vorherigen.

Wenn ich z.B. möchte, dass mein Service automatisch gestartet wird, sobald das Telefon startet, dann muss ich ihn für die Action “android.intent.action.BOOT_COMPLETED” registrieren und dann natürlich noch eine Möglichkeit zur Verfügung stellen darauf zu reagieren. Die Action wird vom System erzeugt, dann guckt das System nach was sich dafür registriert hat (genauer, welche Intent-Filter passen) und schickt dann diesen Intent an genau diese registrierten Applikationen – das nennt Android dann “Broadcast”.

Um einen Broadcast empfangen (“to receive”) zu können benötigt man einen – Überraschung! – BroadcastReceiver, darauf gehe ich in diesem Posting näher ein. Ein ganz einfacher Receiver sieht ungefähr so aus:

    public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Do something based on the Intent received
        }
    }

Da wo jetzt der Kommentar steht würden wir natürlich überprüfen welche Action gesendet wurde und im Falle von “android.intent.action.BOOT_COMPLETED” eben den Service starten.

Jetzt muss ich diesen Receiver genau wie meine Activity im vorherigen Beispiel noch im Manifest eintragen und dort in Form eines Intent-Filters mitteilen wann das System denn nun den Receiver aufrufen soll. Das sieht dann in unserem Beispiel so aus:

    <receiver android:name="com.example.MyBroadcastReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            <category android:name="android.intent.category.HOME"></category>
        </intent-filter>
    </receiver>

Dieser Abschnitt muss (genau wie das activity-Element oben) innerhalb des application-elements geschachtelt sein.

Hinweis

Hier endet mein “Wissen” über Intents, ich will schwer hoffen, dass sich das in nächster Zeit noch vertiefen wird, aber das kommt dann mit der weiteren Entwicklung.

Über Hinweise, Korrekturen und Kritik würde ich mich sehr freuen – ich hab schließlich noch einen langen Weg vor mir bis zum Android-Experten. 😉

Comments Off on [Android] Das Problem mit Intents :, more...

[Android] Zugriff auf UI durch einen Service

by on Sep.29, 2011, under Programmierung

Da ich mich gerade wieder mit Android beschäftige, möchte ich hier einmal einen einfachen Weg darstellen, wie man die UI (Activity) von einem Hintergrund-Dienst (Service) aus verändern kann.

Wenn man im Netz nach einer Lösung für dieses Problem sucht, wird man schnell in Richtung Handler geschickt – diese Lösung finde ich für Minimalaufgaben zu groß und zu aufwändig. Ich wollte einfach nur Nachrichten an die UI schicken, sofern diese überhaupt da ist – dafür benötigt man keinen extra Handler.

Das Problem ist folgendes: Der Service läuft im Hintergrund und losgelöst von der UI – er arbeitet also in einem eigenen Thread. Die UI kann nur aus ihrem eigenen Thread verändert werden. Sobald ich also versuche aus dem Service direkt eine Methode in der UI-Activity aufzurufen um eben diese zu verändern, hagelt es Exceptions. Um das zu umgehen muss ich also irgendwie dem UI-Thread sagen “mach das für mich” – hier ist die einfachste Methode, die ich gefunden habe:

Man gibt dem UI-Thread Runnable-Objekte in seine Queue von abzuarbeitenden Aufgaben mit Hilfe der post()-Methode der View.

Und nun die Details:

Erst einmal muss ich den Service starten, so dass er unabhängig von der UI im Hintergrund läuft – das ist ein anderes Thema, dazu gibt es aber genügend Tutorials im Netz.

Wenn der Service einmal läuft muss er natürlich wissen ob eine Activity vorhanden ist und falls ja, wie er sie erreichen kann. Und hier kommt die schnelle Lösung ins Spiel, die mir zwar theoretisch ein wenig Kopfschmerzen macht, aber praktisch wunderbar funktioniert (sollte dies ein erfahrener Android-Entwickler lesen: Ich freue mich sehr über Verbesserungsvorschläge): Da der Service nur einmal im Hintergrund läuft, geben wir ihm einfach eine statische Singleton-Methode, die von der Activity abgefragt werden kann.

Jetzt müssen wir dem Service nur noch mitteilen, dass die Activity vorhanden ist (quasi wie einen Listener hinzufügen) und schon kann der Service jederzeit mit der Activity kommunizieren. Dafür erstellen wir eine Activity-Variable im Service und eine setActivity()-Methode. (Und falls wir mehrere Activities benachrichtigen wollen machen wir daraus eben eine List<Activity> und eine addActivity()-Methode.)

Jetzt kann der Service jederzeit Methoden auf der Activity aufrufen (so lange diese vorhanden ist).

Wir gehen also folgendermaßen vor:

  1. Activity wird gestartet.
  2. Activity erstellt einen Intent zum Starten des Service (da der Service nur einmal gestartet wird, wird dieser also ignoriert wenn der Service schon läuft).
  3. Die Activity erstellt einen Thread, der nichts anderes macht als die Singleton-Methode des Services aufzurufen – und zwar so lange bis ein valides Service-objekt zurückgegeben wird (das sollte im Normalfall sehr schnell gehen) und diesem nun die Activity mitteilt.
  4. Der Service überprüft wenn er etwas mitteilen soll ob die Activity vorhanden ist – und falls ja, teilt der Activity mit welche Methode ausgeführt werden soll.

Punkt vier ist der interessant Punkt, denn hier passiert die Übergabe von einem Thread in den anderen. In meinem Beispiel will ich der Activity einfach nur einen Text hinzufügen, dafür benutze ich folgenden Code in dem ich das Layout der Activity direkt manipuliere:

    public void showMessage(String message) {
        TextView tv = new TextView(this);
        tv.setText(message);
        layout.addView(tv);        
    }

Vorher habe ich das Layout (hier ein LinearLayout) und die View (hier Scrollview) in der onCreate()-Methode der Activity mit folgendem Code in den Membervariablen layout und view abgespeichert, so dass ich nun darauf zugreifen kann:

        view = new ScrollView(this);
        layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        view.addView(layout);        

        setContentView(view);

Die Methode showMessage() funktioniert so aber nur, wenn sie im UI-Thread aufgerufen wird. Um das ganze aus dem Thread des Service ausführen zu können, verändere ich den Code folgendermaßen: (“MainActivity” muss durch den Namen der Activity-Klasse ersetzt werden.)

    public void showMessage(final String message) {
        view.post(new Runnable() {
            @Override
            public void run() {
                TextView tv = new TextView(MainActivity.this);
                tv.setText(message);
                layout.addView(tv);        
            }
        });
    }

Man beachte das “final” vor dem Parameter – dies ist notwendig um den Wert der Variablen im anderen Thread nutzen zu können. Nun muss ich showMessage() nur noch mit meiner Nachricht als Parameter aufrufen und es wird automatisch im UI-Thread ausgeführt.

 

Damit alles läuft und der Service auch startet: Nicht vergessen den Service in die AndroidManifest.xml einzutragen.


(continue reading…)

Comments Off on [Android] Zugriff auf UI durch einen Service :, more...