Teil 4: Umgang mit mehreren Plattformen

Handling Platform Divergence & Features

Divergenz ist nicht nur ein "plattformübergreifendes" Problem; Geräte auf der gleichen Plattform verfügen über unterschiedliche Funktionen (insbesondere die vielzahl von Android-Geräten, die verfügbar sind). Die offensichtlichste und grundlegende Bildschirmgröße ist, aber andere Geräteattribute können variieren und erfordern, dass eine Anwendung auf bestimmte Funktionen überprüft und sich je nach Anwesenheit (oder Abwesenheit) anders verhält.

Dies bedeutet, dass alle Anwendungen mit einer ordnungsgemäßen Beeinträchtigung der Funktionalität umgehen müssen oder einen nicht attraktiven Featuresatz mit dem kleinsten gemeinsamen Nenner darstellen. Mit der tiefen Integration von Xamarin in die nativen SDKs jeder Plattform können Anwendungen plattformspezifische Funktionen nutzen, sodass es sinnvoll ist, Apps zu entwerfen, um diese Features zu verwenden.

Eine Übersicht darüber, wie sich die Plattformen in der Funktionalität unterscheiden, finden Sie in der Dokumentation zu Plattformfunktionen.

Beispiele für Plattformabweichungen

Grundlegende Elemente, die plattformübergreifend vorhanden sind

Es gibt einige Merkmale mobiler Anwendungen, die universell sind. Hierbei handelt es sich um Konzepte auf höherer Ebene, die in der Regel für alle Geräte gelten und somit die Grundlage für das Design Ihrer Anwendung bilden können:

  • Featureauswahl über Registerkarten oder Menüs
  • Listen mit Daten und Bildlauf
  • Einzelne Ansichten von Daten
  • Bearbeiten einzelner Ansichten von Daten
  • Rückwärtsnavigation

Beim Entwerfen ihres allgemeinen Bildschirmflusses können Sie eine allgemeine Benutzererfahrung auf diesen Konzepten basieren.

Plattformspezifische Attribute

Zusätzlich zu den grundlegenden Elementen, die auf allen Plattformen vorhanden sind, müssen Sie wichtige Plattformunterschiede in Ihrem Design behandeln. Möglicherweise müssen Sie die folgenden Unterschiede berücksichtigen (und codespezifisch schreiben), um diese Unterschiede zu behandeln:

  • Bildschirmgrößen – Einige Plattformen (z. B. iOS und frühere Windows Telefon-Versionen) verfügen über standardisierte Bildschirmgrößen, die relativ einfach für das Ziel sind. Android-Geräte verfügen über eine vielzahl von Bildschirmabmessungen, die mehr Aufwand erfordern, um ihre Anwendung zu unterstützen.
  • Navigationsmetaphern – Unterscheiden Sie sich auf verschiedenen Plattformen (z. B. Hardwaretaste "Zurück", Panorama-UI-Steuerelement) und innerhalb von Plattformen (Android 2 und 4, i Telefon vs iPad).
  • Tastaturen – Einige Android-Geräte verfügen über physische Tastaturen, während andere nur über eine Softwaretastaturen verfügen. Code, der erkennt, wann eine Softtastatur einen Teil des Bildschirms verdeckt, muss für diese Unterschiede sensibel sein.
  • Toucheingabe und Gesten – Die Unterstützung des Betriebssystems für die Gestenerkennung variiert, insbesondere in älteren Versionen jedes Betriebssystems . Frühere Versionen von Android verfügen über sehr eingeschränkte Unterstützung für Toucheingabevorgänge, was bedeutet, dass die Unterstützung älterer Geräte möglicherweise separaten Code erfordert.
  • Pushbenachrichtigungen – Es gibt unterschiedliche Funktionen/Implementierungen auf jeder Plattform (z. B. Live-Kacheln unter Windows).

Gerätespezifische Features

