Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье показано, как создать расширение приложения Windows 10 и разместить его в приложении. Расширения приложений поддерживаются в приложениях UWP и упакованных настольных приложениях.
Чтобы продемонстрировать, как создать расширение приложения, в этой статье используются XML манифеста пакета и фрагменты кода из примера кода расширения Math . Этот пример является приложением UWP, но функции, показанные в примере, также применимы к упакованным классическим приложениям. Выполните следующие инструкции, чтобы приступить к работе с примером:
- Скачайте и распакуйте пример кода математического расширения.
- В Visual Studio 2019 откройте MathExtensionSample.sln. Задайте тип сборки x86 (
сборки Configuration Manager , а затем измените платформына x86 для обоих проектов). - Разверните решение: Build>, разверните решение.
Общие сведения о расширениях приложений
В Windows 10 расширения приложений предоставляют функциональные возможности, аналогичные функциям подключаемых модулей, надстроек и дополнений в других операционных системах. Расширения приложений появились в выпуске Windows 10 Anniversary (версия 1607, сборка 10.0.14393).
Расширения приложений — это приложения UWP или упакованные классические приложения с объявлением расширения, которое позволяет им совместно использовать содержимое и события развертывания с хост-приложением. Приложение расширения может предоставлять несколько расширений.
Так как расширения приложений представляют собой просто UWP приложения или упакованные настольные приложения, они также могут быть полностью функциональными приложениями, предоставлять возможности хостинга и обеспечивать расширения для других приложений, — всё это без создания отдельных пакетов приложений.
При создании хоста расширения приложения вы создаете возможность развития экосистемы вокруг вашего приложения, в которой другие разработчики могут улучшить его такими способами, которых вы не ожидали или для которых у вас не было ресурсов. Рассмотрим расширения Microsoft Office, расширения Visual Studio, расширения браузера и т. д. Они создают более широкие возможности для тех приложений, которые выходят за рамки функциональных возможностей, с которыми они поставляется. Расширения могут добавлять ценность и долговечность вашему приложению.
На высоком уровне для настройки связи расширения приложения необходимо выполнить следующие действия.
- Объявите приложение узлом для расширений.
- Объявите приложение как расширение.
- Определите, следует ли реализовать расширение как службу приложений, фоновую задачу или другой способ.
- Определите, как будут взаимодействовать хосты и их расширения.
- Используйте API Windows.ApplicationModel.AppExtensions в хост-приложении для доступа к расширениям.
Давайте посмотрим, как это делается, проанализировав пример кода математического расширения , который реализует гипотетический калькулятор, позволяющий добавлять новые функции с помощью расширений. В Microsoft Visual Studio 2019 загрузите MathExtensionSample.sln из примера кода.
пример кода математического расширения
Объявить приложение в качестве хост-среды для расширений
Приложение идентифицирует себя как хост расширения приложения, указывая элемент <AppExtensionHost>
в файле Package.appxmanifest. См. файл Package.appxmanifest в проекте MathExtensionHost, чтобы узнать, как это сделать.
Package.appxmanifest в проекте MathExtensionHost
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
IgnorableNamespaces="uap uap3 mp">
...
<Applications>
<Application Id="App" ... >
...
<Extensions>
<uap3:Extension Category="windows.appExtensionHost">
<uap3:AppExtensionHost>
<uap3:Name>com.microsoft.mathext</uap3:Name>
</uap3:AppExtensionHost>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
...
</Package>
Обратите внимание на xmlns:uap3="http://..."
и наличие uap3
в IgnorableNamespaces
. Это необходимо, так как мы используем пространство имен uap3.
<uap3:Extension Category="windows.appExtensionHost">
определяет это приложение как хост расширения.
Элемент имени в <uap3:AppExtensionHost>
— это имя контракта расширения . Если расширение указывает то же имя контракта расширения, хост сможет его найти. По общему правилу мы рекомендуем формировать название контракта расширения, используя название вашего приложения или издателя, чтобы избежать потенциальных конфликтов с другими именами контрактов расширения.
Можно определить несколько узлов и нескольких расширений в одном приложении. В этом примере мы указываем один хост. Расширение определяется в другом приложении.
Объявление приложения расширением
Приложение определяет себя как расширение приложения, объявляя <uap3:AppExtension>
элемент в файле Package.appxmanifest . Откройте файл Package.appxmanifest в проекте MathExtension , чтобы узнать, как это сделать.
Package.appxmanifest в проекте MathExtension:
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
IgnorableNamespaces="uap uap3 mp">
...
<Applications>
<Application Id="App" ... >
...
<Extensions>
...
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.mathext"
Id="power"
DisplayName="x^y"
Description="Exponent"
PublicFolder="Public">
<uap3:Properties>
<Service>com.microsoft.powservice</Service>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
...
</Package>
Опять же, обратите внимание на линию xmlns:uap3="http://..."
и наличие uap3
в IgnorableNamespaces
. Это необходимо, так как мы используем uap3
пространство имен.
<uap3:Extension Category="windows.appExtension">
определяет это приложение как расширение.
Значение <uap3:AppExtension>
атрибутов выглядит следующим образом:
<uap3:Properties>
— необязательный элемент, содержащий пользовательские метаданные, которые узлы могут читать во время выполнения. В примере кода расширение реализуется как сервис приложения, поэтому узлу необходимо получить его название, чтобы можно было к нему обратится. Имя службы приложений определяется в элементе <службы>, который мы определили (мы могли бы назвать его как угодно). Хост в примере кода ищет это свойство во время выполнения, чтобы узнать имя службы приложения.
Определите, как будет реализовано расширение.
В сеансе Build 2016 о расширениях приложений показано, как использовать общую папку, доступную как для узла, так и для расширений. В этом примере расширение реализуется файлом JavaScript, хранящимся в общедоступной папке, вызываемой узлом. Этот подход имеет преимущество упрощения, не требует компиляции и может поддерживать выполнение целевой страницы по умолчанию, которая содержит инструкции по расширению и ссылку на страницу Microsoft Store ведущего приложения. Дополнительные сведения см. в примере кода расширения для приложения сборки 2016 . В частности, см. проект InvertImageExtension и файл InvokeLoad()
в ExtensionManager.cs проекта ExtensibilitySample.
В этом примере мы будем использовать службу приложений для реализации расширения. Службы приложений имеют следующие преимущества:
- Если расширение завершится сбоем, это не вызовет сбоя основного приложения, поскольку основное приложение работает в собственном процессе.
- Вы можете использовать выбранный язык для реализации службы. Он не должен соответствовать языку, используемому для реализации ведущего приложения.
- Служба приложений имеет доступ к собственному контейнеру приложения, который может иметь различные возможности, отличные от возможностей узла.
- Между данными в службе и хост-приложением существует изоляция.
Код службы приложений хоста
Ниже приведен код узла, вызывающий службу приложений расширения:
ExtensionManager.cs проекта MathExtensionHost
public async Task<double> Invoke(ValueSet message)
{
if (Loaded)
{
try
{
// make the app service call
using (var connection = new AppServiceConnection())
{
// service name is defined in appxmanifest properties
connection.AppServiceName = _serviceName;
// package Family Name is provided by the extension
connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;
// open the app service connection
AppServiceConnectionStatus status = await connection.OpenAsync();
if (status != AppServiceConnectionStatus.Success)
{
Debug.WriteLine("Failed App Service Connection");
}
else
{
// Call the app service
AppServiceResponse response = await connection.SendMessageAsync(message);
if (response.Status == AppServiceResponseStatus.Success)
{
ValueSet answer = response.Message as ValueSet;
if (answer.ContainsKey("Result")) // When our app service returns "Result", it means it succeeded
{
return (double)answer["Result"];
}
}
}
}
}
catch (Exception)
{
Debug.WriteLine("Calling the App Service failed");
}
}
return double.NaN; // indicates an error from the app service
}
Это типичный код для вызова службы приложений. Дополнительные сведения о реализации и вызове службы приложений см. в статье "Создание и использование службы приложений".
Одним из важных моментов является то, как определяется, какую именно службу приложений нужно вызвать. Поскольку хост не имеет сведений о реализации расширения, расширение должно указать имя своего сервиса. В примере кода расширение объявляет имя службы приложений в файле в элементе <uap3:Properties>
:
"Package.appxmanifest" в проекте MathExtension
...
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension ...>
<uap3:Properties>
<Service>com.microsoft.powservice</Service>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
Вы можете определить собственный XML-код в элементе <uap3:Properties>
. В этом случае мы определяем имя службы приложений, чтобы хост смог его использовать при вызове расширения.
Когда хост загружает расширение, такой код извлекает имя службы из свойств, определённых в файле Package.appxmanifest расширения:
Update()
в файле ExtensionManager.cs, в проекте MathExtensionHost
...
var properties = await ext.GetExtensionPropertiesAsync() as PropertySet;
...
#region Update Properties
// update app service information
_serviceName = null;
if (_properties != null)
{
if (_properties.ContainsKey("Service"))
{
PropertySet serviceProperty = _properties["Service"] as PropertySet;
this._serviceName = serviceProperty["#text"].ToString();
}
}
#endregion
Имя службы приложений, хранящееся в _serviceName
, позволяет хосту использовать его для вызова этой службы.
Для вызова службы приложений также требуется имя семейства пакета, содержащего службу приложений. К счастью, API расширения приложения предоставляет эти сведения, полученные в строке: connection.PackageFamilyName = AppExtension.Package.Id.FamilyName;
Определение способа взаимодействия узла и расширения
Службы приложений используют ValueSet для обмена информацией. Как создатель хоста, вам нужно придумать протокол для взаимодействия с расширениями, который будет гибким. В примере кода это означает учитывать возможность расширений, которые могут принимать 1, 2 или более аргументов в будущем.
В этом примере протокол для аргументов представляет собой ValueSet, содержащий пары ключ-значение, где ключ имеет формат "Arg" + номер аргумента, например, Arg1
и Arg2
. Хост передает все аргументы в ValueSet, а расширение использует те, которые ему требуются. Если расширение может вычислить результат, то хост ожидает, что возвращенный из расширения ValueSet будет содержать ключ с именем Result
, в котором находится значение вычисления. Если этот ключ отсутствует, узел предполагает, что расширение не смогло завершить вычисление.
Код расширения службы приложений
В примере кода служба приложений расширения не реализуется как фоновая задача. Вместо этого используется модель единой службы приложений proc, в которой служба приложений работает в том же процессе, что и приложение-расширение, его размещающее. Этот процесс по-прежнему отличается от ведущего приложения, предоставляя преимущества разделения процессов, при этом некоторые преимущества производительности позволяют избежать межпроцессного взаимодействия между процессом расширения и фоновым процессом, реализующим службу приложений. См. раздел Преобразование службы приложений для запуска в том же процессе, что и основное приложение, чтобы увидеть разницу между службой приложений, выполняемой как фоновая задача, и той, что запускается в том же процессе.
Система переходит на OnBackgroundActivate()
при активации службы приложений. Этот код настраивает обработчики событий для обработки фактического вызова службы приложений при его возникновении (OnAppServiceRequestReceived()
), а также для работы с системными событиями, такими как получение объекта отложенного выполнения, обрабатывающего событие отмены или закрытия.
App.xaml.cs в проекте MathExtension.
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
base.OnBackgroundActivated(args);
if ( _appServiceInitialized == false ) // Only need to setup the handlers once
{
_appServiceInitialized = true;
IBackgroundTaskInstance taskInstance = args.TaskInstance;
taskInstance.Canceled += OnAppServicesCanceled;
AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
_appServiceDeferral = taskInstance.GetDeferral();
_appServiceConnection = appService.AppServiceConnection;
_appServiceConnection.RequestReceived += OnAppServiceRequestReceived;
_appServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed;
}
}
Код, выполняющий работу расширения, находится в OnAppServiceRequestReceived()
. Эта функция вызывается при вызове службы приложений для выполнения вычисления. Он извлекает необходимые значения из ValueSet. Если он может выполнить вычисление, он помещает результат под ключом с именем Resultв ValueSet, который передаётся узлу. Помните, что в соответствии с протоколом, определенным для того, как этот узел и его расширения будут взаимодействовать, наличие ключа результата
App.xaml.cs в проекте MathExtension.
private async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
// Get a deferral because we use an awaitable API below (SendResponseAsync()) to respond to the message
// and we don't want this call to get cancelled while we are waiting.
AppServiceDeferral messageDeferral = args.GetDeferral();
ValueSet message = args.Request.Message;
ValueSet returnMessage = new ValueSet();
double? arg1 = Convert.ToDouble(message["arg1"]);
double? arg2 = Convert.ToDouble(message["arg2"]);
if (arg1.HasValue && arg2.HasValue)
{
returnMessage.Add("Result", Math.Pow(arg1.Value, arg2.Value)); // For this sample, the presence of a "Result" key will mean the call succeeded
}
await args.Request.SendResponseAsync(returnMessage);
messageDeferral.Complete();
}
Управление расширениями
Теперь, когда мы узнали, как реализовать связь между узлом и его расширениями, давайте посмотрим, как узел находит расширения, установленные в системе, и реагирует на добавление и удаление пакетов, содержащих расширения.
Microsoft Store предоставляет расширения в виде пакетов. AppExtensionCatalog находит установленные пакеты, содержащие расширения, соответствующие имени контракта расширения узла, и предоставляет события, которые возникают при установке или удалении пакета расширения приложения, относящегося к узлу.
В примере ExtensionManager
кода класс (определенный в ExtensionManager.cs в проекте MathExtensionHost ) упаковывает логику загрузки расширений и отвечает на установку и удаление пакета расширения.
Конструктор ExtensionManager
использует AppExtensionCatalog
для поиска расширений приложения в системе с тем же именем контракта расширения, что и у хоста.
ExtensionManager.cs в проекте MathExtensionHost.
public ExtensionManager(string extensionContractName)
{
// catalog & contract
ExtensionContractName = extensionContractName;
_catalog = AppExtensionCatalog.Open(ExtensionContractName);
...
}
При установке пакета расширения ExtensionManager
собирает сведения о расширениях в пакете, у которых такое же имя контракта расширения, как у хоста. Установка может представлять собой обновление, в котором обновляются сведения о затронутом расширении. При удалении пакета расширения удаляются сведения о затронутых расширениях, чтобы пользователь знал, ExtensionManager
какие расширения больше не доступны.
Класс Extension
(определенный в ExtensionManager.cs в проекте MathExtensionHost ) был создан для примера кода, чтобы получить доступ к идентификатору расширения, описанию, логотипу и конкретным приложениям, таким как включение расширения пользователем.
Утверждение, что расширение загружено (см. Load()
в ExtensionManager.cs), означает, что с состоянием пакета всё в порядке и что мы получили его идентификатор, логотип, описание и общедоступную папку (которую мы не используем в этом примере, просто чтобы показать, как её можно получить). Сам пакет расширения не загружается.
Концепция выгрузки используется для отслеживания того, какие расширения больше не должны отображаться пользователю.
ExtensionManager
предоставляет экземпляры коллекции Extension
, чтобы расширения, их имена, описания и логотипы могли быть связаны с данными в пользовательском интерфейсе. Страница ExtensionsTab привязывается к этой коллекции и предоставляет пользовательский интерфейс для включения и отключения расширений, а также их удаления.
Пример пользовательского интерфейса на вкладке расширений
При удалении расширения система предложит пользователю убедиться, что он хочет удалить пакет, содержащий расширение (и, возможно, содержит другие расширения). Если пользователь согласен, пакет удаляется и ExtensionManager
удаляет расширения в удаленном пакете из списка расширений, доступных для ведущего приложения.
Отладка расширений и узлов приложений
Часто узел расширения и расширение не являются частью одного решения. В этом случае для отладки узла и расширения:
- Загрузите хост-проект в одном экземпляре Visual Studio.
- Загрузите расширение в другом экземпляре Visual Studio.
- Запустите хост-приложение в отладчике.
- Запустите приложение расширения в отладчике. (Если вы хотите развернуть расширение, а не отлаживать его, чтобы протестировать событие установки пакета узла, выполните Сборку > Разверните решение).
Теперь вы сможете устанавливать точки останова на хосте и в расширении. При запуске отладки самого приложения расширения появится пустое окно приложения. Если вы не хотите видеть пустое окно, можно изменить параметры отладки для проекта расширения, чтобы не запускать приложение, а отладить его при запуске (щелкните правой кнопкой мыши проект расширения, Свойства>Отладка> выберите Не запускать, а отладить мой код при его старте). Вам по-прежнему потребуется начать отладку (F5) проекта расширения, но он будет ждать, пока хост не активирует расширение, после чего сработают ваши точки останова в расширении.
Отладка примера кода
В примере кода хост и расширение находятся в одном решении. Выполните следующую отладку:
- Убедитесь, что MathExtensionHost является стартовым проектом (щелкните правой кнопкой мыши на проекте MathExtensionHost, выберите Установить в качестве стартового проекта).
- Установите точку останова на
Invoke
в ExtensionManager.cs в проекте MathExtensionHost. - F5 для запуска проекта MathExtensionHost.
- Поместите точку останова в
OnAppServiceRequestReceived
в App.xaml.cs в проекте MathExtension. - Начните отладку проекта
MathExtension (щелкните правой кнопкой мыши проект MathExtension,Отладка ), который развернет его и активирует событие установки пакета на узле.Запустить новый экземпляр - В приложении MathExtensionHost перейдите на страницу Вычисление и щелкните x^y, чтобы активировать расширение. Точка останова
Invoke()
срабатывает первой, и вы увидите, что выполняется вызов службы приложений расширений. Затем будет вызван методOnAppServiceRequestReceived()
в расширении, и вы увидите, что служба приложений вычисляет результат и возвращает его.
расширения для устранения неполадок, реализованные как служба приложений
Если у узла расширения возникают проблемы с подключением к службе приложения для вашего расширения, убедитесь, что атрибут <uap:AppService Name="...">
соответствует тому, что вы указали в элементе <Service>
. Если они не соответствуют, то имя службы, которое ваше расширение предоставляет хосту, не будет совпадать с именем службы приложения, которое вы реализовали, и хост не сможет активировать ваше расширение.
Package.appxmanifest в проекте MathExtension:
<Extensions>
<uap:Extension Category="windows.appService">
<uap:AppService Name="com.microsoft.sqrtservice" /> <!-- This must match the contents of <Service>...</Service> -->
</uap:Extension>
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.mathext" Id="sqrt" DisplayName="Sqrt(x)" Description="Square root" PublicFolder="Public">
<uap3:Properties>
<Service>com.microsoft.powservice</Service> <!-- this must match <uap:AppService Name=...> -->
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
Контрольный список основных сценариев для тестирования
При создании узла расширения и готовности к тестированию того, насколько хорошо он поддерживает расширения, ниже приведены некоторые основные сценарии.
- Запустите хост и разверните расширение приложения
- Получает ли хост новые расширения, которые появляются во время его работы?
- Разверните приложение расширения, а после этого разверните и запустите хост.
- Распознает ли хост уже существующие расширения?
- Запустите хост, а затем удалите приложение расширения.
- Правильно ли хост обнаруживает удаление?
- Запустите хост, а затем обновите приложение расширения до более новой версии.
- Правильно ли хост определяет изменения и выгружает старые версии расширения?
Дополнительные сценарии для тестирования:
- Запустите хост, переместите приложение расширения на удаляемый носитель, удалите носитель
- Обнаруживает ли хост изменение состояния пакета и отключает ли расширение?
- Запустите хост, а затем испортите приложение-расширение (сделав его недействительным, изменив подпись и т. д.)
- Обнаруживает ли хост измененное расширение и правильно ли обрабатывает его?
- Запустите хост, а затем разверните приложение расширения с недопустимым содержимым или свойствами.
- Обнаруживает ли сервер недействительное содержимое и обрабатывает его правильно?
Рекомендации по проектированию
- Укажите пользовательский интерфейс, показывающий пользователя, какие расширения доступны, и позволяет им включить или отключить их. Вы также можете добавить глифы для расширений, которые становятся недоступными, так как пакет переходит в автономный режим и т. д.
- Укажите пользователю, где они могут получить расширения. Возможно, страница расширения может предоставить поисковый запрос Microsoft Store, который открывает список расширений, которые можно использовать с приложением.
- Рассмотрим, как уведомить пользователя о добавлении и удалении расширений. При установке нового расширения можно создать уведомление и пригласить пользователя включить его. Расширения должны быть отключены по умолчанию, чтобы пользователи управлялись.
Как расширения приложений отличаются от необязательных пакетов
Ключевым различием между необязательными пакетами и расширениями приложений являются открытые экосистемы и закрытые экосистемы, а также зависимый пакет и независимый пакет.
Расширения приложений участвуют в открытой экосистеме. Если ваше приложение может размещать расширения приложений, любой пользователь может написать расширение для вашего узла до тех пор, пока они соответствуют вашему методу передачи или получения информации из расширения. Это отличается от необязательных пакетов, которые участвуют в закрытой экосистеме, где издатель решает, кто может сделать необязательный пакет, который можно использовать с приложением.
Расширения приложений являются независимыми пакетами и могут быть автономными приложениями. Они не могут иметь зависимость развертывания от другого приложения. Необязательные пакеты требуют основного пакета и не могут выполняться без него.
Пакет расширения для игры будет хорошим кандидатом на необязательный пакет, потому что он тесно привязан к игре, он не может работать независимо от игры, и вы можете не хотите, чтобы пакеты расширения были созданы только любым разработчиком в экосистеме.
Если в той же игре были настраиваемые надстройки пользовательского интерфейса или тематические элементы, то расширение приложения может быть хорошим выбором, так как приложение, предоставляющее расширение, может работать самостоятельно, и любая 3-ая сторона может сделать их.
Замечания
В этом разделе приведены общие сведения о расширениях приложений. Важно отметить создание хоста и его обозначение соответствующим образом в файле Package.appxmanifest, создание расширения и его соответствующую маркировку в его файле Package.appxmanifest, определение способа реализации расширения (например, как сервис приложений, фоновая задача или иные методы), определение способов взаимодействия хоста с расширениями и использование API AppExtensions для доступа к расширениям и управления ими.
Связанные темы
- Общие сведения о расширениях приложений
- Сессия Build 2016 о расширениях для приложений
- пример кода для расширения приложения сборки 2016
- Поддерживайте свое приложение с помощью фоновых задач
- Как создать и использоватьслужбу приложений.
- пространство имен AppExtensions
- Расширение приложения с помощью служб, расширений и пакетов