Udostępnij za pośrednictwem


Część 4. Praca z wieloma platformami

Obsługa rozbieżności między platformami i funkcjami

Rozbieżność nie jest tylko problemem "międzyplatformowym"; urządzenia na "tej samej" platformie mają różne możliwości (szczególnie różne dostępne urządzenia z systemem Android). Najbardziej oczywistym i podstawowym jest rozmiar ekranu, ale inne atrybuty urządzenia mogą się różnić i wymagają, aby aplikacja sprawdzała niektóre możliwości i zachowywała się inaczej na podstawie ich obecności (lub braku).

Oznacza to, że wszystkie aplikacje muszą mieć do czynienia z bezproblemowym obniżeniem funkcjonalności lub mogą stanowić nieatrakcyjny zestaw funkcji o najniższym wspólnym mianowniku. Głęboka integracja platformy Xamarin z natywnymi zestawami SDK każdej platformy umożliwia aplikacjom korzystanie z funkcji specyficznych dla platformy, dlatego warto zaprojektować aplikacje do korzystania z tych funkcji.

Zapoznaj się z dokumentacją możliwości platformy, aby zapoznać się z omówieniem różnic w funkcjonalności platformy.

Przykłady rozbieżności między platformami

Podstawowe elementy, które istnieją na różnych platformach

Istnieją pewne cechy aplikacji mobilnych, które są uniwersalne. Są to pojęcia wyższego poziomu, które są ogólnie prawdziwe dla wszystkich urządzeń i dlatego mogą stanowić podstawę projektu aplikacji:

  • Wybór funkcji za pomocą kart lub menu
  • Listy danych i przewijanie
  • Pojedyncze widoki danych
  • Edytowanie pojedynczych widoków danych
  • Przechodzenie z powrotem

Podczas projektowania przepływu ekranu wysokiego poziomu można opierać typowe środowisko użytkownika na tych pojęciach.

Atrybuty specyficzne dla platformy

Oprócz podstawowych elementów, które istnieją na wszystkich platformach, należy rozwiązać kluczowe różnice między platformami w projekcie. Może być konieczne rozważenie (i napisanie kodu w szczególności do obsługi) tych różnic:

  • Rozmiary ekranu — niektóre platformy (takie jak iOS i starsze wersje systemu Windows Telefon) mają standardowe rozmiary ekranu, które są stosunkowo proste do kierowania. Urządzenia z systemem Android mają wiele różnych wymiarów ekranu, które wymagają większego nakładu pracy w celu obsługi aplikacji.
  • Metafory nawigacji — różnią się między platformami (np. przycisk "wstecz", kontrolka Interfejs użytkownika Panorama) i na platformach (Android 2 i 4, i Telefon vs iPad).
  • Klawiatury — niektóre urządzenia z systemem Android mają fizyczne klawiatury, podczas gdy inne mają tylko klawiaturę programową. Kod, który wykrywa, kiedy klawiatura nietrwała jest zaciemniana częścią ekranu, musi być wrażliwa na te różnice.
  • Dotyk i gesty — obsługa systemu operacyjnego na potrzeby rozpoznawania gestów różni się, szczególnie w starszych wersjach każdego systemu operacyjnego. Wcześniejsze wersje systemu Android mają bardzo ograniczoną obsługę operacji dotykowych, co oznacza, że obsługa starszych urządzeń może wymagać oddzielnego kodu
  • Powiadomienia wypychane — istnieją różne możliwości/implementacje na każdej platformie (np. Dynamiczne kafelki w systemie Windows).

Funkcje specyficzne dla urządzenia

