Udostępnij za pośrednictwem


Część 5. Praktyczne strategie udostępniania kodu

W tej sekcji przedstawiono przykłady udostępniania kodu dla typowych scenariuszy aplikacji.

Warstwa danych

Warstwa danych składa się z aparatu magazynu i metod odczytywania i zapisywania informacji. W celu zapewnienia wydajności, elastyczności i zgodności międzyplatformowej aparat bazy danych SQLite jest zalecany w przypadku aplikacji międzyplatformowych platform Xamarin. Działa na wielu różnych platformach, w tym Windows, Android, iOS i Mac.

SQLite

SQLite to implementacja bazy danych typu open source. Źródło i dokumentacja można znaleźć w SQLite.org. Obsługa sqLite jest dostępna na każdej platformie mobilnej:

  • iOS — wbudowany w system operacyjny.
  • Android — wbudowany w system operacyjny od wersji 2.2 (poziom 10 interfejsu API).
  • Windows — zobacz rozszerzenie SQLite dla platforma uniwersalna systemu Windows.

Nawet jeśli aparat bazy danych jest dostępny na wszystkich platformach, metody natywne uzyskiwania dostępu do bazy danych są różne. Zarówno systemy iOS, jak i Android oferują wbudowane interfejsy API umożliwiające dostęp do biblioteki SQLite, które mogą być używane z platformy Xamarin.iOS lub Xamarin.Android, jednak korzystanie z natywnych metod zestawu SDK nie zapewnia możliwości udostępniania kodu (poza samymi zapytaniami SQL, zakładając, że są przechowywane jako ciągi). Aby uzyskać szczegółowe informacje na temat wyszukiwania CoreData natywnych funkcji bazy danych w klasie systemu iOS lub Android SQLiteOpenHelper , ponieważ te opcje nie są międzyplatformowe, wykraczają poza zakres tego dokumentu.

ADO.NET

Obsługa platform Xamarin.iOS i Xamarin.Android System.Data Mono.Data.Sqlite oraz (zobacz dokumentację platformy Xamarin.iOS, aby uzyskać więcej informacji). Użycie tych przestrzeni nazw umożliwia pisanie kodu ADO.NET, który działa na obu platformach. Edytuj odwołania projektu, aby uwzględnić System.Data.dll i Mono.Data.Sqlite.dll dodać te instrukcje using do kodu:

using System.Data;
using Mono.Data.Sqlite;

Następnie będzie działać następujący przykładowy kod:

string dbPath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "items.db3");
bool exists = File.Exists (dbPath);
if (!exists)
    SqliteConnection.CreateFile (dbPath);
var connection = new SqliteConnection ("Data Source=" + dbPath);
connection.Open ();
if (!exists) {
    // This is the first time the app has run and/or that we need the DB.
    // Copy a "template" DB from your assets, or programmatically create one like this:
    var commands = new[]{
        "CREATE TABLE [Items] (Key ntext, Value ntext);",
        "INSERT INTO [Items] ([Key], [Value]) VALUES ('sample', 'text')"
    };
    foreach (var command in commands) {
        using (var c = connection.CreateCommand ()) {
            c.CommandText = command;
            c.ExecuteNonQuery ();
        }
    }
}
// use `connection`... here, we'll just append the contents to a TextView
using (var contents = connection.CreateCommand ()) {
    contents.CommandText = "SELECT [Key], [Value] from [Items]";
    var r = contents.ExecuteReader ();
    while (r.Read ())
        Console.Write("\n\tKey={0}; Value={1}",
                r ["Key"].ToString (),
                r ["Value"].ToString ());
}
connection.Close ();

Rzeczywiste implementacje ADO.NET oczywiście byłyby podzielone między różne metody i klasy (ten przykład dotyczy tylko celów demonstracyjnych).

SQLite-NET — międzyplatformowe ORM

