Freigeben über


Teil 5: Praktische Strategien für die Codefreigabe

In diesem Abschnitt finden Sie Beispiele zum Freigeben von Code für allgemeine Anwendungsszenarien.

Datenschicht

Die Datenschicht besteht aus einem Speichermodul und Methoden zum Lesen und Schreiben von Informationen. Zur Leistungs-, Flexibilitäts- und plattformübergreifenden Kompatibilität wird das SQLite-Datenbankmodul für plattformübergreifende Xamarin-Anwendungen empfohlen. Es wird auf einer Vielzahl von Plattformen ausgeführt, darunter Windows, Android, iOS und Mac.

SQLite

SQLite ist eine Open-Source-Datenbankimplementierung. Die Quelle und Dokumentation finden Sie unter SQLite.org. SQLite-Unterstützung ist auf jeder mobilen Plattform verfügbar:

Auch wenn das Datenbankmodul auf allen Plattformen verfügbar ist, unterscheiden sich die systemeigenen Methoden für den Zugriff auf die Datenbank. Sowohl iOS als auch Android bieten integrierte APIs für den Zugriff auf SQLite, die von Xamarin.iOS oder Xamarin.Android verwendet werden können. Die Verwendung der nativen SDK-Methoden bietet jedoch keine Möglichkeit, Code freizugeben (außer vielleicht die SQL-Abfragen selbst, vorausgesetzt, sie werden als Zeichenfolgen gespeichert). Ausführliche Informationen zu systemeigenen Datenbankfunktionen finden Sie CoreData in der iOS- oder Android-Klasse SQLiteOpenHelper . Da diese Optionen nicht plattformübergreifend sind, liegen sie außerhalb des Umfangs dieses Dokuments.

ADO.NET

Sowohl Xamarin.iOS als auch Xamarin.Android unterstützen System.Data und (weitere Informationen finden Sie in der Xamarin.iOS-Dokumentation).Mono.Data.Sqlite Mithilfe dieser Namespaces können Sie ADO.NET Code schreiben, der auf beiden Plattformen funktioniert. Bearbeiten Sie die Verweise des Projekts, um sie einzuschließen System.Data.dll und hinzuzufügen, und Mono.Data.Sqlite.dll fügen Sie diese using-Anweisungen zu Ihrem Code hinzu:

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

Anschließend funktioniert der folgende Beispielcode:

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 ();

Reale Implementierungen von ADO.NET würden offensichtlich auf verschiedene Methoden und Klassen aufgeteilt werden (dieses Beispiel dient nur zu Demonstrationszwecken).

SQLite-NET – Plattformübergreifendes ORM

Ein ORM (oder Object-Relational Mapper) versucht, die Speicherung von Daten zu vereinfachen, die in Klassen modelliert sind. Anstatt SQL-Abfragen manuell zu schreiben, die CREATE TABLEs oder SELECT, INSERT- und DELETE-Daten, die manuell aus Klassenfeldern und Eigenschaften extrahiert werden, fügt ein ORM eine Codeebene hinzu, die dies für Sie tut. Mithilfe von Reflexionen zur Untersuchung der Struktur Ihrer Klassen kann ein ORM automatisch Tabellen und Spalten erstellen, die einer Klasse entsprechen, und Abfragen generieren, um die Daten zu lesen und zu schreiben. Auf diese Weise kann Anwendungscode einfach Objektinstanzen an das ORM senden und abrufen, das alle SQL-Vorgänge unter der Haube übernimmt.

SQLite-NET fungiert als einfaches ORM, mit dem Sie Ihre Klassen in SQLite speichern und abrufen können. Es blendet die Komplexität des plattformübergreifenden SQLite-Zugriffs mit einer Kombination aus Compilerdirektiven und anderen Tricks aus.

