Специальный выпуск Windows 10 2015

ТОМ 30, НОМЕР 11

Интеграция приложений - Связывание и интеграция приложений в Windows 10

Арун Сингх | Windows 2015

Продукты и технологии:

Windows 10, Windows Runtime, объявление протокола, Universal Windows Platform
В статье рассматриваются:

  • взаимодействие приложений;
  • связывание и интеграция приложений в Windows 10;
  • более эффективный обмен данными между приложениями.

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

Один из способов решить эту проблему — просто включить всю функциональность в одно приложение. По сути, этот подход часто встречается в приложениях настольного класса. Однако это рискованный путь. Очень быстро вы окажетесь с «раздутым» приложением, где большинство использует только какое-то подмножество функциональности. В этом случае разработчики должны управлять как сложностью UI, так и обновлениями для всего приложения. Еще хуже, что по мере дальнейшего усложнения UI пользователи, особенно мобильных устройств, начнут больше склоняться к другим вариантам. Фактически современная тенденция направлена на разложение приложений на индивидуальные части, чтобы пользователи могли устанавливать или использовать только то, что им нужно в дороге, не волнуясь о лишних мегабайтах.

Второй способ решения обозначенной проблемы — задействовать облако как средство коммуникации между приложениями. Это прекрасно работает, пока данных не становится больше определенного объема или пока у вас нет пользователей с ограниченными соединениями. Проблема начинает проявляться с жалоб наподобие «Я обновил свой статус вот здесь, а он не показывается в другом приложении!». Вдобавок я всегда нахожу несколько странным, что разработчики приложений должны прибегать к услугам облака для взаимодействия двух приложений на одном и том устройстве. Должен быть способ получше.

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

Подготовка приложения к созданию глубинных ссылок

Начнем с примера складского приложения, которое может показывать подробные сведения о товарах. Также добавим в смесь приложение продаж, способное отображать устойчивые тенденции о том, что продается и где, а также куда и какие товары нужно подвезти. Приложение продаж имеет UI для анализа данных «вглубь», позволяющий пользователям видеть подробные сведения об индивидуальных товарах. Конечно, большая часть подробных сведений о товарах находится в складском приложении. Рис. 1 демонстрирует сценарий, о котором я говорю.

Приложение продаж имеет глубинные ссылки на складское приложение
Рис. 1. Приложение продаж имеет глубинные ссылки на складское приложение

Sales App Приложение продаж
Show Product Details Отображение подробных сведений о товарах
InventoryApp Складское приложение

 

В этом сценарии вы должны прежде всего сделать складское приложение доступным для запуска. Для этого вы добавляете объявление протокола в манифест пакета складского приложения (package.appxmanifest). Объявление протокола — это способ для складского приложения сообщить миру о том, что его можно запускать из других приложений. На рис. 2 показано, как выглядит это объявление. Заметьте, что я использую для протокола имя com.contoso.showproduct. Это хорошее соглашение по именованию для пользовательских протоколов, так как Contoso владеет доменом contoso.com. Вероятность того, что разработчик какого-то другого приложения случайно использует ту же пользовательскую схему, исключается.

Объявление протокола
Рис. 2. Объявление протокола

Вот XML, генерируемый объявлением протокола:

<uap:Extension Category="windows.protocol">
  <uap:Protocol Name="com.contoso.showproduct" />
</uap:Extension>

Затем нужно добавить некоторый код активации, чтобы складское приложение могло соответственно отреагировать, когда оно запускается по этому новому протоколу. Этот код должен находиться в классе Application (App.xaml.cs) складского приложения, потому что именно этому классу направляются все операции активации. Вы переопределяете метод OnActivated класса Application, чтобы реагировать на активацию протокола. На

Рис. 3. Обработка глубинной связи

protected override void OnActivated(IActivatedEventArgs args)
{
  Frame rootFrame = CreateRootFrame();
  if (args.Kind == ActivationKind.Protocol)
  {
    var protocolArgs = args as ProtocolActivatedEventArgs;
    rootFrame.Navigate(typeof(ProtocolActivationPage), protocolArgs.Uri);
  }
  else
  {
    rootFrame.Navigate(typeof(MainPage));
  }
  // Гарантируем, что текущее окно активно
  Window.Current.Activate();
}