OrM (lub Maper relacyjny obiekt) próbuje uprościć przechowywanie danych modelowanych w klasach. Zamiast ręcznie pisać zapytania SQL, które TWORZĄ TABLEs lub SELECT, WSTAW i USUŃ dane ręcznie wyodrębnione z pól i właściwości klasy, ORM dodaje warstwę kodu, która to robi. Używając odbicia w celu zbadania struktury klas, ORM może automatycznie tworzyć tabele i kolumny zgodne z klasą i generować zapytania w celu odczytywania i zapisywania danych. Dzięki temu kod aplikacji może po prostu wysyłać i pobierać wystąpienia obiektów do usługi ORM, która zajmuje się wszystkimi operacjami SQL pod maską.

SQLite-NET działa jako prosty ORM, który umożliwia zapisywanie i pobieranie klas w sqlite. Ukrywa złożoność międzyplatformowego dostępu SQLite za pomocą kombinacji dyrektyw kompilatora i innych sztuczek.

Funkcje programu SQLite-NET:

  • Tabele są definiowane przez dodawanie atrybutów do klas modelu.
  • Wystąpienie bazy danych jest reprezentowane przez podklasę SQLiteConnection klasy , głównej klasy w bibliotece SQLite-Net.
  • Dane można wstawiać, wykonywać zapytania i usuwać przy użyciu obiektów. Instrukcje SQL nie są wymagane (chociaż w razie potrzeby można pisać instrukcje SQL).
  • Podstawowe zapytania Linq można wykonywać na kolekcjach zwracanych przez SQLite-NET.

Kod źródłowy i dokumentacja platformy SQLite-NET jest dostępna w witrynie SQLite-Net w serwisie github i została zaimplementowana w obu analizach przypadków. Poniżej przedstawiono prosty przykład kodu SQLite-NET (z analizy przypadku Tasky Pro ).

TodoItem Najpierw klasa używa atrybutów do zdefiniowania pola jako klucza podstawowego bazy danych:

public class TodoItem : IBusinessEntity
{
    public TodoItem () {}
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
}

TodoItem Umożliwia to utworzenie tabeli przy użyciu następującego wiersza kodu (bez instrukcji SQL) w wystąpieniuSQLiteConnection:

CreateTable<TodoItem> ();

Dane w tabeli można również manipulować innymi metodami w obiekcie SQLiteConnection (ponownie bez wymagania instrukcji SQL):

Insert (TodoItem); // 'task' is an instance with data populated in its properties
Update (TodoItem); // Primary Key field must be populated for Update to work
Table<TodoItem>.ToList(); // returns all rows in a collection

Zobacz kod źródłowy analizy przypadku, aby zapoznać się z kompletnymi przykładami.

Dostęp do plików

Dostęp do plików jest pewien, że jest kluczowym elementem dowolnej aplikacji. Typowe przykłady plików, które mogą być częścią aplikacji, to:

  • Pliki bazy danych SQLite.
  • Dane generowane przez użytkownika (tekst, obrazy, dźwięk, wideo).
  • Pobrane dane do buforowania (obrazy, pliki HTML lub PDF).

System.IO bezpośredni dostęp

Zarówno platformy Xamarin.iOS, jak i Xamarin.Android umożliwiają dostęp do systemu plików przy użyciu klas w System.IO przestrzeni nazw.

Każda platforma ma różne ograniczenia dostępu, które należy wziąć pod uwagę:

  • Aplikacje systemu iOS działają w piaskownicy z bardzo ograniczonym dostępem do systemu plików. Firma Apple dodatkowo określa, jak należy używać systemu plików, określając określone lokalizacje, których kopia zapasowa jest tworzona (i inne, które nie są). Aby uzyskać więcej informacji, zapoznaj się z przewodnikiem Praca z systemem plików na platformie Xamarin.iOS .
  • System Android ogranicza również dostęp do niektórych katalogów związanych z aplikacją, ale obsługuje również nośniki zewnętrzne (np. Karty SD) i uzyskiwanie dostępu do udostępnionych danych.
  • System Windows Phone 8 (Silverlight) nie zezwala na bezpośredni dostęp do plików — pliki można manipulować tylko przy użyciu programu IsolatedStorage.
  • Windows 8.1 projekty WinRT i Windows 10 UWP oferują tylko operacje asynchroniczne plików za pośrednictwem Windows.Storage interfejsów API, które różnią się od innych platform.