Features von SQLite-NET:

  • Tabellen werden durch Hinzufügen von Attributen zu Modellklassen definiert.
  • Eine Datenbankinstanz wird durch eine Unterklasse von SQLiteConnection , der Hauptklasse in der SQLite-Net-Bibliothek dargestellt.
  • Daten können mithilfe von Objekten eingefügt, abgefragt und gelöscht werden. Es sind keine SQL-Anweisungen erforderlich (obwohl Sie SQL-Anweisungen bei Bedarf schreiben können).
  • Grundlegende Linq-Abfragen können für die von SQLite-NET zurückgegebenen Auflistungen ausgeführt werden.

Der Quellcode und die Dokumentation für SQLite-NET sind auf SQLite-Net auf Github verfügbar und wurden in beiden Fallstudien implementiert. Nachfolgend sehen Sie ein einfaches Beispiel für SQLite-NET-Code (aus der Tasky Pro-Fallstudie ).

Zunächst verwendet die TodoItem Klasse Attribute zum Definieren eines Felds als Datenbank-Primärschlüssel:

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

Dadurch kann eine TodoItem Tabelle mit der folgenden Codezeile (und keine SQL-Anweisungen) für eine SQLiteConnection Instanz erstellt werden:

CreateTable<TodoItem> ();

Daten in der Tabelle können auch mit anderen Methoden für die SQLiteConnection (wieder, ohne SQL-Anweisungen erforderlich) bearbeitet werden:

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

Vollständige Beispiele finden Sie im Quellcode der Fallstudie.

Dateizugriff

Der Dateizugriff ist sicher, dass er ein wichtiger Bestandteil jeder Anwendung ist. Häufige Beispiele für Dateien, die Teil einer Anwendung sein können, sind:

  • SQLite-Datenbankdateien.
  • Vom Benutzer generierte Daten (Text, Bilder, Sound, Video).
  • Heruntergeladene Daten zum Zwischenspeichern (Bilder, HTML- oder PDF-Dateien).

System.IO Direct Access

Sowohl Xamarin.iOS als auch Xamarin.Android ermöglichen den Dateisystemzugriff mithilfe von Klassen im System.IO Namespace.

Jede Plattform verfügt über unterschiedliche Zugriffsbeschränkungen, die berücksichtigt werden müssen:

  • iOS-Anwendungen werden in einer Sandbox mit sehr eingeschränktem Dateisystemzugriff ausgeführt. Apple bestimmt weiter, wie Sie das Dateisystem verwenden sollten, indem Sie bestimmte Speicherorte angeben, die gesichert sind (und andere nicht). Weitere Informationen finden Sie im Handbuch zum Arbeiten mit dem Dateisystem in Xamarin.iOS .
  • Android beschränkt auch den Zugriff auf bestimmte Verzeichnisse im Zusammenhang mit der Anwendung, unterstützt aber auch externe Medien (z. B. SD-Karten) und zugriff auf freigegebene Daten.
  • Windows Phone 8 (Silverlight) lässt keinen direkten Dateizugriff zu – Dateien können nur mithilfe IsolatedStoragevon Dateien bearbeitet werden.
  • Windows 8.1 WinRT- und Windows 10-UWP-Projekte bieten nur asynchrone Dateivorgänge über Windows.Storage APIs, die sich von den anderen Plattformen unterscheiden.

Beispiel für iOS und Android

Ein triviales Beispiel, das eine Textdatei schreibt und liest, wird unten gezeigt. Die Verwendung Environment.GetFolderPath ermöglicht die Ausführung desselben Codes unter iOS und Android, die jeweils basierend auf ihren Dateisystemkonventionen ein gültiges Verzeichnis zurückgeben.

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

Weitere Informationen zu iOS-spezifischen Dateisystemfunktionen finden Sie im Dokument "Xamarin.iOS Working with the File System ". Denken Sie beim Schreiben von plattformübergreifendem Dateizugriffscode daran, dass bei einigen Dateisystemen die Groß-/Kleinschreibung beachtet wird und unterschiedliche Verzeichnistrennzeichen vorhanden sind. Es empfiehlt sich, beim Erstellen von Datei- oder Verzeichnispfaden immer die gleiche Groß-/Kleinschreibung für Dateinamen und die Path.Combine() Methode zu verwenden.