Вы проверяете вид входящего IActivatedEventArgs, чтобы понять, является ли это активацией протокола. Если да, вы приводите типы входящих аргументов к ProtocolActivatedEventArgs и отправляете входящий URI на страницу ProductDetails. Эта страница настраивается на разбор URI вроде com.contoso.showproduct:Details?ProductId=3748937 и отображает подробные сведения о соответствующем товаре. К этому моменту складское приложение готово к обработке входящих глубинных ссылок (deep links).

Последний шаг в этом сценарии — разрешить в приложении продаж поддержку глубинных ссылок на складское приложение. Это самая простая часть всего процесса. Приложение продаж использует метод Launcher.LaunchUriAsync для глубинной ссылки на складское приложение. Вот как мог бы выглядеть такой код:

Uri uri = new Uri("com.contoso.showproduct:?ProductId=3748937");
await Launcher.LaunchUriAsync(uri);

Обмен данными между приложениями

Существуют сценарии, где приложениям нужно обмениваться данными, но при этом не обязательно перенаправлять пользователя в другое приложение. Скажем, мое приложение продаж может отображать объемы продаж по регионам и даже детализировать информацию до уровня конкретных магазинов. Выводя эти данные по товарам, было бы полезно знать, сколько штук товара доступно в каком-либо магазине или регионе. Лучший источник этих данных — складское приложение, но в этом случае запуск складского приложения нарушил бы пользовательскую среду (UX) исходного приложения. Для такого рода сценариев как раз и предназначено расширение AppService (bit.ly/1JfcVkx).

Идея проста: складское приложение предоставляет «сервис», который может вызываться приложением продаж. Последнее использует этот сервис для запроса у складского приложения имеющихся у него данных. Соединение, установленное между приложение продаж и складским приложением, может поддерживаться открытым до тех пор, пока приложение продаж не будет приостановлено.

Создание сервиса складского приложения

Давайте рассмотрим, как складское приложение создает и публикует сервис, который оно намерено предоставлять. Сервисы приложений в основном специализируются на фоновых задачах. Поэтому, чтобы добавить сервис приложения, вы добавляете проект Windows Runtime Component (Universal Windows) в решение Visual Studio, содержащее складское приложение. Проекты Windows Runtime Component вы найдете в окне Add New Project в Visual Studio под Visual C# | Windows | Universal. Шаблоны этого проекта для других языков находятся по аналогичным путям.

В новый проект Windows Runtime Component добавьте новый класс, InventoryServiceTask. Сервисы приложений специализируются на фоновых задачах потому, что, как было показано ранее, этот код должен выполняться в фоне, не проявляясь в UI. Чтобы сообщить ОС, что InventoryServiceTask — фоновая задача, вы должны просто реализовать интерфейс IBackgroundTask. Метод Run интерфейса IBackgroundTask будет точкой входа для сервиса складского приложения. В нем вы указываете отсрочку (deferral), давая знать ОС, что эту задачу следует сохранять, пока она нужна клиенту (приложению продаж). Кроме того, вы подключаете обработчик специфичного для сервиса приложения события RequestReceived. Этот обработчик будет вызываться всякий раз, когда клиент отправляет запрос на обработку этому сервису. На рис. 4 показано, как выглядит код, инициализирующий сервис складского приложения.

Рис. 4. Инициализация сервиса складского приложения в методе Run

namespace Contoso.Inventory.Service
{
  public sealed class InventoryServiceTask : IBackgroundTask
  {
    BackgroundTaskDeferral serviceDeferral;
    AppServiceConnection connection;
    public void Run(IBackgroundTaskInstance taskInstance)
    {
      // Указываем отсрочку, чтобы избежать
      // завершения работы сервиса
      serviceDeferral = taskInstance.GetDeferral();
      taskInstance.Canceled += OnTaskCanceled;
      var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
      connection = details.AppServiceConnection;
      // Прослушиваем входящие запросы к сервису приложения
      connection.RequestReceived += OnRequestReceived;
    }
  }
}

Теперь посмотрим на реализацию обработчика RequestReceived. И вновь вы указываете отсрочку, как только поступает запрос. Вы удаляете эту отсрочку по завершении обработки входящего запроса. Клиент и сервис приложения взаимодействуют, передавая структуру данных ValueSet. Каждая ValueSet — это словарь «ключ-значение», который может содержать простые типы вроде целых чисел, чисел с плавающей точкой, строк и байтовых массивов.