Przykład dla systemów iOS i Android

Poniżej przedstawiono prosty przykład zapisu i odczytywania pliku tekstowego. Użycie Environment.GetFolderPath umożliwia uruchamianie tego samego kodu w systemach iOS i Android, które zwracają prawidłowy katalog na podstawie konwencji systemu plików.

string filePath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "MyFile.txt");
System.IO.File.WriteAllText (filePath, "Contents of text file");
Console.WriteLine (System.IO.File.ReadAllText (filePath));

Zapoznaj się z dokumentem Xamarin.iOS Working with the File System (Praca z systemem plików Xamarin.iOS), aby uzyskać więcej informacji na temat funkcji systemu plików specyficznego dla systemu iOS. Podczas pisania międzyplatformowego kodu dostępu do plików należy pamiętać, że niektóre systemy plików mają wielkość liter i mają różne separatory katalogów. Dobrym rozwiązaniem jest zawsze użycie tej samej wielkości liter dla nazw plików i Path.Combine() metody podczas konstruowania ścieżek plików lub katalogów.

Windows.Storage dla systemów Windows 8 i Windows 10

Książka Creating Mobile Apps with Xamarin.Forms (Tworzenie aplikacji mobilnych za pomocą zestawunarzędzi Xamarin.Forms) Rozdział 20. Async i File I/O zawierają przykłady dla Windows 8.1 i Windows 10.

Za pomocą narzędzia DependencyService można odczytywać pliki i pliki na tych platformach przy użyciu obsługiwanych interfejsów API:

