zurück zum Artikel

Die Android-Uhren sind gereift

Tam Hanna
Die Android-Uhren sind gereift

Die erste Version von Googles Android für Wearables war äußerst spartanisch. Android Wear 2.0 ändert das radikal und funktioniert auch ohne verbundenes Smartphone.

Bisherige Smartwatches mit Android Wear waren im Großen und Ganzen auf das Anzeigen von Notifications und anderen Inhalten beschränkt, die sie von einem angeschlossenen Smartphone bezogen; Stand-alone-Funktionen gab es nur eingeschränkt. Das erwies sich für Google in mehrerer Hinsicht als hinderlich, besonders weil eine Uhr mit Android Wear 1.0 nur mit Android-Smartphones zusammenarbeiten konnte. Den Hardwareherstellern missfiel freilich, dass die oft zahlungskräftigen iPhone-Nutzer ihre Produkte nicht einsetzen konnten.

Da Android Wear 2.0 bisher kaum verbreitet ist, setzt der Artikel für die Beispiele auf Googles Emulator, der auf passender Hardware mehr als ausreichend schnell arbeitet. Als Entwicklungsumgebung dient Android Studio in der Version 2.3. Dazu gehört das API-Level 25, also das SDK für Android 7.1.1. Emulator-Images gibt es in Versionen für ARM und Intel, wobei nur letztere die Hardwarebeschleunigung aktueller CPUs nutzen.

Das Android-Wear-Image muss separat heruntergeladen werden.

Das Android-Wear-Image muss separat heruntergeladen werden.

Es gibt Applikationen für Android Wear, die auf API-Level 23 basieren und ein eingebettetes APK mitbringen. Wer bereits eine Applikation für Android Wear 1.0 ausgeliefert hat, muss alle Versionen des Haupt-APK mit einem eingebetteten zweiten APK für Android Wear 1.0 ausstatten. Sonst zerstört die aus dem Store heruntergeladene APK-Datei alle auf der Uhr des Nutzers befindlichen Informationen, was eine Ein-Stern-Bewertung garantiert. Aus diesem Grund sei explizit noch einmal auf die offizielle Anleitung zum Packaging [1] verwiesen.

Das Projekt benötigt im Feld Target Android Devices sowohl "Phone and Tablet" als auch "Wear". Im nächsten Schritt erhält das mobile Projekt eine leere Activity und die Applikationen der Uhr eine "Blank Wear Activity". Beim Benennen legt Android Studio für jede Plattform einen eigenen Flavor an, der seine eigenen Speicherelemente mitbringt. Android Studio muss beim Erstellen des Projekts Dateien aus dem Internet herunterladen, was einige Minuten in Anspruch nehmen kann.

Interessant wird es in in der Manifestdatei des Wear Flavor: Google deklariert dort die Nutzung des Wear-Features:

<manifest. . .>
<uses-feature android:name="android.hardware.type.watch" />
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />

Der gezeigte Metadata-Block informiert das Android-Ökosystem darüber, ob die Applikation ohne Companion App am Smartphone arbeitet.

Applikationen ohne Standalone-Parameter bietet der Store nur dann an, wenn die Uhr auf ein Smartphone mit Playstore zurückgreifen kann. Ein true bei standalone besagt explizit nicht, dass die App nicht mit einer Anwendung am Handy kommunizieren kann, sondern false gilt nur für den Fall, dass die Applikation ohne Handy-Applikation absolut nicht funktionstüchtig ist.

Smartwatches existieren sowohl mit quadratischem als auch mit rundem Display. Das Beispielprogramm verwendet daher zwei separate MainActivity-Dateien. Der erste Schritt verwendet die quadratische Anzeige. Das neue virtuelle Gerät lässt sich über den Assistenten in die Rubrik Wear mit der Vorlage "Android Square Round" auf Basis des API-Levels 25 erzeugen. Beim Start der App erscheint der Android Wear Homescreen. Auffällig ist der ebenfalls emulierte physikalische Knopf, der die Programmliste auf den Bildschirm holt. Dort findet sich unter anderem auch der PlayStore.