Określ, jakie są minimalne funkcje wymagane dla aplikacji; lub zdecyduj, jakie dodatkowe funkcje mają być używane na każdej platformie. Kod będzie wymagany do wykrywania funkcji i wyłączania funkcji lub oferowania alternatyw (np. alternatywą dla lokalizacji geograficznej może być pozwolinie użytkownikowi na wpisanie lokalizacji lub wybranie z mapy):

  • Aparat — funkcjonalność różni się między urządzeniami: niektóre urządzenia nie mają kamery, a inne mają zarówno kamery przednie, jak i tylne. Niektóre kamery są w stanie nagrywać wideo.
  • Lokalizacja geograficzna i mapy — obsługa lokalizacji GPS lub Wi-Fi nie jest obecna na wszystkich urządzeniach. Aplikacje muszą również obsługiwać różne poziomy dokładności obsługiwanej przez każdą metodę.
  • Akcelerometr, żyroskop i kompas — te funkcje są często spotykane tylko w wybranych urządzeniach na każdej platformie, więc aplikacje prawie zawsze muszą zapewnić rezerwowy sprzęt, gdy sprzęt nie jest obsługiwany.
  • Twitter i Facebook — tylko "wbudowane" odpowiednio w systemach iOS5 i iOS6. We wcześniejszych wersjach i innych platformach należy udostępnić własne funkcje uwierzytelniania i interfejs bezpośrednio przy użyciu interfejsu API poszczególnych usług.
  • Near Field Communications (NFC) — tylko na (niektóre) telefony z systemem Android (w momencie pisania).

Radzenie sobie z rozbieżnością platformy

Istnieją dwa różne podejścia do obsługi wielu platform z tej samej bazy kodu, z których każdy ma własny zestaw korzyści i wad.

  • Abstrakcja platformy — wzorzec fasady biznesowej zapewnia ujednolicony dostęp na różnych platformach i abstrahuje konkretne implementacje platformy do pojedynczego, ujednoliconego interfejsu API.
  • Implementacja rozbieżna — wywoływanie określonych funkcji platformy za pośrednictwem implementacji rozbieżnych za pośrednictwem narzędzi architektonicznych, takich jak interfejsy i dziedziczenie lub kompilacja warunkowa.

Abstrakcja platformy

Abstrakcja klas

Używanie interfejsów lub klas bazowych zdefiniowanych w kodzie udostępnionym i implementowanych lub rozszerzonych w projektach specyficznych dla platformy. Pisanie i rozszerzanie udostępnionego kodu z abstrakcjami klas jest szczególnie odpowiednie dla bibliotek klas przenośnych, ponieważ mają ograniczony podzbiór dostępnej dla nich struktury i nie mogą zawierać dyrektyw kompilatora do obsługi gałęzi kodu specyficznych dla platformy.

Interfejsy

Użycie interfejsów umożliwia zaimplementowanie klas specyficznych dla platformy, które nadal mogą być przekazywane do udostępnionych bibliotek, aby korzystać z wspólnego kodu.

Interfejs jest zdefiniowany w kodzie udostępnionym i przekazywany do biblioteki udostępnionej jako parametr lub właściwość.

Aplikacje specyficzne dla platformy mogą następnie implementować interfejs i nadal korzystać z udostępnionego kodu w celu jego przetworzenia.

Zalety

Implementacja może zawierać kod specyficzny dla platformy, a nawet odwołania do bibliotek zewnętrznych specyficznych dla platformy.

Wady

Należy utworzyć i przekazać implementacje do udostępnionego kodu. Jeśli interfejs jest używany głęboko w kodzie udostępnionym, kończy się przekazywanie przez wiele parametrów metody lub w inny sposób wypychane przez łańcuch wywołań. Jeśli kod udostępniony używa wielu różnych interfejsów, wszystkie muszą zostać utworzone i ustawione w udostępnionym kodzie gdzieś.

Dziedziczenie

Kod udostępniony może implementować klasy abstrakcyjne lub wirtualne, które można rozszerzyć w co najmniej jednym projektach specyficznych dla platformy. Jest to podobne do użycia interfejsów, ale z pewnym zachowaniem już zaimplementowane. Istnieją różne punkty widzenia dotyczące tego, czy interfejsy lub dziedziczenie są lepszym wyborem projektowym: w szczególności dlatego, że język C# zezwala tylko na pojedyncze dziedziczenie, które może dyktować sposób projektowania interfejsów API w przyszłości. Używaj dziedziczenia z ostrożnością.