StorageFolder localFolder = ApplicationData.Current.LocalFolder;
IStorageFile storageFile = await localFolder.CreateFileAsync("MyFile.txt",
                                        CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(storageFile, "Contents of text file");

Aby uzyskać więcej informacji, zapoznaj się z rozdziałem 20 książki.

Wydzielona pamięć masowa w systemie Windows Phone 7 i 8 (Silverlight)

Izolowany magazyn to wspólny interfejs API do zapisywania i ładowania plików na wszystkich platformach iOS, Android i starszych.

Jest to domyślny mechanizm dostępu do plików w systemie Windows Phone (Silverlight), który został zaimplementowany w środowiskach Xamarin.iOS i Xamarin.Android, aby umożliwić pisanie wspólnego kodu dostępu do plików. Do System.IO.IsolatedStorage klasy można odwoływać się na wszystkich trzech platformach w projekcie udostępnionym.

Aby uzyskać więcej informacji, zapoznaj się z omówieniem izolowanego magazynu dla systemu Windows Phone .

Interfejsy API izolowanego magazynu nie są dostępne w bibliotekach klas przenośnych. Jedną z alternatyw dla PCL jest nuGet PCLStorage

Dostęp do plików międzyplatformowych w listach PCLS

Istnieje również nuGet zgodny z PCL — PCLStorage — który zapewnia międzyplatformowy dostęp do plików dla platform obsługiwanych przez platformę Xamarin i najnowszych interfejsów API systemu Windows.

Operacje sieciowe

Większość aplikacji mobilnych będzie mieć składnik sieci, na przykład:

  • Pobieranie obrazów, wideo i audio (np. miniatur, zdjęć, muzyki).
  • Pobieranie dokumentów (np. HTML, PDF).
  • Przekazywanie danych użytkownika (takich jak zdjęcia lub tekst).
  • Uzyskiwanie dostępu do usług internetowych lub interfejsów API innych firm (w tym protokołu SOAP, XML lub JSON).

Program .NET Framework udostępnia kilka różnych klas na potrzeby uzyskiwania dostępu do zasobów sieciowych: HttpClient, WebClienti HttpWebRequest.

HttpClient

Klasa HttpClient w System.Net.Http przestrzeni nazw jest dostępna na platformach Xamarin.iOS, Xamarin.Android i większości platform systemu Windows. Istnieje biblioteka nuGet biblioteki klienta HTTP firmy Microsoft, która może służyć do przenoszenia tego interfejsu API do przenośnych bibliotek klas (i Windows Phone 8 Silverlight).

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://xamarin.com");
var response = await myClient.SendAsync(request);

Element WebClient

Klasa WebClient udostępnia prosty interfejs API do pobierania danych zdalnych z serwerów zdalnych.

platforma uniwersalna systemu Windows operacje muszą być asynchroniczne, mimo że platformy Xamarin.iOS i Xamarin.Android obsługują operacje synchroniczne (które można wykonywać w wątkach w tle).

Kod prostej operacji asychronicznej WebClient to:

var webClient = new WebClient ();
webClient.DownloadStringCompleted += (sender, e) =>
{
    var resultString = e.Result;
    // do something with downloaded string, do UI interaction on main thread
};
webClient.Encoding = System.Text.Encoding.UTF8;
webClient.DownloadStringAsync (new Uri ("http://some-server.com/file.xml"));

WebClient zawiera DownloadFileCompleted również i DownloadFileAsync do pobierania danych binarnych.

HttpWebRequest

HttpWebRequest oferuje więcej możliwości dostosowywania niż WebClient i w rezultacie wymaga więcej kodu do użycia.

Kod prostej operacji synchronicznej HttpWebRequest to:

var request = HttpWebRequest.Create(@"http://some-server.com/file.xml ");
request.ContentType = "text/xml";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
    if (response.StatusCode != HttpStatusCode.OK)
        Console.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        var content = reader.ReadToEnd();
        // do something with downloaded string, do UI interaction on main thread
    }
}

W naszej dokumentacji usług sieci Web znajduje się przykład.

Osiągalność

Urządzenia przenośne działają w różnych warunkach sieciowych, od szybkich połączeń Wi-Fi lub 4G ze słabymi obszarami odbioru i wolnymi połączeniami danych EDGE. W związku z tym dobrym rozwiązaniem jest wykrycie, czy sieć jest dostępna, a jeśli tak, jaki typ sieci jest dostępny, przed podjęciem próby nawiązania połączenia z serwerami zdalnymi.

Akcje, które aplikacja mobilna może wykonać w takich sytuacjach, to:

  • Jeśli sieć jest niedostępna, poinformuj użytkownika. Jeśli użytkownik wyłączył go ręcznie (np. Tryb samolotowy lub wyłączanie sieci Wi-Fi), a następnie mogą rozwiązać ten problem.
  • Jeśli połączenie ma wartość 3G, aplikacje mogą zachowywać się inaczej (na przykład firma Apple nie zezwala na pobieranie aplikacji większych niż 20 Mb przez 3G). Aplikacje mogą używać tych informacji, aby ostrzegać użytkownika o nadmiernych porach pobierania podczas pobierania dużych plików.
  • Nawet jeśli sieć jest dostępna, dobrym rozwiązaniem jest zweryfikowanie łączności z serwerem docelowym przed zainicjowaniem innych żądań. Zapobiegnie to wielokrotnemu przekraczaniu limitu czasu operacji sieciowych aplikacji, a także umożliwi wyświetlenie użytkownikowi bardziej informacyjnego komunikatu o błędzie.

Usługi sieci Web

Zapoznaj się z naszą dokumentacją dotyczącą pracy z usługami sieci Web, która obejmuje uzyskiwanie dostępu do punktów końcowych REST, SOAP i WCF przy użyciu platformy Xamarin.iOS. Istnieje możliwość ręcznego tworzenia żądań usług internetowych i analizowania odpowiedzi, jednak dostępne są biblioteki, aby znacznie prostsze, w tym Azure, RestSharp i ServiceStack. Dostęp do podstawowych operacji WCF można uzyskać w aplikacjach platformy Xamarin.

Azure

Microsoft Azure to platforma w chmurze, która oferuje szeroką gamę usług dla aplikacji mobilnych, w tym magazynu i synchronizacji danych oraz powiadomień wypychanych.

Odwiedź azure.microsoft.com , aby wypróbować go bezpłatnie.

RestSharp

RestSharp to biblioteka platformy .NET, która może być uwzględniona w aplikacjach mobilnych w celu zapewnienia klienta REST, który upraszcza dostęp do usług internetowych. Ułatwia to udostępnienie prostego interfejsu API do żądania danych i przeanalizowanie odpowiedzi REST. Funkcja RestSharp może być przydatna

Witryna internetowa RestSharp zawiera dokumentację dotyczącą implementowania klienta REST przy użyciu usługi RestSharp. Usługa RestSharp udostępnia przykłady platform Xamarin.iOS i Xamarin.Android w witrynie GitHub.

W naszej dokumentacji usług sieci Web znajduje się również fragment kodu platformy Xamarin.iOS.

ServiceStack

W przeciwieństwie do restSharp, ServiceStack to zarówno rozwiązanie po stronie serwera do hostowania usługi internetowej, jak i biblioteki klienta, które można zaimplementować w aplikacjach mobilnych w celu uzyskania dostępu do tych usług.

Witryna internetowa serviceStack wyjaśnia przeznaczenie projektu i linki do dokumentów i przykładów kodu. Przykłady obejmują kompletną implementację usługi internetowej po stronie serwera, a także różne aplikacje po stronie klienta, które mogą uzyskać do niej dostęp.

WCF

Narzędzia platformy Xamarin mogą pomóc w użyciu niektórych usług Windows Communication Foundation (WCF). Ogólnie rzecz biorąc, platforma Xamarin obsługuje ten sam podzestaw po stronie klienta programu WCF, który jest dostarczany ze środowiskiem uruchomieniowym silverlight. Obejmuje to najpopularniejsze implementacje kodowania i protokołów WCF: komunikaty PROTOKOŁU SOAP zakodowane tekstowo za pośrednictwem protokołu transportowego BasicHttpBindingHTTP przy użyciu .

Ze względu na rozmiar i złożoność struktury WCF mogą istnieć bieżące i przyszłe implementacje usług, które wykraczają poza zakres obsługiwany przez domenę podzestawu klienta platformy Xamarin. Ponadto obsługa programu WCF wymaga użycia narzędzi dostępnych tylko w środowisku systemu Windows w celu wygenerowania serwera proxy.

Wątkowość

Czas odpowiedzi aplikacji jest ważny dla aplikacji mobilnych — użytkownicy oczekują, że aplikacje będą ładowane i wykonywane szybko. Ekran "zamrożony", który zatrzymuje akceptowanie danych wejściowych użytkownika, wydaje się wskazywać, że aplikacja uległa awarii, dlatego ważne jest, aby nie wiązać wątku interfejsu użytkownika z długotrwałymi wywołaniami blokującymi, takimi jak żądania sieciowe lub wolne operacje lokalne (na przykład rozpakowywanie pliku). W szczególności proces uruchamiania nie powinien zawierać długotrwałych zadań — wszystkie platformy mobilne zabiją aplikację, która trwa zbyt długo, aby załadować.