Windows.Storage für Windows 8 und Windows 10

Das Erstellen mobiler Apps mit Xamarin.Forms bookChapter 20. Async und File I/O enthalten Beispiele für Windows 8.1 und Windows 10.

Mithilfe der DependencyService unterstützten APIs können Sie Dateien auf diesen Plattformen lesen und speichern:

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

Weitere Informationen finden Sie im Buch Kapitel 20 .

Isolierter Speicher unter Windows Phone 7 & 8 (Silverlight)

Isolierter Speicher ist eine gängige API zum Speichern und Laden von Dateien auf allen iOS-, Android- und älteren Windows Phone-Plattformen.

Es ist der Standardmechanismus für den Dateizugriff in Windows Phone (Silverlight), der in Xamarin.iOS und Xamarin.Android implementiert wurde, um das Schreiben von gemeinsamem Dateizugriffscode zu ermöglichen. Auf die System.IO.IsolatedStorage Klasse kann auf allen drei Plattformen in einem freigegebenen Projekt verwiesen werden.

Weitere Informationen finden Sie in der Übersicht über den isolierten Speicher für Windows Phone .

Die isolierten Speicher-APIs sind in portablen Klassenbibliotheken nicht verfügbar. Eine Alternative für PCL ist das PCLStorage NuGet

Plattformübergreifender Dateizugriff in PCLs

Es gibt auch eine PCL-kompatible NuGet – PCLStorage – die plattformübergreifende Dateizugriff für Xamarin-unterstützte Plattformen und die neuesten Windows-APIs bietet.

Netzwerkvorgänge

Die meisten mobilen Anwendungen verfügen über Netzwerkkomponenten, z. B.:

  • Herunterladen von Bildern, Video und Audio (z. B. Miniaturansichten, Fotos, Musik).
  • Herunterladen von Dokumenten (z. B. HTML, PDF).
  • Hochladen von Benutzerdaten (z. B. Fotos oder Text).
  • Zugreifen auf Webdienste oder Drittanbieter-APIs (einschließlich SOAP, XML oder JSON).

.NET Framework bietet einige verschiedene Klassen für den Zugriff auf Netzwerkressourcen: HttpClient, , WebClientund HttpWebRequest.

HttpClient

Die HttpClient Klasse im System.Net.Http Namespace ist in Xamarin.iOS, Xamarin.Android und den meisten Windows-Plattformen verfügbar. Es gibt eine Microsoft HTTP-Clientbibliothek NuGet , die verwendet werden kann, um diese API in portable Klassenbibliotheken (und Windows Phone 8 Silverlight) zu übertragen.

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

WebClient

Die WebClient Klasse stellt eine einfache API zum Abrufen von Remotedaten von Remoteservern bereit.

Universelle Windows-Plattform Vorgänge müssen asynchron sein, obwohl Xamarin.iOS und Xamarin.Android synchrone Vorgänge unterstützen (die in Hintergrundthreads ausgeführt werden können).

Der Code für einen einfachen asychronen WebClient Vorgang lautet:

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 hat DownloadFileCompleted und DownloadFileAsync zum Abrufen von Binärdaten.

HttpWebRequest

HttpWebRequest bietet mehr Anpassung als WebClient und erfordert daher mehr Code für die Verwendung.

Der Code für einen einfachen synchronen HttpWebRequest Vorgang lautet:

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

Es gibt ein Beispiel in unserer Webdienstdokumentation.

Reachability

Mobile Geräte funktionieren unter einer Vielzahl von Netzwerkbedingungen von schnellen WLAN- oder 4G-Verbindungen zu schlechten Empfangsbereichen und langsamen EDGE-Datenverbindungen. Aus diesem Grund empfiehlt es sich, zu erkennen, ob das Netzwerk verfügbar ist, und falls ja, welche Art von Netzwerk verfügbar ist, bevor Sie versuchen, eine Verbindung mit Remoteservern herzustellen.