На рис. 5 показано, как сервис складского приложения обрабатывает входящие запросы. Он анализирует входящее сообщение на наличие команды, а затем отвечает, передавая правильный результат. В данном случае вы видите команду GetProductUnitCountForRegion, на которую сервис реагирует передачей количества штук товара и времени последнего обновления данных. Сервис мог бы прекрасно получать эти данные от веб-сервиса или просто извлекать их из автономного кеша. Прелесть в том, что клиенту (приложению продаж) не нужно знать, откуда берутся эти данные.

Рис. 5. Прием запросов к складскому приложению

async void OnRequestReceived(AppServiceConnection sender,
  AppServiceRequestReceivedEventArgs args)
{
  // Получаем отсрочку, чтобы можно было использовать
  // ожидаемый (awaitable) API для ответа на сообщение
  var messageDeferral = args.GetDeferral();
  try
  {
    var input = args.Request.Message;
    string command = input["Command"] as string;
    switch(command)
    {
      case "GetProductUnitCountForRegion":
        {
          var productId = (int)input["ProductId"];
          var regionId = (int)input["RegionId"];
          var inventoryData = GetInventoryData(productId, regionId);
          var result = new ValueSet();
          result.Add("UnitCount", inventoryData.UnitCount);
          result.Add("LastUpdated", inventoryData.LastUpdated.ToString());
          await args.Request.SendResponseAsync(result);
        }
        break;
      // Другие команды
      default:
        return;
    }
  }
  finally
  {
    // Завершаем отсрочку сообщения, чтобы платформа узнала,
    // что мы закончили обработку
    messageDeferral.Complete();
  }
}
// Корректно обрабатываем отмену
// фоновой задачи этого сервиса приложения private void OnTaskCanceled(IBackgroundTaskInstance sender,
  BackgroundTaskCancellationReason reason)
{
  if (serviceDeferral != null)
  {
    // Завершаем отсрочку сервиса
    serviceDeferral.Complete();
    serviceDeferral = null;
  }
}

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

Прежде чем кто-либо сможет вызывать сервис складского приложения, вы должны опубликовать его и присвоить ему какую-либо конечную точку. Сначала добавьте ссылку на новый компонент Windows Runtime в проект складского приложения. Затем добавьте объявление сервиса в проекте этого приложения, как показано на рис. 6. В поле Entry point указывается полное имя класса InventoryServiceTask, а в поле Name — имя, которое вы будете использовать для идентификации конечной точки этого сервиса приложения. Для обращения к сервису клиенты будут использовать то же имя.

Объявление сервиса приложения
Рис. 6. Объявление сервиса приложения

Вот XML, генерируемый объявлением сервиса приложения:

<uap:Extension Category="windows.appService"
  EntryPoint="Contoso.Inventory.Service.InventoryServiceTask">
  <uap:AppService Name="com.contoso.inventoryservice"/>
</uap:Extension>

Другая часть информации, необходимая клиенту для взаимодействия с сервисом складского приложения, — имя семейства пакетов складского приложения. Самый простой способ получить это значение — вызвать метод Windows.ApplicationModel.Package.Current.Id.FamilyName в складском приложении. Обычно я просто вывожу это значение в окно отладки и копирую его оттуда.

Вызов сервиса приложения

Теперь, когда сервис складского приложения готов к работе, его можно вызывать из приложения продаж. Чтобы вызвать этот сервис, клиент может задействовать AppServiceConnection API. Экземпляр класса AppServiceConnection требует имени конечной точки сервиса приложения и имени семейства пакетов, где находится сервис. Считайте эти два значения адресом сервиса приложения.

На рис. 7 приведен код, используемый приложением продаж для соединения с сервисом складского приложения. Заметьте, что свойству AppServiceConnection.AppServiceName было задано имя конечной точки, объявленное в манифесте пакета складского приложения. Кроме того, Package Family Name складского приложения было включено в свойство AppServiceConnection.PackageFamilyName. После этого вызовите метод AppServiceConnection.OpenAsync, чтобы открыть соединение. OpenAsync API возвращает код состояния по завершении этой операции, с помощью которого определяется, успешно ли установлено соединение.

Рис. 7. Вызов сервиса складского приложения

