Fragmentierung auf der Android-Plattform

Klassen, die es noch nicht gibt

Analog zu den Methoden gibt es Klassen, die man verwenden möchte, die jedoch nicht in den älteren Versionen vorhanden sind. Auch in diesem Fall wird es beim Entwickeln keine Kompilierungsfehler geben, beim Ausführen aber krachen. Da Klassen erst geladen werden müssen, ist hier die Situation etwas komplexer als bei den Methodenaufrufen. Die Lösung besteht darin, den Zugriff auf die kritischen Klassen über einen Proxy abzusichern. Hierzu ein Beispiel: Um die BackupManager Klasse zu verwenden, die erst ab Version 2.2 vorhanden ist, sieht die Proxy-Implementierung wie in Listing 1 aus (siehe auch Abb. 2).

Abb. 2: Klassendiagram für das Proxymuster
Listing 1
//BackupService.java
static boolean isPresent() {       new BackupManager(null);       return true;     }
//BackupProvider.java
static boolean isBackupServicePresent() {      try {       BackupService.isPresent();       return true;        } catch (VerifyError ve) {        Log.w(TAG, "BackupService not available");        }        return false;       }
//SampleActivity.java
if (!BackupProvider.isBackupServicePresent()) {       backupButton.setEnabled(false);
}

Die BackupService-Klasse enthält sämtliche Zugriffe auf den BackupManager und spiegelt damit seine Schnittstelle. Die BackupProvider-Klasse kapselt jeden Zugriff auf den BackupService in einem try…catch-Block. Die MyActivity-Klasse, in der wir den BackupManager verwenden wollen, benutzt nur die Methoden der BackupProvider-Instanz.

Ist man bereit, das Error Handling in der Activity zu handhaben, kann man die BackupProvider-Indirektion auch weglassen. Die Instanzierung der optionalen API-Klasse muss jedoch – bedingt durch das Classloading – in einer eigenen Proxy-Klasse erfolgen.

Hardwarefragmentierung

Mit unterschiedlichen Softwareversionen wissen wir nun umzugehen. Doch wie verhält es sich mit den variablen Hardwareausstattungen? Da Hardware stets durch Software gesteuert wird, nicht wesentlich anders. Grundsätzlich geht es hier um die Frage, wie man bestimmte Komponenten detektieren kann und das Deployment der App abhängig von der verfügbaren Hardware steuert.

Die Sichtbarkeit im Marketplace wird über das App-Manifest kontrolliert. Hier werden alle verwendeten Hardwarefeatures definiert und zwar in Form von <uses-feature>-Elementen. Spezifizierte Featureanforderungen sind per default obligatorisch – diese Anforderungen müssen von den Zielgeräten erfüllt sein, ansonsten ist für sie die App im Marketplace auch nicht sichtbar. So kann man beispielsweise sicherstellen, dass eine Navigations-App auch stets einen GPS-Empfänger zur Verfügung hat.

Hardwarefeatures können auch als optional deklariert werden. Dabei wird das required-Attribut des <uses-feature>-Elements auf false gesetzt. Optionale Features bewirken keine Filterung im Marketplace, der Entwickler muss also den Code so gestalten, dass er auch ohne die entsprechende Hardware zurechtkommt. Zum Beispiel ist zur Bestimmung der genauen Orientierung eines Gerätes die Verwendung des Gyroskop-Sensors empfehlenswert. Fehlt dieser, kann man sich dennoch mit Beschleunigungssensor und Kompass behelfen.

Weniger offensichtlich als die deklarierten sind die impliziten Features. Diese werden aus den deklarierten Berechtigungen (Permissions) abgeleitet, und zwar als obligatorische Features. Beispiel: Die Permission, die Kamera zu benutzen, erfordert das Kamerahardwarefeature.

Hardwarefeatures werden an unterschiedlichen Orten des APIs verwaltet. Auf Sensoren wird dabei grundsätzlich über den SensorManager zugegriffen. Auf andere Funktionen über den PackageManager, die Configuration-Klasse oder über das Camera-Objekt. Hier ein paar Beispiele für die Abfrage der gebräuchlichsten Hardwarefeatures:

  • Temperatursensor:
  • SensorManager manager = (SensorManager) context.getSystemService(
                              Context.SENSOR_SERVICE);     Sensor temperatureSensor = manager.getDefaultSensor(Sensor.TYPE_TEMPERATURE);     if (temperatureSensor != null) {  
      //Temperatursensor vorhanden
    }
  • Physische Tastatur: Auf einem Android-Gerät kann eine komplette QWERTZ-, eine 12-Key- oder keine physische Tastatur vorhanden sein. Entsprechende Konstanten gibt es in der Configuration-Klasse.
  • boolean hasPhysicalKeyboard = context.getResources()         .getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
  • Touchscreen (Multitouch): Dieses und noch weitere Features können mit dem PackageManager geprüft werden. Die Konstanten dafür sind in der PackageManager-Klasse definiert.
  • fMultitouchPresent = context.getPackageManager().hasSystemFeature(
       PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
  • Vorderseitige Kamera: Die Kamerainformation wird von der entsprechend genannten CameraInfo-Klasse bezogen.
  • Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    for (int i = 0; i 

Eine Hardwareeigenschaft, die leider nicht über ein entsprechendes API abgefragt werden kann, ist die Leistung. Die CPU- und Grafikkartenleistung sowie der verfügbare Arbeitsspeicher variieren stark von Modell zu Modell. Für die meisten Anwendungen ist das kein Problem, für Spiele oder grafik- und berechnungsintensive Applikationen kann es leicht eines werden. Der prominenteste diesbezügliche Fall ist die Android-Portierung von Angry Birds. Auf gewissen älteren Geräten dauerte es teilweise bis zu 15 Minuten das Spiel zu starten.

Leider lässt uns der ansonsten sehr zuverlässige Emulator in diesem Fall im Stich. Der einzige Ausweg besteht darin, die Anwendung auf konkreten, als problematisch bekannten Geräten zu testen. Falls Schwierigkeiten identifiziert werden, können Optimierungen vorgenommen werden, oder der Entwickler kann eine abgespeckte Version der Anwendung für diese Geräte(klassen) zur Verfügung stellen. Die letzte Option ist, die betroffenen Geräte in der Marktkonsole von der Distribution auszuschließen.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.