Oznacza to, że interfejs użytkownika powinien implementować "wskaźnik postępu" lub w inny sposób "możliwy do użycia" interfejs użytkownika, który jest szybki do wyświetlenia, oraz asynchroniczne zadania do wykonywania operacji w tle. Wykonywanie zadań w tle wymaga użycia wątków, co oznacza, że zadania w tle wymagają sposobu komunikowania się z powrotem z głównym wątkiem w celu wskazania postępu lub ukończenia.

Biblioteka zadań równoległych

Zadania utworzone za pomocą biblioteki zadań równoległych mogą być uruchamiane asynchronicznie i zwracać wątek wywołujący, co czyni je bardzo przydatnymi do wyzwalania długotrwałych operacji bez blokowania interfejsu użytkownika.

Prosta równoległa operacja zadania może wyglądać następująco:

using System.Threading.Tasks;
void MainThreadMethod ()
{
    Task.Factory.StartNew (() => wc.DownloadString ("http://...")).ContinueWith (
        t => label.Text = t.Result, TaskScheduler.FromCurrentSynchronizationContext()
    );
}

Kluczem jest TaskScheduler.FromCurrentSynchronizationContext() ponowne użycie elementu SynchronizationContext.Current wątku wywołującego metodę (tutaj główny wątek, który jest uruchomiony MainThreadMethod) jako sposób marshalingu wywołań wstecznych do tego wątku. Oznacza to, że jeśli metoda jest wywoływana w wątku interfejsu użytkownika, uruchomi operację ContinueWith z powrotem w wątku interfejsu użytkownika.

Jeśli kod uruchamia zadania z innych wątków, użyj następującego wzorca, aby utworzyć odwołanie do wątku interfejsu użytkownika, a zadanie nadal może wywołać go z powrotem:

static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();

Wywoływanie wątku interfejsu użytkownika

W przypadku kodu, który nie korzysta z biblioteki zadań równoległych, każda platforma ma własną składnię do marshalingu operacji z powrotem do wątku interfejsu użytkownika:

  • iOSowner.BeginInvokeOnMainThread(new NSAction(action))
  • Androidowner.RunOnUiThread(action)
  • Xamarin.FormsDevice.BeginInvokeOnMainThread(action)
  • WindowsDeployment.Current.Dispatcher.BeginInvoke(action)

Zarówno składnia systemów iOS, jak i Android wymaga udostępnienia klasy "context", co oznacza, że kod musi przekazać ten obiekt do dowolnych metod wymagających wywołania zwrotnego w wątku interfejsu użytkownika.

Aby wykonać wywołania wątków interfejsu użytkownika w kodzie udostępnionym, postępuj zgodnie z przykładem IDispatchOnUIThread (dzięki uprzejmości @follesoe). Zadeklaruj interfejs i program do interfejsu w kodzie udostępnionym IDispatchOnUIThread , a następnie zaimplementuj klasy specyficzne dla platformy, jak pokazano poniżej:

// program to the interface in shared code
public interface IDispatchOnUIThread {
    void Invoke (Action action);
}
// iOS
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly NSObject owner;
    public DispatchAdapter (NSObject owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.BeginInvokeOnMainThread(new NSAction(action));
    }
}
// Android
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly Activity owner;
    public DispatchAdapter (Activity owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.RunOnUiThread(action);
    }
}
// WP7
public class DispatchAdapter : IDispatchOnUIThread {
    public void Invoke (Action action) {
        Deployment.Current.Dispatcher.BeginInvoke(action);
    }
}

Deweloperzy platformy Xamarin.Forms powinni używać Device.BeginInvokeOnMainThread we wspólnym kodzie (projekty udostępnione lub PCL).

Możliwości i degradacja platformy i urządzeń

Bardziej szczegółowe przykłady obsługi różnych możliwości podano w dokumentacji możliwości platformy. Zajmuje się ona wykrywaniem różnych możliwości i sposobem bezproblemowego obniżenia wydajności aplikacji w celu zapewnienia dobrego środowiska użytkownika, nawet jeśli aplikacja nie może działać z pełnym potencjałem.