using (var connection = new AppServiceConnection())
{
  // Устанавливаем новое соединение с сервисом приложения
  connection.AppServiceName = "com.contoso.inventoryservice";
  connection.PackageFamilyName = "Contoso.Inventory_876gvmnfevegr";
  AppServiceConnectionStatus status = await connection.OpenAsync();
  // Новое соединение успешно открыто
  if (status != AppServiceConnectionStatus.Success)
  {
    return;
  }
  // Задаем входные данные и посылаем сообщение сервису
  var inputs = new ValueSet();
  inputs.Add("Command", "GetProductUnitCountForRegion");
  inputs.Add("ProductId",productId);
  inputs.Add("RegionId", regionId);
  AppServiceResponse response = await connection.SendMessageAsync(inputs);
  // Если сервис успешно отвечает, отображаем результат
  if (response.Status == AppServiceResponseStatus.Success)
  {
    var unitCount = response.Message["UnitCount"] as string;
    var lastUpdated = response.Message["LastUpdated"] as string;
    // Отображаем значения, полученные от сервиса
  }
}

После соединения клиент посылает сервису приложения набор значений в ValueSet, используя метод AppServiceConnection.SendMessageAsync. Заметьте, что свойство Command в ValueSet устанавливается в GetProductUnitCountForRegion. Это команда, распознаваемая сервисом. SendMessageAsync возвращает ответ, содержащий ValueSet, который был отправлен сервисом. Разбираем значения UnitCount и LastUpdated и выводим их. И это все, что требуется для взаимодействия с сервисом приложения. Вы помещаете AppServiceConnection в блок using. Это приводит к вызову метода Dispose в AppServiceConnection, как только блок using заканчивается. Вызов Dispose — способ для клиента сообщить, что он завершил взаимодействие с сервисом приложения и теперь соединение можно закрыть.

Постойте-ка, а использует ли Microsoft эти API?

Конечно, Microsoft использует эти API. Большинство приложений Microsoft, поставляемых вместе с Windows 10, на самом деле являются приложения Universal Windows Platform (UWP). К ним относятся такие программы, как Photos, Camera, Mail, Calendar, Groove Music и Store. Разработчики, которые писали эти приложения, использовали многие из рассмотренных здесь API для реализации интеграционных сценариев. Например, вы никогда не обращали внимания на ссылку Get music in Store в приложении Groove Music? Когда вы касаетесь или щелкаете эту ссылку, Groove Music использует Launcher.LaunchUriAsync API, чтобы перенаправить вас в приложение Store.

Другой яркий пример — приложение Settings. Когда вы заходите в Accounts | Your account и пытаетесь использовать камеру, чтобы сделать снимок для профиля, вызывается Launcher.LaunchUriForResultsAsync API, который запускает программу Camera. LaunchUriForResultsAsync — это специализированная форма LaunchUriAsync, более подробно описанная по ссылке aka.ms/launchforresults.

Большое количество программ также использует сервисы приложений для передачи информации в Cortana в реальном времени. Например, когда приложение пытается установить голосовые команды, на которые должна реагировать Cortana, на самом деле вызывается сервис приложения, предоставляемый Cortana.

Заключение

Windows 10 поставляется с мощными средствами, помогающими во взаимодействии между приложениями, выполняемыми на одном устройстве. Эти средства не накладывают никаких ограничений на то, что приложения могут передавать друг другу или какими данными они могут обмениваться. Так и задумано. Цель — позволить приложениям определять собственные контракты друг с другом и расширять функциональность других приложений. Это также дает возможность разработчикам разбивать свои приложения на меньшие части, которые легче сопровождать, обновлять и использовать. Это очень важно, потому что пользователи все чаще располагают несколькими устройствами и используют то устройство, которое, по их мнению, лучше подходит для конкретной задачи. Кроме того, все эти API универсальны, а значит, они могут работать на настольных компьютерах, лэптопах, планшетах, смартфонах, а вскоре и на Xbox, Surface Hub и HoloLens.


Арун Сингх (Arun Singh) — старший менеджер программ в группе Universal Windows Platform. Следите за его заметками в Twitter (@aruntalkstech) или читайте его блог на aruntalkstech.com.

Выражаю благодарность за рецензирование статью экспертам Гектору Барбере (Hector Barbera), Джиллу Бендеру (Jill Bender), Говарду Капуштайну (Howard Kapustein), Абдуле Хади Шейху (Abdul Hadi Sheikh), Стефану Уику (Stefan Wick) и Джону Уисуоллу (Jon Wiswall).