Ermitteln Sie, welche Mindestfunktionen für die Anwendung erforderlich sind; oder wenn Sie entscheiden, welche zusätzlichen Features auf jeder Plattform genutzt werden sollen. Code wird benötigt, um Features zu erkennen und Funktionen zu deaktivieren oder Alternativen anzubieten (z. B. eine Alternative zum geografischen Standort könnte sein, um dem Benutzer die Eingabe eines Standorts oder eine Auswahl aus einer Karte zu ermöglichen):

  • Kamera – Die Funktionalität unterscheidet sich von allen Geräten: Einige Geräte verfügen nicht über eine Kamera, andere haben sowohl front- als auch nach hinten gerichtete Kameras. Einige Kameras sind in der Lage, Videoaufnahmen aufzunehmen.
  • Geo-Standort & Karten – Unterstützung für GPS- oder WLAN-Standort ist auf allen Geräten nicht vorhanden. Apps müssen auch auf die unterschiedlichen Genauigkeitsebenen achten, die von den einzelnen Methoden unterstützt werden.
  • Beschleunigungsmesser, Gyroskop und Kompass – Diese Features befinden sich häufig nur in einer Auswahl von Geräten auf jeder Plattform, sodass Apps fast immer einen Fallback bereitstellen müssen, wenn die Hardware nicht unterstützt wird.
  • Twitter und Facebook – nur "integriert" unter iOS5 bzw. iOS6. In früheren Versionen und anderen Plattformen müssen Sie Ihre eigenen Authentifizierungsfunktionen und -schnittstellen direkt mit der API der einzelnen Dienste bereitstellen.
  • Near Field Communications (NFC) – Nur auf (einigen) Android-Smartphones (zum Zeitpunkt des Schreibens).

Umgang mit Plattform-Divergenzen

Es gibt zwei verschiedene Ansätze zur Unterstützung mehrerer Plattformen aus derselben Codebasis, die jeweils eine eigene Gruppe von Vorteilen und Nachteilen aufweisen.

  • Platform Abstraction – Business Façade Pattern, bietet einen einheitlichen Zugriff über Plattformen hinweg und abstrahiert die jeweiligen Plattformimplementierungen in einer einzigen, einheitlichen API.
  • Divergente Implementierung – Aufruf bestimmter Plattformfeatures über divergierende Implementierungen über Architekturtools wie Schnittstellen und Vererbung oder bedingte Kompilierung.

Plattformabstraktion

Klassenstraktion

Verwenden von Schnittstellen oder Basisklassen, die im freigegebenen Code definiert sind und in plattformspezifischen Projekten implementiert oder erweitert werden. Das Schreiben und Erweitern von gemeinsam genutztem Code mit Klassenabstraktionen eignet sich besonders für portable Klassenbibliotheken, da sie über eine begrenzte Teilmenge des Frameworks verfügen und keine Compilerdirektiven enthalten können, um plattformspezifische Codeverzweigungen zu unterstützen.

Schnittstellen

Mithilfe von Schnittstellen können Sie plattformspezifische Klassen implementieren, die weiterhin an Ihre freigegebenen Bibliotheken übergeben werden können, um allgemeinen Code zu nutzen.

Die Schnittstelle wird im freigegebenen Code definiert und als Parameter oder Eigenschaft an die freigegebene Bibliothek übergeben.

Die plattformspezifischen Anwendungen können dann die Schnittstelle implementieren und trotzdem gemeinsam genutzten Code nutzen, um sie zu verarbeiten.

Vorteile

Die Implementierung kann plattformspezifischen Code und sogar plattformspezifische externe Bibliotheken enthalten.

Nachteile

Müssen Sie Implementierungen erstellen und an den freigegebenen Code übergeben. Wenn die Schnittstelle tief im freigegebenen Code verwendet wird, wird sie dann über mehrere Methodenparameter übergeben oder anderweitig über die Aufrufkette gedrückt. Wenn der freigegebene Code viele verschiedene Schnittstellen verwendet, müssen sie alle erstellt und im freigegebenen Code an einer beliebigen Stelle festgelegt werden.

Vererbung

Der freigegebene Code kann abstrakte oder virtuelle Klassen implementieren, die in einem oder mehreren plattformspezifischen Projekten erweitert werden können. Dies ähnelt der Verwendung von Schnittstellen, aber mit einem bereits implementierten Verhalten. Es gibt verschiedene Standpunkte darüber, ob Schnittstellen oder Vererbung eine bessere Entwurfsauswahl sind: Insbesondere weil C# nur eine einzelne Vererbung zulässt, kann sie die Art und Weise diktieren, wie Ihre APIs in Zukunft entworfen werden können. Verwenden Sie vererbung mit Vorsicht.

Die Vor- und Nachteile von Schnittstellen gelten gleichermaßen für die Vererbung, mit dem zusätzlichen Vorteil, dass die Basisklasse einen Implementierungscode enthalten kann (möglicherweise eine gesamte plattformunabhängige Implementierung, die optional erweitert werden kann).

Xamarin.Forms