Aktionen, die eine mobile App in diesen Situationen ausführen kann:

  • Wenn das Netzwerk nicht verfügbar ist, empfehlen Sie den Benutzer. Wenn sie sie manuell deaktiviert haben (z. B. Flugzeugmodus oder Deaktivieren von WLAN) dann können sie das Problem beheben.
  • Wenn die Verbindung 3G ist, verhalten sich Anwendungen möglicherweise anders (z. B. lässt Apple nicht zu, dass Apps mit mehr als 20 Mb über 3G heruntergeladen werden können). Anwendungen könnten diese Informationen verwenden, um den Benutzer vor übermäßigen Downloadzeiten beim Abrufen großer Dateien zu warnen.
  • Selbst wenn das Netzwerk verfügbar ist, empfiehlt es sich, die Verbindung mit dem Zielserver zu überprüfen, bevor andere Anforderungen initiiert werden. Dadurch wird verhindert, dass die Netzwerkvorgänge der App wiederholt abläuft und dem Benutzer auch eine informativere Fehlermeldung angezeigt wird.

WebServices

Lesen Sie unsere Dokumentation zum Arbeiten mit Webdiensten, die den Zugriff auf REST-, SOAP- und WCF-Endpunkte mit Xamarin.iOS abdeckt. Es ist möglich, Webdienstanforderungen handfertig zu erstellen und die Antworten zu analysieren, es gibt jedoch Bibliotheken, um dies viel einfacher zu machen, einschließlich Azure, RestSharp und ServiceStack. Auch auf grundlegende WCF-Vorgänge kann in Xamarin-Apps zugegriffen werden.

Azure

Microsoft Azure ist eine Cloudplattform, die eine Vielzahl von Diensten für mobile Apps bereitstellt, einschließlich Datenspeicher und Synchronisierung sowie Pushbenachrichtigungen.

Besuchen Sie azure.microsoft.com , um es kostenlos zu testen.

RestSharp

RestSharp ist eine .NET-Bibliothek, die in mobilen Anwendungen enthalten sein kann, um einen REST-Client bereitzustellen, der den Zugriff auf Webdienste vereinfacht. Sie hilft, indem eine einfache API zum Anfordern von Daten bereitgestellt und die REST-Antwort analysiert wird. RestSharp kann nützlich sein

Die RestSharp-Website enthält Dokumentation zur Implementierung eines REST-Clients mithilfe von RestSharp. RestSharp bietet Beispiele für Xamarin.iOS und Xamarin.Android auf github.

Es gibt auch einen Xamarin.iOS-Codeausschnitt in unserer Webdienstdokumentation.

ServiceStack

Im Gegensatz zu RestSharp ist ServiceStack sowohl eine serverseitige Lösung zum Hosten eines Webdiensts als auch eine Clientbibliothek, die in mobilen Anwendungen implementiert werden kann, um auf diese Dienste zuzugreifen.

Die ServiceStack-Website erläutert den Zweck des Projekts und Links zu Dokument- und Codebeispielen. Die Beispiele umfassen eine vollständige serverseitige Implementierung eines Webdiensts sowie verschiedene clientseitige Anwendungen, die darauf zugreifen können.

WCF

Xamarin-Tools können Ihnen dabei helfen, einige Wcf-Dienste (Windows Communication Foundation) zu nutzen. Im Allgemeinen unterstützt Xamarin dieselbe clientseitige Teilmenge von WCF, die mit der Silverlight-Laufzeit ausgeliefert wird. Dazu gehören die gängigsten Codierungs- und Protokollimplementierungen von WCF: textcodierte SOAP-Nachrichten über das HTTP-Transportprotokoll mithilfe des BasicHttpBinding.

Aufgrund der Größe und Komplexität des WCF-Frameworks gibt es möglicherweise aktuelle und zukünftige Dienstimplementierungen, die außerhalb des Bereichs liegen, der von der Client-Subsetdomäne von Xamarin unterstützt wird. Darüber hinaus erfordert wcf-Unterstützung die Verwendung von Tools, die nur in einer Windows-Umgebung verfügbar sind, um den Proxy zu generieren.

