Don’t call us, we’ll call you

Android Tutorial: Umstellung der Kommunikation von Pull auf Push

Lars Röwekamp, Arne Limburg
we-call-you

© Shutterstock / Sergey Nivens

Mobile Anwendungen zeigen oftmals gänzlich andere Kommunikationsmuster auf als ihre Pendants aus der Webwelt. Die App öffnen, den aktuellen Status abfragen und gleich wieder schließen, und das viele Male am Tag. So in etwa sieht das typische Nutzungsverhalten aus. Nicht selten kommt es dabei durch die vielen kleinen Requests zu einer erhöhten Last auf dem Server, die so ursprünglich nicht geplant war – die damit verbundenen Probleme eingeschlossen. Eine mögliche Lösung, dem erhöhten Lastaufkommen Herr zu werden, stellt die Umstellung des Kommunikationsmusters von Pull auf Push dar.

Klassische Webanwendungen kommunizieren in der Regel nach dem bekannten Request-Response-Muster. Der Client fragt gezielt Informationen bzw. Daten beim Server an, und der Server sendet diese, nach dem Processing, an den Client zurück. Zur Reduzierung des Lastaufkommens wird dabei häufig auf AJAX oder ähnliche Mechanismen gesetzt. Das Grundprinzip bleibt aber stets das gleiche. Das eben beschriebene Kommunikationsmuster geht so lange gut, wie sich die Anzahl der Anfragen in überschaubaren Grenzen hält. Gleichzeitig macht es nur dann Sinn, wenn bei wiederholter Anfrage an den Server auch tatsächlich neue bzw. geänderte Daten für den anfragenden Client vorliegen.

Während das eben beschriebene Pattern für typische Webanwendungen durchaus valide erscheint, sieht dies bei Apps häufig anders aus. Wieder und wieder fragen diese beim Server nach neuen Informationen bzw. Daten, ohne dass sich der serverseitige Status in der Zwischenzeit geändert hat. Das Resultat sind eine Vielzahl unnötiger Server-Requests und eine damit verbundene hohe Auslastung des Servers ohne wirklichen Nutzermehrwert.

Ist dies wirklich ein reales oder eher ein theoretisches Problem? Eine Studie der IBM, bezogen auf Apps im Bankenumfeld, zeigte bei der Analyse des Use Cases „check bank account“ einen Overhead von knapp 80 Prozent. Anders formuliert haben vier von fünf Anfragen lediglich Traffic auf dem Server erzeugt, ohne dem Nutzer einen zusätzlichen Mehrwert zu bieten. Was liegt also näher, als nur dann zu kommunizieren, wenn wirklich neue Informationen bzw. Daten am Server vorliegen, und als logische Konsequenz das Kommunikationsprotokoll entsprechend von Pull auf Push umzustellen?

Push ist nicht gleich Push

In Artikeln, Blogs oder anderen Publikationen findet man unterschiedlichste Beschreibungen des Begriffs „Push“. Bevor wir also zeigen, wie sich eine Server-side-Push-Kommunikation unter Android realisieren lässt, wollen wir kurz auf die unterschiedlichen Varianten eingehen:

  • Simulated Push via repeated Pull Request
  • Push Notification, Pull Payload
  • Real Push with Payload

Die erste Variante ist nicht wirklich ein Push-Mechanismus, sondern simuliert diesen lediglich. Der Client triggert regelmäßig den Server an und fragt, ob neue Daten vorhanden sind. Ist dies der Fall, sendet der Server die Daten an den Client, wo sie im Anschluss verarbeitet, also z. B. in einer lokalen DB gespeichert werden. Der Vorteil dieser Variante gegenüber einer klassischen Request-Response-Kommunikation aus der App heraus ist, dass die Anfragen mittels clientseitigem Service, der im Hintergrund läuft, auch dann ausgeführt werden können, wenn die App gerade nicht aktiv ist. Der Nutzer hat somit beim Start der App immer die aktuellen Daten vorliegen. Das oben beschriebene Problem der erhöhten Serverlast wird mit dieser Variante allerdings nicht beseitigt. Ganz im Gegenteil, bei schlecht gewähltem Pull-Intervall wird es sogar noch verstärkt.

Bei der zweiten Variante – Push Notification, Pull Payload – meldet sich der Server via Push Notification beim Client, sobald neue Daten vorliegen. Die Daten selbst sind dabei allerdings nicht Bestandteil der Notification. Sind die Daten für den Client von Interesse, stellt dieser eine gezielte Anfrage an den Server. Auch dieses Modell erzeugt natürlich eine gewisse Last auf dem Server. Dies geschieht allerdings nur dann, wenn tatsächlich neue Daten vorliegen und somit die zur Last führenden Requests auch einen wirklichen Mehrwert für den Client bringen. Unnötige Calls dagegen werden automatisch vermieden.