Zalety i wady interfejsów mają zastosowanie równie do dziedziczenia, z dodatkową zaletą, że klasa bazowa może zawierać kod implementacji (być może całą niezależną implementację platformy, którą można opcjonalnie rozszerzyć).

Xamarin.Forms

Zapoznaj się z dokumentacją zestawu narzędzi Xamarin.Forms .

Inne biblioteki międzyplatformowe

Te biblioteki oferują również międzyplatformowe funkcje dla deweloperów języka C#:

Kompilacja warunkowa

Istnieją pewne sytuacje, w których kod udostępniony nadal będzie musiał działać inaczej na każdej platformie, ewentualnie uzyskiwanie dostępu do klas lub funkcji, które zachowują się inaczej. Kompilacja warunkowa działa najlepiej w przypadku projektów udostępnionych zasobów, w których ten sam plik źródłowy jest przywoływany w wielu projektach, które mają zdefiniowane różne symbole.

Projekty platformy Xamarin zawsze definiują __MOBILE__ , które wartości są prawdziwe zarówno w projektach aplikacji dla systemów iOS, jak i Android (zwróć uwagę na pre-podkreślenie podwójne i poprawkę po poprawce dla tych symboli).

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

iOS

Platforma Xamarin.iOS definiuje __IOS__ , których można użyć do wykrywania urządzeń z systemem iOS.

#if __IOS__
// iOS-specific code
#endif

Istnieją również symbole specyficzne dla zegarka i telewizora:

#if __TVOS__
// tv-specific stuff
#endif

#if __WATCHOS__
// watch-specific stuff
#endif

Android

Kod, który powinien zostać skompilowany tylko w aplikacjach platformy Xamarin.Android, może użyć następującego polecenia

#if __ANDROID__
// Android-specific code
#endif

Każda wersja interfejsu API definiuje również nową dyrektywę kompilatora, dlatego kod podobny do tego umożliwia dodawanie funkcji, jeśli są przeznaczone nowsze interfejsy API. Każdy poziom interfejsu API zawiera wszystkie symbole niższego poziomu. Ta funkcja nie jest naprawdę przydatna do obsługi wielu platform; zazwyczaj symbol będzie wystarczający __ANDROID__ .

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

Mac

Xamarin.Mac definiuje __MACOS__ , którego można użyć do kompilowania tylko dla systemu macOS:

#if __MACOS__
// macOS-specific code
#endif

Platforma uniwersalna systemu Windows (UWP)

Użyj witryny WINDOWS_UWP. Nie ma żadnych podkreśleń wokół ciągu, takich jak symbole platformy Xamarin.

#if WINDOWS_UWP
// UWP-specific code
#endif

Korzystanie z kompilacji warunkowej

Prostym przykładem analizy przypadku kompilacji warunkowej jest ustawienie lokalizacji pliku dla pliku bazy danych SQLite. Trzy platformy mają nieco inne wymagania dotyczące określania lokalizacji pliku:

  • iOS — firma Apple preferuje umieszczenie danych innych niż użytkownik w określonej lokalizacji (katalogu Biblioteka), ale nie ma stałej systemowej dla tego katalogu. Kod specyficzny dla platformy jest wymagany do utworzenia właściwej ścieżki.
  • Android — ścieżka systemowa zwrócona przez Environment.SpecialFolder.Personal program jest akceptowalną lokalizacją do przechowywania pliku bazy danych.
  • Windows Telefon — izolowany mechanizm magazynu nie zezwala na określenie pełnej ścieżki, tylko ścieżki względnej i nazwy pliku.
  • platforma uniwersalna systemu Windows — używa Windows.Storage interfejsów API.

Poniższy kod używa kompilacji warunkowej, aby upewnić się, że DatabaseFilePath jest ona poprawna dla każdej platformy:

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;
    }
}

Wynikiem jest klasa, którą można skompilować i użyć na wszystkich platformach, umieszczając plik bazy danych SQLite w innej lokalizacji na każdej platformie.