Weitere Informationen finden Sie in der Dokumentation zu Xamarin.Forms .

Andere plattformübergreifende Bibliotheken

Diese Bibliotheken bieten auch plattformübergreifende Funktionalität für C#-Entwickler:

Bedingte Kompilierung

Es gibt einige Situationen, in denen Ihr freigegebener Code immer noch auf jeder Plattform anders funktioniert, möglicherweise auf Klassen oder Features, die sich anders verhalten. Die bedingte Kompilierung eignet sich am besten für Freigegebene Objektprojekte, auf die in mehreren Projekten mit unterschiedlichen Symbolen verwiesen wird.

Xamarin-Projekte definieren __MOBILE__ immer, was sowohl für iOS- als auch für Android-Anwendungsprojekte zutrifft (beachten Sie den doppelten Unterstrich vor und nach der Korrektur auf diesen Symbolen).

#if __MOBILE__
// Xamarin iOS or Android-specific code
#endif

iOS

Xamarin.iOS definiert __IOS__ , welche Sie zum Erkennen von iOS-Geräten verwenden können.

#if __IOS__
// iOS-specific code
#endif

Es gibt auch Watch- und TV-spezifische Symbole:

#if __TVOS__
// tv-specific stuff
#endif

#if __WATCHOS__
// watch-specific stuff
#endif

Android

Code, der nur in Xamarin.Android-Anwendungen kompiliert werden sollte, kann folgendes verwenden:

#if __ANDROID__
// Android-specific code
#endif

Jede API-Version definiert auch eine neue Compilerdirektive. So können Sie mit Code wie diesem Features Features hinzufügen, wenn neuere APIs gezielt sind. Jede API-Ebene enthält alle Symbole auf niedrigerer Ebene. Dieses Feature ist nicht wirklich nützlich, um mehrere Plattformen zu unterstützen; in der Regel reicht das __ANDROID__ Symbol aus.

#if __ANDROID_11__
// code that should only run on Android 3.0 Honeycomb or newer
#endif

Mac

Xamarin.Mac definiert __MACOS__ , welche Sie nur für macOS kompilieren können:

#if __MACOS__
// macOS-specific code
#endif

Universelle Windows-Plattform (UWP)

Verwenden Sie WINDOWS_UWP. Es gibt keine Unterstriche, die die Zeichenfolge wie die Xamarin-Plattformsymbole umgeben.

#if WINDOWS_UWP
// UWP-specific code
#endif

Verwenden der bedingten Kompilierung

Ein einfaches Beispiel für eine bedingte Kompilierung ist das Festlegen des Dateispeicherorts für die SQLite-Datenbankdatei. Die drei Plattformen haben geringfügig unterschiedliche Anforderungen für die Angabe des Dateispeicherorts:

  • iOS – Apple bevorzugt nicht-benutzerbezogene Daten, die an einem bestimmten Speicherort (dem Bibliotheksverzeichnis) abgelegt werden, es gibt jedoch keine Systemkonstante für dieses Verzeichnis. Plattformspezifischer Code ist erforderlich, um den richtigen Pfad zu erstellen.
  • Android – Der von diesem Zurückgegebene Environment.SpecialFolder.Personal Systempfad ist ein zulässiger Speicherort zum Speichern der Datenbankdatei.
  • Windows Telefon – Der isolierte Speichermechanismus lässt nicht zu, dass ein vollständiger Pfad angegeben wird, nur ein relativer Pfad und Dateiname.
  • Universelle Windows-Plattform – Verwendet Windows.Storage APIs.

Der folgende Code verwendet die bedingte Kompilierung, um sicherzustellen, dass dies DatabaseFilePath für jede Plattform korrekt ist:

public static string DatabaseFilePath
{
    get
    {
        var filename = "TodoDatabase.db3";
#if SILVERLIGHT
        // Windows Phone 8
        var path = filename;
#else

#if __ANDROID__
        string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
#else
#if __IOS__
        // we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms
        // (they don't want non-user-generated data in Documents)
        string documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); // Documents folder
        string libraryPath = Path.Combine (documentsPath, "..", "Library");
#else
        // UWP
        string libraryPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
#endif
#endif
        var path = Path.Combine(libraryPath, filename);
#endif
        return path;
    }
}

Das Ergebnis ist eine Klasse, die auf allen Plattformen erstellt und verwendet werden kann und die SQLite-Datenbankdatei an einem anderen Speicherort auf jeder Plattform platziert.