Threading

Die Reaktionsfähigkeit der Anwendung ist für mobile Anwendungen wichtig – Benutzer erwarten, dass Anwendungen schnell geladen und ausgeführt werden. Ein "fixierter" Bildschirm, der die Annahme von Benutzereingaben beendet, wird angezeigt, um anzugeben, dass die Anwendung abgestürzt ist. Daher ist es wichtig, den UI-Thread nicht mit lang andauernden Blockierungsaufrufen wie Netzwerkanforderungen oder langsamen lokalen Vorgängen (z. B. das Aufheben einer Datei) zu binden. Insbesondere sollte der Startvorgang keine lang andauernden Aufgaben enthalten – alle mobilen Plattformen beenden eine App, die zu lange zum Laden benötigt.

Dies bedeutet, dass die Benutzeroberfläche eine "Statusanzeige" oder eine anderweitig verwendbare Benutzeroberfläche implementieren sollte, die schnell angezeigt werden kann, und asynchrone Aufgaben zum Ausführen von Hintergrundvorgängen. Das Ausführen von Hintergrundaufgaben erfordert die Verwendung von Threads, was bedeutet, dass die Hintergrundaufgaben eine Möglichkeit benötigen, um mit dem Hauptthread zu kommunizieren, um den Fortschritt anzuzeigen oder wenn sie abgeschlossen wurden.

Parallele Aufgabenbibliothek

Mit der parallelen Aufgabenbibliothek erstellte Aufgaben können asynchron ausgeführt und auf ihren aufrufenden Thread zurückgegeben werden, wodurch sie sehr nützlich für das Auslösen lang ausgeführter Vorgänge sind, ohne die Benutzeroberfläche zu blockieren.

Ein einfacher paralleler Vorgang kann wie folgt aussehen:

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

Der Schlüssel wird TaskScheduler.FromCurrentSynchronizationContext() die SynchronizationContext.Current des Threads wiederverwenden, der die Methode aufruft (hier der Hauptthread, der ausgeführt MainThreadMethodwird) als Möglichkeit, Aufrufe an diesen Thread zurückzuleiten. Dies bedeutet, wenn die Methode im UI-Thread aufgerufen wird, wird der ContinueWith Vorgang wieder im UI-Thread ausgeführt.

Wenn der Code Aufgaben aus anderen Threads startet, verwenden Sie das folgende Muster, um einen Verweis auf den UI-Thread zu erstellen, und die Aufgabe kann weiterhin darauf zurückrufen:

static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();

Aufrufen des UI-Threads

Für Code, der die Parallel Task Library nicht verwendet, verfügt jede Plattform über eine eigene Syntax für Marshaling-Vorgänge zurück in den UI-Thread:

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

Sowohl die iOS- als auch die Android-Syntax erfordern eine "context"-Klasse, die verfügbar sein muss, was bedeutet, dass der Code dieses Objekt an alle Methoden übergeben muss, die einen Rückruf im UI-Thread erfordern.

Um UI-Threadaufrufe in freigegebenem Code zu tätigen, folgen Sie dem IDispatchOnUIThread-Beispiel (mit Freundlichkeit von @follesoe). Deklarieren und programmieren Sie eine IDispatchOnUIThread Schnittstelle im freigegebenen Code, und implementieren Sie dann die plattformspezifischen Klassen wie hier gezeigt:

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

Xamarin.Forms-Entwickler sollten in gemeinsamem Code (Freigegebene Projekte oder PCL) verwenden Device.BeginInvokeOnMainThread .

Plattform- und Gerätefunktionen und -Beeinträchtigung

Weitere spezifische Beispiele für den Umgang mit verschiedenen Funktionen sind in der Dokumentation zu Plattformfunktionen aufgeführt. Es befasst sich mit der Erkennung verschiedener Funktionen und der ordnungsgemäßen Herabstuft einer Anwendung, um eine gute Benutzererfahrung zu bieten, auch wenn die App nicht in vollem Umfang funktionieren kann.