Noch effizienter im Sinne der anfallenden Requests ist die letzte Variante. Bei „Real Push with Payload“ benachrichtigt der Server den Client nicht nur, sondern schickt die relevanten bzw. interessanten Daten gleich mit. Ein weiterer Request seitens des Clients ist somit obsolet.

Welche der drei Varianten zu bevorzugen ist, hängt stark von der App, der Häufigkeit der serverseitigen Datenänderung und der Größe der zu übertragenden Daten ab. Die erste Variante – Simulated Push via repeated Pull Request – macht in der Regel nur Sinn, wenn der Nutzer bzw. die App bewusst ein Abfrageintervall wählt, das in etwa identisch mit der Änderungshäufigkeit der Daten auf dem Server ist oder deutlich geringer. Die zweite Variante – Push Notification, Pull Payload – ist immer dann sinnvoll, wenn die zu übertragenden Daten recht groß sind, da dieser Mechanismus dem Client die Möglichkeit gibt, bewusst zu entscheiden, ob und wann die Daten geladen werden sollen. Die dritte und letzte Variante – Real Push with Payload – wird in der Regel bei eher überschaubaren Datenmengen verwendet, bei denen der Overhead eines zusätzlichen Requests deutlich ins Gewicht fallen würde.

Nachdem geklärt ist, was wir unter dem Begriff „Push“ verstehen, wollen wir uns anschauen, wie sich die eigene Android-App um einen solchen Push-Mechanismus erweitern lässt. Um uns das Leben dabei möglichst einfach zu machen, verwenden wir dazu Google Cloud Messaging und ein von Android Studio generiertes Backend auf Basis der Google App Engine zum Versenden von Nachrichten.

Google Cloud Messaging

Um serverseitige Messages via Google Cloud Messaging erhalten zu können, muss sich der Client zunächst beim Google-Cloud-Messaging-Server mithilfe einer vorab durch den Entwickler generierten Sender-ID einmalig registrieren und im Anschluss die als Resultat dieses Vorgangs erhaltene Registration-ID an den eigentlichen Server der Anwendung senden (Abb. 1).

Abb. 1: Google Cloud Messaging in Aktion

Abb. 1: Google Cloud Messaging in Aktion

Der Server der Anwendung kann nun mithilfe der ID, wann immer sinnvoll, Nachrichten an den Google-Cloud-Messaging-Server schicken, der diese dann an das Zieldevice „pusht“.

Soweit die Theorie. Ein klassisches Pattern für die Implementierung der Message-Verarbeitung auf dem Client sieht so aus, dass dort für die eingehenden Nachrichten ein WakefulBroadcastReceiver implementiert und als Empfänger für Google Cloud Messages registriert wird. Der Receiver nimmt eine eingehende Message entgegen und delegiert das erhaltene Intent mit der eigentlichen Nachricht als Payload weiter an einen Service, der wiederum für die spezifische Abarbeitung der Nachricht zuständig ist (Listing 1). Um sicherzustellen, dass der Service die Abarbeitung vollständig durchführen kann, ohne dass sich das Device zwischendurch „schlafen legt“, sollte der Aufruf des Service via startWakefulService(…) erfolgen.

Listing 1: WakefulBroadcastReceiver als Message Handler

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        // Explicitly specify that GcmIntentService will handle the intent. 
        ComponentName comp = new ComponentName(context.getPackageName(),
                GcmIntentService.class.getName());

        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
    }
}

Für den Service bietet es sich an, als Typ IntentService zu wählen, da so sichergestellt wird, dass die Abarbeitung innerhalb des Service in einem eigenen Thread erfolgt und es somit keine Konflikte mit dem UI-Thread geben kann. Der Service evaluiert in unserem Fall den Typ der eingegangenen Message und führt im Anschluss die passende (Business-)Logik mit der Message Payload aus – z. B. Speichern von Daten in der lokalen DB. Im Anschluss setzt der Service via NotificationManager eine lokale Notification ab, die den Nutzer dazu animieren soll, sich die neuen Daten in seiner App anzuschauen. Am Ende der eben beschriebenen Abarbeitung innerhalb des Service ist darauf zu achten, dass das Wake Lock des WakefulBroadcastReceivers via completeWakefulIntent(intent) wieder freigeben wird (Listing 2).

Listing 2: IntentService zur Bearbeitung der Payload

public class GcmIntentService extends IntentService {

    ...

    public GcmIntentService() {
        super("GcmIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();

        // retrieve GoogleCloudMessaging instance 
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);

        // handle different possible message types
        //
        //   1. check for message type: 
        //     The getMessageType() intent parameter must be the intent you received 
        //     in your BroadcastReceiver. 
        //    - MESSAGE_TYPE_SEND_ERROR: error situation
        //    - MESSAGE_TYPE_DELETED: deleted messages on server
        //    - MESSAGE_TYPE_MESSAGE: intent extra "message" contains server message 
        //
        //   2. call sendNotification to build a notification with an 
        //      adequate message for received message type
        //

        String messageType = gcm.getMessageType(intent); 

        if (!extras.isEmpty()) {  // has effect of unparcelling Bundle
            /*
             * Filter messages based on message type. Since it is likely that GCM
             * will be extended in the future with new message types, just ignore
             * any message types you're not interested in, or that you don't
             * recognize. 
             */
            if (GoogleCloudMessaging.
                    MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
                // handle error ...
            } else if (GoogleCloudMessaging.
                    MESSAGE_TYPE_DELETED.equals(messageType)) {
                // If it's a regular GCM message, do some work ...
            } else if (GoogleCloudMessaging.
                    MESSAGE_TYPE_MESSAGE.equals(messageType)) {
                // do some stuff with the message payload.
                ...
                // Post notification of received message.
                String message = extras.getString("message", "unknown");

                // local method to build notification with pending intent
                sendNotification("Received: " + message);
             }
         }
        // Release the wake lock provided by the WakefulBroadcastReceiver.
        GcmBroadcastReceiver.completeWakefulIntent(intent);
    }
}

Kickstart

Um den App-Entwicklern den Einstieg möglichst einfach zu gestalten, bietet Android Studio die Möglichkeit, via Wizard einen eigenen Backend-Server auf Basis von Googles App Engine aufzusetzen. Zu diesem Zweck ruft man zunächst in seinem bestehenden App-Projekt New Module auf und wählt im anschließend aufpoppenden Wizard den Typ Google Cloud Module aus. Im nächsten Schritt bietet der Wizard drei unterschiedliche Varianten für das eigene Backend an:

  • App Engine Java Servlet Module
  • App Engine Java Endpoints Module
  • App Engine Backend with Google Cloud Messaging

Auch wenn theoretisch alle drei Varianten in Frage kämen, bietet sich in unserem Fall natürlich die dritte Variante an, da uns hier das vollständige Backend inklusive aller zum Versenden der Push-Nachrichten notwendigen Klassen generiert wird. Das Resultat ist ein einfacher Cloud Messaging Service inklusive rudimentärem Web-UI, mit dessen Hilfe wir Textnachrichten an registrierte Clients versenden können.

Fazit

Push-Nachrichten stellen ein probates Mittel im Kampf gegen überhöhte Serverlast dar. Wiederholte Anfragen des Clients nach neuen Daten, die nicht selten ins Leere laufen, werden durch gezielte, serverseitige Benachrichtigungen des Clients ersetzt. Last tritt so nur dann auf, wenn sich am Server der Status der Daten tatsächlich geändert hat. Studien zum Vergleich von Push vs. Pull haben bei einfachen Use Cases eine Verbesserung des Kommunikationsverhaltens von bis zu 80 Prozent aufgezeigt.

Unter Android bietet sich der Google-Cloud-Messaging-Server als Schaltzentrale zwischen dem eigenen Backend und den registrierten Clientdevices an. Treten serverseitige Änderungen auf, benachrichtigt der eigene Server den Google-Cloud-Messaging-Server, der wiederum die Nachricht an die Zieldevices „pusht“. Je nach gewähltem Kommunikationsmuster beinhaltet die Nachricht bereits die relevanten Daten (bis zu 4 KB) oder weist alternativ darauf hin, dass eine Anfrage an den Server neue Daten liefern wird.

Ganz nebenbei stellen Push-Nachrichten – in richtiger Dosierung – auch ein nicht zu unterschätzendes Marketinginstrument dar, da der Nutzer durch jede Nachricht erneut an die App erinnert wird und motiviert ist, sie zu öffnen.

Alles in allem lohnt sich ein Review der eigenen App-Kommunikationsarchitektur auf jeden Fall. Nicht selten bringt ein Umstieg von Pull auf Push für einzelne Bereiche bereits einen erheblichen Vorteil mit sich. Also ran an die eigene App und einfach einmal ausprobieren. In diesem Sinne: Stay tuned …

Aufmacherbild: Media technology illustration via Shutterstock / Urheberrecht: Sergey Nivens

Geschrieben von
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Kommentare

1
Hinterlasse einen Kommentar

avatar
4000
1 Kommentar Themen
0 Themen Antworten
0 Follower
 
Kommentar, auf das am meisten reagiert wurde
Beliebtestes Kommentar Thema
1 Kommentatoren
Peter Letzte Kommentartoren
  Subscribe  
Benachrichtige mich zu:
Peter
Gast
Peter

Bei mir bleibt die Frage offen, wie funktioniert Schritt 5 ? Läuft im Android client ein „server“ oder wieder ein polling gegen die GCM. GCM von Google ist selbstverständlich durch viele requests nicht so leicht in die Knie zu zwingen. Also, wo ist der reale Unterschied zu pull ?