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:
- Xamarin.Essentials – Plattformübergreifende APIs für allgemeine Features.
- SkiaSharp – Plattformübergreifende 2D-Grafiken.
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.