Часть 5 – Практические стратегии совместного использования кода

В этом разделе приводятся примеры способов совместного использования кода для распространенных сценариев приложений.

Уровень данных

Уровень данных состоит из подсистемы хранилища и методов для чтения и записи информации. Для производительности, гибкости и кроссплатформенной совместимости СУБД SQLite рекомендуется для кроссплатформенных приложений Xamarin. Он работает на разных платформах, включая Windows, Android, iOS и Mac.

SQLite

SQLite реализовано в виде ПО с открытым исходным кодом. Источник и документации можно найти на SQLite.org. SQLite поддержка доступна на каждой мобильной платформе:

Даже у СУБД доступной на всех платформах, нативные методы доступа к базе данных различны. iOS и Android предлагают встроенные API для доступа к SQLite, которые могут быть использованы Xamarin.iOS или Xamarin.Android. Однако использование встроенных методов SDK не позволяет совместно использовать код (кроме, возможно, SQL-запросов, предполагая, что они хранятся в виде строк). Для получения дополнительной информации о нативном функционале использования SQLite следует искать в CoreData для iOS или в классе SQLiteOpenHelper для Android; Поскольку эти функции не являются кроссплатформенными, они выходят за рамки настоящего документа.

ADO.NET

Xamarin.iOS и Xamarin.Android поддерживают System.Data и Mono.Data.Sqlite (см. Xamarin.iOS документацию для получения дополнительной информации). Использование этих пространств имен позволяет писать код ADO.NET, который работает на обеих платформах. Для использования нужно добавить System.Data.dll и Mono.Data.Sqlite.dll в ссылки проекта и в код с помощью оператора using:

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

Тогда следующий пример кода будет работать:

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

Реальные реализации ADO.NET, очевидно, будут разделены между различными методами и классами (этот пример для демонстрационных целей).

SQLite-NET – Кроссплатформенный ORM

ORM (или Object-Relational Mapper) позволяет упростить хранение данных, смоделированных в классах. Вместо того, что бы писать вручную SQL-запросы, создания таблиц или выборки, вставки и удаления данных, которые вручную извлечены из полей и свойств класса, ORM добавляет слой кода, который делает это за вас. Использую рефлексию при исследовании структуры классов, ORM может автоматически создавать таблицы и столбцы, которые соответствуют классу и создавать запросы для чтения и записи данных. Это позволяет коду приложения просто отправлять и получать экземпляры объектов к ORM, который заботится о всех SQL-операциях.

SQLite-NET действует как простой ORM, который позволит вам сохранить и восстановить свои классы в SQLite. Он скрывает сложность кроссплатформенного доступа к SQLite при совмещении директив компилятора и других хитростей.

Особенности SQLite-NET:

  • Таблицы определяются путем добавления атрибутов моделей классов.
  • Экземпляр базы данных представлен подклассом SQLiteConnection, основного класса в SQLite-Net библиотеке.
  • Данные могут быть вставлены, запрошены и удалены, используя объекты. Никаких SQL-запросов не требуется (хотя вы можете написать SQL-запросы в случае необходимости).
  • Основные Linq-запросы могут быть выполнены на коллекциях возвращенных SQLite-NET.

Исходный код и документация SQLite-NET доступна на SQLite-Net на GitHub и был реализован в обоих тематических исследованиях. Простой пример кода SQLite-NET (на примере Tasky Pro) приведены ниже.

Во-первых класс TodoItem использует атрибуты для определения полей для первичного ключа базы данных:

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 с помощью следующей строки кода (и без каких-либо SQL-запросов) на экземпляре SQLiteConnection:

CreateTable<TodoItem> ();