Android Wear 1.0 war darauf beschränkt, über das angeschlossene Telefon zu kommunizieren. Die entsprechende API steht nach wie vor zur Verfügung. Google weist in der Dokumentation allerdings an mehreren Stellen darauf hin, dass ihre Nutzung zum Zugriff auf das Internet ein explizites Antipattern darstellt.

Stattdessen sollen Entwickler die in den neuen Uhren eingebauten WLAN- und/oder GSM-Transmitter benutzen und das Telefon nur als Fallback einbinden. Allerdings routet ein Smartphone eine mit Bluetooth Low Energie (BLE) verbundene Uhr, wenn ihr keine eigene Verbindung zur Verfügung steht. Die maximale Bandbreite für BLE liegt jedoch im Kilobitbereich, und die permanente Aktivierung des Transmitters verbraucht viel Energie. Google begegnet diesem Problem durch die Einführung einer API, über die sich der Netzwerkzustands ermitteln lässt. Entwickler sollten aufwändige Operationen nur durchführen, wenn das Netzwerk eine hohe Bandbreite aufweist.

Das lässt sich in onCreate implementieren, wo das Laden der XML-Ressourcen über die WatchViewStub-Klasse erfolgt:

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final WatchViewStub stub =
(WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(
new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {

Der an dieser Stelle interessante Code findet sich in der Methode onLayoutInflated, deren Aufruf nach dem erfolgreichen Beleben der Steuerelementstruktur erfolgt:

mTextView = (TextView) stub.findViewById(R.id.text);
int MIN_BANDWIDTH_KBPS = 16; //4x Google-Empfehlung
myConnMan = (ConnectivityManager) getSystemService(
Context.CONNECTIVITY_SERVICE);
Network activeNetwork = myConnMan.getActiveNetwork();
if (activeNetwork != null) {
int bandwidth =
myConnMan.getNetworkCapabilities(activeNetwork)
.getLinkDownstreamBandwidthKbps();

ConnectivityManager bietet eine Funktion, die Daten über die aktive Netzwerkverbindung liefert. Der Code vergleicht sie mit der Konstante MIN_BANDWIDTH_KBS – der verwendete Wert ist das Vierfache dessen, was eine BLE-Verbindung laut Google-Dokumentation erreichen kann.

  if (bandwidth < MIN_BANDWIDTH_KBPS) {
mTextView.setText("Bandbreite unzureichend");
} else {
mTextView.setText("Bereit zum Netzwerkzugriff");
}
}
else{
mTextView.setText("Kein Netz!");
}

Bei einer unzureichenden Bandbreite erlaubt der ConnectivityManager das Anfordern einer schnelleren Netzwerkverbindung. Details dazu finden sich in einem offiziellen Beispiel, das auf GitHub [2] verfügbar ist. Der Emulator meldet Erfolg, da er von Haus aus auf das vorhandene Netzwerk zugreifen kann. Die im Android-Emulator verbauten Werkzeuge zur Geschwindigkeitslimitierung funktionieren allerdings momentan aufgrund eines Fehlers in QEMU nur leidlich. Google pflegt eine Liste von Workarounds [3].

Vor dem ersten Start müssen Entwickler die Berechtigung ACCESS_NETWORK_STATE deklarieren, da Android sonst beim Zugriff auf den Verbindungsstatus Probleme verursacht:

<manifest . . .>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE">
</uses-permission>

Die Beispielanwendung verwendet zum Veranschaulichen Grafiken, die den Preis von Edelmetallen wiedergeben und von KitCO stammt, das eine Gruppe von Diagrammen bereitstellt:

Entwickler sollten diese URLs nicht in kommerziellen Produkten einsetzen, aber in der Praxis reagiert das Unternehmen vergleichsweise gelassen bei der privaten Nutzung. Die folgenden Schritten setzen jedenfalls voraus, dass drei Bilder als Drawables vorliegen und mit geringem Aufwand in eine in das Layout zu integrierende ImageView geladen werden können.

Ein schöner Aspekt der menschlichen Physik ist, dass es pro Nase nur zwei Hände gibt. Halten beide je eine Kaffeetasse oder einen Dienstpass, können sie schwerlich den Touchscreen verwenden. Das als "Wrist Gestures" bezeichnetes Gesteneingabesystem ordnet daher den Bewegungen der Uhr bestimmten Handlungen zu. Entwickler sollten die Gesten jedoch niemals zur Aktivierung destruktiver Aktionen verwenden, da es nur eine Frage der Zeit ist, bis eine zufällige Bewegung versehentlich die Funktion auslöst.

Der einfachste Weg zur Einarbeitung in die Gesten ist die Nutzung des unter Settings | Gestures | Launch Tutorial bereitstehenden Tutorials. Der Emulator zeigt den Bewegungsmesser über ein Spezialmenü an, das über einen Klick auf Virtual Sensors bei den drei Punkten neben dem Hauptfenster erreichbar ist. In der Praxis sind Tests mit echter Hardware jedoch sinnvoller, da die Gesten-Emulation nur leidlich funktioniert.

Das virtuelle Telefon erlaubt in stark eingeschränktem Rahmen das Simulieren von Bewegungen.

Das virtuelle Telefon erlaubt in stark eingeschränktem Rahmen das Simulieren von Bewegungen.

Bei aktivierter Gestenverarbeitung setzen die meisten Steuerelemente des Android-Wear-Frameworks automatisch Gesten in Bewegungen um. Der folgende Code verwendet die Gesteninformation direkt. Die von der Navigationsbewegungen verschickten Ereignisse sind Keycodes, die sich nicht wesentlich von klassischen Tastenereignissen unterscheiden. Ein Beispiel für das Entgegennehmen sieht folgendermaßen aus:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_NAVIGATE_NEXT:
myCounter++;
if(myCounter>2)myCounter=0;
refreshScreen();
return true;
case KeyEvent.KEYCODE_NAVIGATE_PREVIOUS:
myCounter--;
if(myCounter<0)myCounter=2;
refreshScreen();
return true;
}
return super.onKeyDown(keyCode, event);
}

Von besonderer Bedeutung ist, dass die Verarbeitung von Tastatur-Events in Android auf eine kooperative Weise erfolgt: Clients sollten die von ihnen nicht verarbeitete Ereignisse ans System weiterreichen. Das erfolgt durch den Aufruf von super.onKeyDown. Im Interesse kompakten Codes soll es an dieser Stelle ausreichen, statt einer echten Aktualisierung des Bildschirms eine Meldung in die Debugger-Konsole zu schicken.

private void refreshScreen()
{
Log.e("Wear", Integer.toString(myCounter));
}

Damit ist das Programm bereit zum Testen auf dem Emulator. Passende Bewegungen an den Slidern der Gestensteuerung sollten zur Ausgabe des Ereignisses auf der Konsole führen. Mittelfristig wäre es schön, wenn Google Entwicklern eine würdevollere Testmethode anböte.

Bei Uhren bezeichnet der Begriff Komplikationen die Elemente, die nicht der eigentlichen Zeitanzeige dienen: In klassischen Armbanduhren sind den gebotenen Features aus Platzgründen enge Grenzen gesetzt.

Im Handcomputerbereich ist das Problem komplizierter: Zum einen ist auf einer Smartwatch der Anzeigeplatz ebenfalls eng beschränkt. Zum anderen führt das Anbieten einer Bitmap-Zeichenfläche am Homescreen zu Ärger: Microsoft implementierte so den klassischen Homescreen von Windows Mobile, der in der Theorie (und bei sorgfältiger Programmierung) perfekt funktionierte. Leider hatte Microsoft die Rechnung ohne diejenigen Nutzer gemacht, die schlecht programmierte Plug-Ins massenhaft aktivierten. Diese gingen zu Lasten der Rechenleistung und des Arbeitsspeichers, sodass es am Ende Kritiken über kurze Laufzeit und schlechte Reaktivität hagelte.

Auch in Android Wear erfolgt der Datenaustausch über einen Zwischenhändler.

Auch in Android Wear erfolgt der Datenaustausch über einen Zwischenhändler.

(Bild: Google)

Aktuell unterstützt Google sechs unterschiedliche Datentypen, die in der folgenden Tabelle kurz zusammengefasst sind. Entwickler müssen einen passenden Datentyp auszuwählen und ihn über einen Service bereitstellen. Die folgenden Schritte setzen auf RANGED_VALUE. Android Wear bietet bei Symbolen neben der normalen Grafik zusätzlich eine farbreduzierte (Burn-in Protection) Version an, ein Symbol, das die teilweise einbrenngefährdeten OLED-Bildschirme (Stichwort SGS II und ebay-App) weniger stark belasten sollte.

Typ Erforderliche Daten Optionale Daten
SHORT_TEXT Kurztext Symbol
Farbreduziertes Symbol
Kurztitel
ICON Symbol Farbreduziertes Symbol
RANGED_VALUE Wert
Maximum
Minimum
Symbol
Farbreduziertes Symbol
Kurztext
Kurztitel
LONG_TEXT Langer Text Langtitel
Symbole
Bitmap
SMALL_IMAGE Bitmap
LARGE_IMAGE Bitmap

Zunächst müssen Entwickler die Manifestdatei um die Deklaration eines ComplicationProviderService erweitern, was mit folgendem Snippet geschieht, das die Berechtigung BIND_COMPLICATION_PROVIDER deklariert:

<application . . .>
<service
android:name=".HeiseRangedClass"
android:label="HeiseRangedAnzeiger"
android:icon="@mipmap/ic_launcher"
android:permission=
"com.google.android.wearable.permission.
BIND_COMPLICATION_PROVIDER">
<intent-filter>
<action android:name=
"android.support.wearable.complications.
ACTION_COMPLICATION_UPDATE_REQUEST"/>
</intent-filter>
//Metadata hier
</service>

Aktualisierungen können entweder automatisch oder auf Anfrage erfolgen. Für Letzteres muss UPDATE_PERIOD_SECONDS den Wert Null haben. Folgender Code aktualisiert die Darstellung alle zehn Sekunden.

<meta-data
android:name=
"android.support.wearable.complications.
UPDATE_PERIOD_SECONDS"
android:value="10" />

Die Metadata-Deklarationen gehören in den Service und dürfen nicht auf Ebene des Gesamtsystems liegen. Auch die Festlegung der unterstützten Datentypen erfolgt im Manifest – interessanterweise können Komplikations-Provider auch mehrere Werte unterstützen:

<meta-data
android:name=
"android.support.wearable.complications.SUPPORTED_TYPES"
android:value="RANGED_VALUE,SHORT_TEXT,ICON"/>

Für das Beispiel genügt es, value nur einen Wert zuzuweisen:

<meta-data
android:name=
"android.support.wearable.complications.SUPPORTED_TYPES"
android:value="RANGED_VALUE"/>


Beim Kompilieren der Komplikationen kommt es immer wieder zu Meldungen, die auf fehlende Namespaces hinweisen. Die Ursache dafür ist, dass die Deklaration der Klasse in build.gradle veraltet ist. Abhilfe schafft das Verwenden von + statt einer spezifischen Version:

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.support:wearable:+'


Nun fehlt noch die in der Manifest-Datei angemeldete Serviceklasse, deren Deklaration das Vorhandensein einer Methode voraussetzt:

public class HeiseRangedClass
extends ComplicationProviderService {
@Override
public void
onComplicationUpdate(int complicationId,
int dataType,
ComplicationManager complicationManager) {
}

Von besonderem Interesse sind dabei zwei Felder: complicationID liefert eine Korrelations-ID, während dataType die Art der Anfrage gemäß der oben gezeigten Tabelle ermöglicht. Die eigentliche Methode sieht folgendermaßen aus:

public void
onComplicationUpdate(int complicationId,
int dataType,
ComplicationManager complicationManager) {
ComplicationData complicationData = null;
if (dataType == ComplicationData.TYPE_RANGED_VALUE){

Im ersten Schritt erfolgt eine Überprüfung der angelieferten Anfrage: Der ComplicationData-Builder verlangt bereits bei der Initialisierung der neuen Klasseninstanz Informationen darüber, welche Art von Anfrage zu bedienen ist. Das eigentliche Bevölkern der Anfrage erfolgt über die von JavaScript bekannte Vorgehensweise der verketteten Aufrufe von set-Methoden, die jeweils den aktuellen Zustand der Klasse zurückliefern und sich somit als a().b().c() verknüpfen lassen:

complicationData =
new ComplicationData.Builder(
ComplicationData.TYPE_RANGED_VALUE)
.setValue(22)
.setMinValue(0)
.setMaxValue(100)
.setShortText(ComplicationText.plainText("Hallo Welt!"))
.build();}

Da das Aktualisieren der Bildschirminhalte Energie verbraucht, können Complication-Services das Betriebssystem auch dazu auffordern, keine Aktualisierung durchzuführen:

if(complicationData!=null)complicationManager.updateComplicationData(complicationId, complicationData);
else complicationManager.noUpdateRequired(complicationId);


Google spendiert eine Gruppe zusätzlicher Funktionen, deren Implementierung weitere Informationen über das Verhalten beziehungsweise den Lebenszyklus des Gesamtsystems liefern. Der folgende Code zeigt die beiden Methoden, deren Aufruf beim Aktivieren und Deaktivieren der Komplikation erfolgt:

@Override
public void
onComplicationActivated(int complicationId,
int dataType,
ComplicationManager complicationManager) {
Log.d("Heise",
"onComplicationActivated(): " + complicationId);
super.onComplicationActivated(complicationId,
dataType,
complicationManager);
}
@Overridepublic void
onComplicationDeactivated(int complicationId) {
Log.d("Heise",
"onComplicationDeactivated(): " + complicationId);
super.onComplicationDeactivated(complicationId);
}

Nach dem Laden der Applikation in den Emulator öffnet langes Klicken im leeren Bereich der Uhrenanzeige das Kontextemenü. Entwickler können die Option data und dort das Beispielprogramm auswählen. Anschließend sollte die Komplikation wie in der folgenden Abbildung erscheinen.

Alles funktioniert problemlos.

Alles funktioniert problemlos.

Die zweite Version von Android Wear behebt aus technischer Sicht alle Schwächen des Vorgängers. Ob das für einen Durchbruch im Markt reicht, ist nach Meinung des Autors fragwürdig.

Der Grund dafür hat mit der Technik wenig zu tun: Die meisten Menschen dürften Armbanduhren als Schmuckstück oder aus Image-Gründen kaufen. Ob eine Smartwatch in der Weinbar und dem Club für Aufmerksamkeit sorgt, muss sich in der Praxis zeigen. Auch der zum Markteintritt als hip geltende Kollege aus Cupertino konnte nur wenig Land gewinnen: Keramik-Rado und Rolex sind derzeit eben noch nicht als Smartwatch verfügbar.

Das vollständige Codebeispiel steht zum Herunterladen [4] bereit.

Tam Hanna
befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handheldcomputern. Er entwickelt Programme für diverse Plattformen.
(rme [5])


URL dieses Artikels:
https://www.heise.de/-3767991

Links in diesem Artikel:
[1] https://developer.android.com/training/wearables/apps/packaging.html
[2] https://github.com/googlesamples/android-WearHighBandwidthNetworking
[3] https://code.google.com/p/android/issues/detail?id=204894
[4] http://www.tamoggemon.com/test/2017/HeiseGoldWear2.zip
[5] mailto:rme@ix.de