Данными в таблице также можно манипулировать использую другие методы на SQLiteConnection (опять же, не требуя 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

Доступ к файлам

Доступ к файлам, несомненно, будет ключевой частью любого приложения. Типичные примеры файлов, которые могут быть частью приложения:

  • Файлы базы данных SQLite.
  • Данные созданные пользователем (техт, изображения, аудио- и видео-файлы).
  • Загруженные данные для кэширования (изображения, html или PDF файлы).

System.IO Прямой доступ

Xamarin.iOS и Xamarin.Android разрешают доступ к файловой системе с помощью классов в пространстве имен System.IO.

Каждая платформа имеет ограничения доступа, которые должны быть приняты во внимание:

  • iOS приложения выполняются в изолированной программной среде с очень ограниченным доступом к файловой системе. Apple также диктует, как вы должны использовать файловую систему, указав определенные места, которые являются резервными (и другие, которые таковыми не являются). Обратитесь к руководству Работа с файловой системой в Xamarin.iOS  для получения более подробной информации.
  • Android также ограничивает доступ к некоторым каталогам, относящиеся к приложению, но он поддерживает внешние носители (например, SD карты) и доступ к общим данным.
  • Windows Phone 8 (Silverlight) не допускает прямого доступа к файлам – файлами можно манипулировать только с помощью IsolatedStorage.
  • Проекты Windows 8.1 WinRT и Windows 10 UWP предлагают только асинхронные файловые операции через Windows.Storage, которые отличаются от других платформ.

Примеры для iOS и Android

Ниже показан тривиальный пример, который пишет и читает текстовый файл. Использование Environment.GetFolderPath позволяет один и тот же код использовать в iOS и Android, которые возвращают действительный каталог на основе соглашений их файловой системы.

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

Обратитесь к документации Xamarin.iOS Работа с файловой системой для получения дополнительной информации о функциях iOS-специфичной файловой системы. При написании кроссплатформенного кода доступа к файлам, помните, что некоторые файловые системы учетываюь регистр символов и имеют разные разделители каталогов. Рекомендуется всегда использовать один регистр для имен файлов и метод Path.Combine() при построении пути к файлу или каталогу.

Windows.Storage в Windows 8 и Windows 10

В книге по создании мобильных приложения с Xamarin.Forms Глава 20. Асинхронный и файловый ввод/вывод  (Async and File I/O) включает в себя примеры для ОС Windows 8.1 и Windows 10.

Используя DependencyService можно читать и писать файлы на этих платформах, используя поддерживаемые API-интерфейсы:

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

Изолированное хранилище в Windows Phone 7 и 8 (Silverlight)

Изолированное хранилище (Isolated Storage) — общий API для сохранения и загрузки файлов во всех iOS, Android и старых платформах Windows Phone.

Это механизм по умолчанию для доступа к файлам в Windows Phone (Silverlight), который был реализован в Xamarin.iOS и Xamarin.Android, для обеспечения общего кода доступа к файлам для записи. На класс System.IO.IsolatedStorage можно ссылаться во всех трех платформах, в Shared Project.

См. Обзор Изолированного хранилища (Isolated Storage) для Windows Phone для получения дополнительной информации.

API интерфейсы Isolated Storage недоступны в Portable Class Libraries. Единственной альтернативой для PCL является PCLStorage NuGet

Кроссплатформенный доступ к файлам в PCL

Существует также PCL-совместимый кроссплатформенный Nuget компонент доступа к файлам — PCLStorage для платформ, поддерживаемых Xamarin и новейшими Windows API.

Примеры кода доступны на странице соответствующих компонентов.

Сетевые операции

Большинству мобильных приложений необходимо иметь сетевой компонент, например:

  • Загрузка изображений, видео и аудио (например, эскизы, фотографии, музыка).
  • Загрузка  документов (например, HTML, PDF).
  • Загрузка данных пользователя (например, фотографии или текст).
  • Доступ к веб-службам или API-интерфейсам сторонних разработчиков  (включая SOAP, XML или JSON).

Платформа .NET Framework предоставляет несколько различных классов для доступа к сетевым ресурсам: HttpClient, WebClient, и HttpWebRequest.

HttpClient

Класс HttpClient в пространстве имен System.Net.Http доступен в Xamarin.iOS, Xamarin.Android и большинстве платформ Windows. Существует Microsoft HTTP Client Library Nuget, который может быть использован, чтобы принести этот API в PCL и Windows Phone 8 Silverlight.

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

WebClient

Класс WebClient предоставляет простой API для получения данных с удаленных серверов.

Операции Universal Windows Platofrm (UWP) должны быть обязательно асинхронными, а Xamarin.iOS и Xamarin.Android поддерживают только синхронные операции (которые могут быть сделаны в фоновых потоках).

Код для простой асинхронной операции WebClient:

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 также имеет DownloadFileCompleted и DownloadFileAsync для получения двоичных данных.

HttpWebRequest

HttpWebRequest предлагает больше настроек, чем WebClient и в результате требует большего количества кода для использования.

Код для простой синхронной операции HttpWebRequest:

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

Доступность

Мобильные устройства работают в различных условиях сети от быстрого Wi-Fi или 4G подключения до районов с плохим приемом и медленных подключений  EDGE. Поэтому хорошая практика, для начала определить, доступна ли сеть и если да, какой тип сети доступен, прежде чем подключаться к удаленным серверам.

Действия, которые мобильные приложения должны выполнять в таких ситуациях включают в себя:

  • Если сеть недоступна, сообщите об этом пользователю. Если он вручную отключил её (например, включен Режим полета или выключен Wi-Fi), то пользователи смогут решить эту проблему.
  • Если имеется соединение 3G, то приложения могут вести себя по-разному (например, Apple не поддерживает в приложениях загрузки через 3G больше 20Mb данных). Приложения могут использовать эту информацию, чтобы предупредить пользователя о чрезмерном времени загрузки при получении больших файлов.
  • Даже если сеть доступна, то хорошей практикой будет  проверка соединения с целевым сервером перед запуском других запросов. Это предотвратит неоднократные сетевые операции приложения с истечением времени ожидания  и также позволят отображать для пользователя более информативное сообщение об ошибке.

Здесь пример кода Xamarin.iOS (который основан на примере доступности от Apple), чтобы помочь обнаружить доступность сети.

Веб-сервисы

Смотрите нашу документацию по Работе с веб-службами, которая охватывает доступ к REST, SOAP и WCF конечных точек с помощью Xamarin.iOS. Можно вручную создавать кустарные запросы к веб-сервисам и анализировать ответы, однако есть доступные библиотеки, чтобы сделать это намного проще, в том числе Azure, RestSharp и ServiceStack. Даже базовые операции WCF доступны в Xamarin приложениях.

Azure

Microsoft Azure — облачная платформа, которая предоставляет широкий спектр услуг для мобильных приложений, включая хранение данных, синхронизации и push-уведомлений.

Посетите azure.microsoft.com, чтобы попробовать его бесплатно и включите компонент Azure, чтобы начать работу.

RestSharp

RestSharp — библиотека .NET, которые могут быть включены в мобильные приложения клиента REST, который упрощает доступ к веб-службам. Она помогает, предоставляя простой API для запроса данных и проанализировать ответ REST. RestSharp может быть полезным.

Сайт RestSharp содержит документацию о том, как реализовать клиента REST с помощью RestSharp. RestSharp предоставляет примеры для Xamarin.iOS и Xamarin.Android на GitHub.

 Существует также фрагмент кода Xamarin.iOS в нашей документации веб-служб.

ServiceStack

В отличие от RestSharp ServiceStack является серверным решением для организации веб-службы, а также клиентской библиотекой, которая может быть реализована в мобильных приложениях для доступа к этому сервису.

Веб-сайт ServiceStack объясняет цель проекта, имеет ссылки на документацию и примеры кода. Примеры включают полную реализацию на стороне сервера веб-службы, а также в различных клиентских приложениях, которые могут получить доступ к ней.

Существует пример Xamarin.iOS на сайте ServiceStack, и фрагмент кода в нашей документации веб-служб.

WCF

Xamarin инструменты могут помочь вам использовать некоторые службы Windows Communication Foundation (WCF). В общем Xamarin поддерживает то же подмножество на стороне клиента WCF, который поставляется вместе со средой выполнения Silverlight. Это включает в себя наиболее распространенные реализации кодирования и протокола WCF: текст в кодировке сообщения SOAP через транспортный протокол HTTP  с использованием BasicHttpBinding.

Из-за размера и сложности платформы WCF может быть реализации текущих и будущих сервисов, которые выходят за пределы сферы действия поддерживаемого домене подмножества клиента в Xamarin. Кроме того, поддержка WCF требует использования инструментов, доступных только в среде Windows, чтобы сгенерировать прокси-сервер.

Многопоточность

Скорость реакции приложений имеет важное значение для мобильных приложений – пользователи ожидают, что приложения будут быстро и загружаться, и выполняться. А если появляется «замороженный» экран, который прекращает принимать входные данные пользователя, то это указывает на то, что в приложении произошёл сбой, поэтому очень важно не объединять поток пользовательского интерфейса с длительной блокировки вызовов, например сетевыми запросами или медленными локальными операциями (например, разархивирования файла). Также процесс запуска не должен содержать долго выполняющихся задач – все мобильные платформы будут убивать приложение, которое занимает слишком много времени для загрузки.

Это означает, что ваш пользовательский интерфейс должен реализовать ‘индикатор прогресса’ или иной ‘годный к употреблению’ пользовательский интерфейс, который быстр для отображения и предполагает использование асинхронных задач для выполнения фоновых операций. Выполнение фоновых задач требует использования потоков, то есть фоновым задачам нужен способ передачи информации обратно в основной поток, для того, чтобы указать прогресс выполнения или сообщить об окончании выполнения.

Библиотека параллельных задач (Parallel Task Library)

Задачи, созданные с Parallel Task Library могут  выполняться асинхронно и передавать упраление вызывающему их потоку, что делает их очень полезными для запуска долго выполняющихся операций без блокирования пользовательского интерфейса.

Простая задача параллельной операции может выглядеть следующим образом:

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

Ключ TaskScheduler.FromCurrentSynchronizationContext который будет повторно использовать SynchronizationContext.Current трэды, вызывающий метод (это основной поток, тот который работает — MainThreadMethod) как способ управления обратными вызовами на этот поток. То есть, если метод вызывается в потоке пользовательского интерфейса, он будет обрабатывать операцию ContinueWith с возвращением управления обратно в поток пользовательского интерфейса.

Если в коде задачи запускаются из других потоков, используйте следующий шаблон для создания ссылки на поток пользовательского интерфейса и задача по-прежнему может обращаться к нему:

static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();

Вызов потока пользовательского интерфейса

Для кода, который не использует Parallel Task Library, каждая платформа имеет свой собственный синтаксис для возврата управления в поток пользовательского интерфейса:

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

И iOS, и Android синтаксис требуют доступный класс «контекста», чтобы код мог передать этот объект в любые методы, которые требуют обратного вызова в поток пользовательского интерфейса.

Для того, чтобы реализовать вызовы потока пользовательского интерфейса в общем коде, следуйте примеру IDispatchOnUIThread (любезно предоставлено @follesoe). Создайте интерфейс IDispatchOnUIThread в совместно используемом коде, а затем реализуйте классы специфичные для платформы, как показано здесь:

// 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 разработчики должны использовать Device.BeginInvokeOnMainThread в общем коде (Shared Projects или PCL).


Оригинал статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *