Развертывание приложений с помощью API жизненного цикла приложения
Модель инстантирования приложения определяет, может ли одновременно выполняться несколько экземпляров процесса приложения. API жизненного цикла приложения в пакете SDK для приложений Windows позволяет управлять количеством экземпляров приложения одновременно и перенаправлением активаций в другие экземпляры при необходимости.
В этой статье описывается, как использовать API жизненного цикла приложений для управления включением приложений в приложениях WinUI.
Необходимые компоненты
Чтобы использовать API жизненного цикла приложения в приложениях WinUI 3:
- Скачайте и установите последнюю версию пакета SDK для приложений Windows. Дополнительные сведения см. в статье "Начало работы с WinUI".
- Выполните инструкции по созданию простого проекта WinUI 3 или применению пакета SDK для приложений Windows в существующем проекте.
Одно экземплярные приложения
Приложения выполняются одним экземпляром, если одновременно может выполняться только один основной процесс. Попытка запустить второй экземпляр одноэлементного приложения обычно приводит к активации главного окна первого экземпляра. Обратите внимание, что это относится только к основному процессу. Одноэлементные приложения могут создавать несколько фоновых процессов и по-прежнему считаться одним экземпляром.
Приложения WinUI по умолчанию являются несколькими экземплярами, но по умолчанию имеют возможность стать одним экземпляром, решив при запуске создать новый экземпляр или активировать существующий экземпляр.
Приложение Фотографии (Майкрософт) является хорошим примером одного экземплярного приложения WinUI. При первом запуске фотографий будет создано новое окно. При попытке повторного запуска фотографий вместо этого будет активировано существующее окно.
Пример реализации единого внедрения в приложении WinUI 3 с помощью C#см. в статье "Создание одно экземплярного приложения WinUI".
Приложения с несколькими экземплярами
Приложения выполняются несколькими экземплярами, если основной процесс может выполняться несколько раз одновременно. При попытке запустить второй экземпляр много экземпляра приложения создается новый процесс и главное окно.
Традиционно распаковка приложений по умолчанию выполняется с несколькими экземплярами, но при необходимости может реализовать однозакторную настройку. Обычно это делается с помощью одного именованного мьютекса, чтобы указать, запущено ли приложение.
Блокнот является хорошим примером много экземплярного приложения. При каждом запуске Блокнота создается новый экземпляр Блокнота независимо от того, сколько экземпляров уже запущено.
Как пакет SDK для приложений Windows отличается от инстантирования UWP
Поведение в пакете SDK для приложений Windows основано на модели UWP, классе, но с некоторыми ключевыми различиями:
Класс AppInstance
- UWP: класс Windows.ApplicationModel.AppInstance ориентирован исключительно на сценарии перенаправления экземпляров.
- Пакет SDK для приложений Windows: класс Microsoft.Windows.AppLifeycle.AppInstance поддерживает сценарии перенаправления экземпляров и содержит дополнительные функции для поддержки новых функций в последующих выпусках.
Список экземпляров
- UWP: GetInstances возвращает только экземпляры, которые приложение явно зарегистрировано для потенциального перенаправления.
- Пакет SDK для приложений Windows: GetInstances возвращает все запущенные экземпляры приложения, использующие API AppInstance, независимо от того, зарегистрирован ли ключ. Это может включать текущий экземпляр. Если вы хотите, чтобы текущий экземпляр был включен в список, вызовите.
AppInstance.GetCurrent
Отдельные списки поддерживаются для разных версий одного приложения, а также экземпляров приложений, запускаемых различными пользователями.
Регистрация ключей
Каждый экземпляр приложения с несколькими экземплярами может зарегистрировать произвольный ключ с помощью FindOrRegisterForKey
метода. Ключи не имеют никакого смысла; приложения могут использовать ключи в любой форме или способе.
Экземпляр приложения может задать ключ в любое время, но для каждого экземпляра допускается только один ключ; При задании нового значения перезаписывается предыдущее значение.
Экземпляр приложения не может задать ключ тому же значению, что и другой экземпляр, уже зарегистрированный. Попытка зарегистрировать существующий ключ приведет к FindOrRegisterForKey
возврату экземпляра приложения, который уже зарегистрировал этот ключ.
- UWP: экземпляр должен зарегистрировать ключ для включения в список, возвращаемый из GetInstances.
- Пакет SDK для приложений Windows: регистрация ключа отделяется от списка экземпляров. Экземпляру не нужно регистрировать ключ для включения в список.
Отмена регистрации ключей
Экземпляр приложения может отменить регистрацию ключа.
- UWP: если экземпляр отменяет регистрацию ключа, он больше недоступен для перенаправления активации и не включен в список экземпляров, возвращаемых из GetInstances.
- Пакет SDK для приложений Windows: экземпляр, который отменил регистрацию ключа, по-прежнему доступен для перенаправления активации и по-прежнему включен в список экземпляров, возвращенных из GetInstances.
Целевые объекты перенаправления экземпляров
Несколько экземпляров приложения могут активировать друг друга, процесс с именем "перенаправление активации". Например, приложение может реализовать одностановку, только инициализировав себя, если другие экземпляры приложения не найдены при запуске, а вместо этого перенаправление и выход, если другой экземпляр существует. Приложения с несколькими экземплярами могут перенаправлять активации в соответствии с бизнес-логикой этого приложения. При перенаправлении активации в другой экземпляр он использует обратный вызов этого экземпляра Activated
, тот же обратный вызов, который используется во всех других сценариях активации.
- UWP: только экземпляры, зарегистрированные ключом, могут быть целевым объектом для перенаправления.
- Пакет SDK для приложений Windows: любой экземпляр может быть целевым объектом перенаправления, независимо от того, имеет ли он зарегистрированный ключ.
Поведение после перенаправления
UWP: перенаправление — это операция терминала; приложение завершается после перенаправления активации, даже если перенаправление завершилось сбоем.
Пакет SDK для приложений Windows: в пакете SDK для приложений Windows перенаправление не является операцией терминала. Это частично отражает потенциальные проблемы при произвольном прекращении приложения Win32, которое, возможно, уже выделило некоторую память, но также позволяет поддерживать более сложные сценарии перенаправления. Рассмотрим много экземплярное приложение, в котором экземпляр получает запрос на активацию при выполнении большого количества ресурсов ЦП. Это приложение может перенаправить запрос активации на другой экземпляр и продолжить обработку. Этот сценарий невозможен, если приложение было завершено после перенаправления.
Запрос на активацию можно перенаправить несколько раз. Экземпляр A может перенаправляться на экземпляр B, который, в свою очередь, может перенаправляться на экземпляр приложений C. Приложения пакета SDK для приложений Для Windows, которые используют эту функцию, должны защищаться от циклов активации. Это зависит от приложения, чтобы определить, как обрабатывать циклическое перенаправление в зависимости от того, что имеет смысл для рабочих процессов, поддерживаемых приложением.
События активации
Для обработки повторной активации приложение может зарегистрировать событие Активации.
- UWP: событие передает iActivatedEventArgs приложению.
- Пакет SDK для приложений Windows: событие передает экземпляр Microsoft.Windows.AppLifecycle.AppActivationArguments в приложение, которое содержит один из
-ActivatedEventArgs
экземпляров.
Примеры
Обработка активаций
В этом примере показано, как приложение регистрирует и обрабатывает Activated
событие. Когда оно получает Activated
событие, это приложение использует аргументы событий для определения типа действия, вызванного активацией, и отвечает соответствующим образом.
int APIENTRY wWinMain(
_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Initialize the Windows App SDK framework package for unpackaged apps.
HRESULT hr{ MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion) };
if (FAILED(hr))
{
OutputFormattedDebugString(
L"Error 0x%X in MddBootstrapInitialize(0x%08X, %s, %hu.%hu.%hu.%hu)\n",
hr, majorMinorVersion, versionTag,
minVersion.Major, minVersion.Minor, minVersion.Build, minVersion.Revision);
return hr;
}
if (DecideRedirection())
{
return 1;
}
// Connect the Activated event, to allow for this instance of the app
// getting reactivated as a result of multi-instance redirection.
AppInstance thisInstance = AppInstance::GetCurrent();
auto activationToken = thisInstance.Activated(
auto_revoke, [&thisInstance](
const auto& sender, const AppActivationArguments& args)
{ OnActivated(sender, args); }
);
// Carry on with regular Windows initialization.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_CLASSNAME, szWindowClass, MAX_LOADSTRING);
RegisterWindowClass(hInstance);
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
MddBootstrapShutdown();
return (int)msg.wParam;
}
void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
int const arraysize = 4096;
WCHAR szTmp[arraysize];
size_t cbTmp = arraysize * sizeof(WCHAR);
StringCbPrintf(szTmp, cbTmp, L"OnActivated (%d)", activationCount++);
ExtendedActivationKind kind = args.Kind();
if (kind == ExtendedActivationKind::Launch)
{
ReportLaunchArgs(szTmp, args);
}
else if (kind == ExtendedActivationKind::File)
{
ReportFileArgs(szTmp, args);
}
}
Логика перенаправления на основе типа активации
В этом примере приложение регистрирует обработчик для активированного события, а также проверяет наличие событий активации, чтобы решить, следует ли перенаправлять активацию в другой экземпляр.
Для большинства типов активаций приложение продолжает процесс регулярной инициализации. Однако если активация была вызвана открытием связанного типа файла, и если другой экземпляр этого приложения уже открыт, текущий экземпляр перенаправит активацию в существующий экземпляр и завершит работу.
Это приложение использует регистрацию ключей для определения открытых файлов в экземплярах. Когда экземпляр открывает файл, он регистрирует ключ, содержащий это имя файла. Затем другие экземпляры могут проверять зарегистрированные ключи и искать определенные имена файлов и регистрировать себя в качестве экземпляра этого файла, если другой экземпляр еще не имеет.
Обратите внимание, что, хотя регистрация ключа является частью API жизненного цикла приложения в пакете SDK для приложений Windows, содержимое ключа указывается только в самом приложении. Приложению не нужно регистрировать имя файла или другие значимые данные. Однако это приложение решило отслеживать открытые файлы с помощью ключей на основе конкретных потребностей и поддерживаемых рабочих процессов.
bool DecideRedirection()
{
// Get the current executable filesystem path, so we can
// use it later in registering for activation kinds.
GetModuleFileName(NULL, szExePath, MAX_PATH);
wcscpy_s(szExePathAndIconIndex, szExePath);
wcscat_s(szExePathAndIconIndex, L",1");
// Find out what kind of activation this is.
AppActivationArguments args = AppInstance::GetCurrent().GetActivatedEventArgs();
ExtendedActivationKind kind = args.Kind();
if (kind == ExtendedActivationKind::Launch)
{
ReportLaunchArgs(L"WinMain", args);
}
else if (kind == ExtendedActivationKind::File)
{
ReportFileArgs(L"WinMain", args);
try
{
// This is a file activation: here we'll get the file information,
// and register the file name as our instance key.
IFileActivatedEventArgs fileArgs = args.Data().as<IFileActivatedEventArgs>();
if (fileArgs != NULL)
{
IStorageItem file = fileArgs.Files().GetAt(0);
AppInstance keyInstance = AppInstance::FindOrRegisterForKey(file.Name());
OutputFormattedMessage(
L"Registered key = %ls", keyInstance.Key().c_str());
// If we successfully registered the file name, we must be the
// only instance running that was activated for this file.
if (keyInstance.IsCurrent())
{
// Report successful file name key registration.
OutputFormattedMessage(
L"IsCurrent=true; registered this instance for %ls",
file.Name().c_str());
}
else
{
keyInstance.RedirectActivationToAsync(args).get();
return true;
}
}
}
catch (...)
{
OutputErrorString(L"Error getting instance information");
}
}
return false;
}
Произвольное перенаправление
Этот пример расширяется в предыдущем примере, добавив более сложные правила перенаправления. Приложение по-прежнему выполняет проверку открытого файла из предыдущего примера. Однако если предыдущий пример всегда создаст новый экземпляр, если он не перенаправлялся на основе проверки открытого файла, в этом примере добавляется концепция "повторно используемый" экземпляра. Если найден повторно используемый экземпляр, текущий экземпляр перенаправляется на повторно используемый экземпляр и завершает работу. В противном случае он регистрируется как повторно используемый и продолжается с его нормальной инициализацией.
Опять же, обратите внимание, что концепция экземпляра, доступного для повторного использования, не существует в API жизненного цикла приложения; он создается и используется только в самом приложении.
int APIENTRY wWinMain(
_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
// Initialize COM.
winrt::init_apartment();
AppActivationArguments activationArgs =
AppInstance::GetCurrent().GetActivatedEventArgs();
// Check for any specific activation kind we care about.
ExtendedActivationKind kind = activationArgs.Kind;
if (kind == ExtendedActivationKind::File)
{
// etc... as in previous scenario.
}
else
{
// For other activation kinds, we'll trawl all instances to see if
// any are suitable for redirecting this request. First, get a list
// of all running instances of this app.
auto instances = AppInstance::GetInstances();
// In the simple case, we'll redirect to any other instance.
AppInstance instance = instances.GetAt(0);
// If the app re-registers re-usable instances, we can filter for these instead.
// In this example, the app uses the string "REUSABLE" to indicate to itself
// that it can redirect to a particular instance.
bool isFound = false;
for (AppInstance instance : instances)
{
if (instance.Key == L"REUSABLE")
{
isFound = true;
instance.RedirectActivationToAsync(activationArgs).get();
break;
}
}
if (!isFound)
{
// We'll register this as a reusable instance, and then
// go ahead and do normal initialization.
winrt::hstring szKey = L"REUSABLE";
AppInstance::FindOrRegisterForKey(szKey);
RegisterClassAndStartMessagePump(hInstance, nCmdShow);
}
}
return 1;
}
Оркестрация перенаправления
В этом примере снова добавляется более сложное поведение перенаправления. Здесь экземпляр приложения может зарегистрировать себя в качестве экземпляра, обрабатывающего все активации определенного типа. Когда экземпляр приложения получает Protocol
активацию, сначала проверяет экземпляр, который уже зарегистрирован для обработки Protocol
активаций. Если он находит его, он перенаправляет активацию в этот экземпляр. Если нет, текущий экземпляр регистрируется для Protocol
активации, а затем применяет дополнительную логику (не отображается), которая может перенаправить активацию по какой-либо другой причине.
void OnActivated(const IInspectable&, const AppActivationArguments& args)
{
const ExtendedActivationKind kind = args.Kind;
// For example, we might want to redirect protocol activations.
if (kind == ExtendedActivationKind::Protocol)
{
auto protocolArgs = args.Data().as<ProtocolActivatedEventArgs>();
Uri uri = protocolArgs.Uri();
// We'll try to find the instance that handles protocol activations.
// If there isn't one, then this instance will take over that duty.
auto instance = AppInstance::FindOrRegisterForKey(uri.AbsoluteUri());
if (!instance.IsCurrent)
{
instance.RedirectActivationToAsync(args).get();
}
else
{
DoSomethingWithProtocolArgs(uri);
}
}
else
{
// In this example, this instance of the app handles all other
// activation kinds.
DoSomethingWithNewActivationArgs(args);
}
}
В отличие от версии RedirectActivationTo
UWP, реализация пакета SDK для приложений Windows для RedirectActivationToAsync требует явного передачи аргументов событий при перенаправлении активаций. Это необходимо, так как в то время как UWP тесно управляет активацией и может гарантировать, что правильные аргументы активации передаются в правильные экземпляры, версия пакета SDK для приложений Windows поддерживает множество платформ и не может полагаться на специальные функции UWP. Одним из преимуществ этой модели является то, что приложения, использующие пакет SDK для приложений Windows, имеют возможность изменять или заменять аргументы, которые будут переданы целевому экземпляру.
Перенаправление без блокировки
Большинство приложений хотят перенаправить как можно раньше, прежде чем выполнять ненужную инициализацию. Для некоторых типов приложений логика инициализации выполняется в потоке STA, который не должен быть заблокирован. Метод AppInstance.RedirectActivationToAsync является асинхронным, и вызывающее приложение должно ожидать завершения метода, в противном случае перенаправление завершится ошибкой. Однако ожидание асинхронного вызова блокирует STA. В таких ситуациях вызовите RedirectActivationToAsync в другом потоке и задайте событие после завершения вызова. Дождитесь этого события с помощью неблокирующих API, таких как CoWaitForMultipleObjects. Ниже приведен пример C# для приложения WPF.
private static bool DecideRedirection()
{
bool isRedirect = false;
// Find out what kind of activation this is.
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
ExtendedActivationKind kind = args.Kind;
if (kind == ExtendedActivationKind.File)
{
try
{
// This is a file activation: here we'll get the file information,
// and register the file name as our instance key.
if (args.Data is IFileActivatedEventArgs fileArgs)
{
IStorageItem file = fileArgs.Files[0];
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(file.Name);
// If we successfully registered the file name, we must be the
// only instance running that was activated for this file.
if (keyInstance.IsCurrent)
{
// Hook up the Activated event, to allow for this instance of the app
// getting reactivated as a result of multi-instance redirection.
keyInstance.Activated += OnActivated;
}
else
{
isRedirect = true;
// Ensure we don't block the STA, by doing the redirect operation
// in another thread, and using an event to signal when it has completed.
redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
if (redirectEventHandle != IntPtr.Zero)
{
Task.Run(() =>
{
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(redirectEventHandle);
});
uint CWMO_DEFAULT = 0;
uint INFINITE = 0xFFFFFFFF;
_ = CoWaitForMultipleObjects(
CWMO_DEFAULT, INFINITE, 1,
new IntPtr[] { redirectEventHandle }, out uint handleIndex);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error getting instance information: {ex.Message}");
}
}
return isRedirect;
}
Отмена регистрации для перенаправления
Приложения, которые зарегистрировали ключ, могут отменить регистрацию этого ключа в любое время. В этом примере предполагается, что текущий экземпляр ранее зарегистрировал ключ, указывающий, что у него был определенный файл, то есть последующие попытки открыть этот файл будут перенаправлены на него. При закрытии файла необходимо удалить ключ, содержащий имя файла.
void CALLBACK OnFileClosed(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
AppInstance::GetCurrent().UnregisterKey();
}
Предупреждение
Хотя ключи автоматически отменяются при завершении процесса, условия гонки возможны, когда другой экземпляр мог инициировать перенаправление в завершенный экземпляр до отмены регистрации экземпляра. Чтобы устранить эту возможность, приложение может использовать UnregisterKey для ручной отмены регистрации ключа перед его завершением, что дает приложению возможность перенаправить активации в другое приложение, которое не находится в процессе выхода.
Сведения об экземпляре
Класс Microsoft.Windows.AppLifeycle.AppInstance представляет один экземпляр приложения. В текущей предварительной версии включает только методы и свойства, AppInstance
необходимые для поддержки перенаправления активации. В последующих выпусках будет расширено включение других методов и свойств, AppInstance
относящихся к экземпляру приложения.
void DumpExistingInstances()
{
for (AppInstance const& instance : AppInstance::GetInstances())
{
std::wostringstream sStream;
sStream << L"Instance: ProcessId = " << instance.ProcessId
<< L", Key = " << instance.Key().c_str() << std::endl;
::OutputDebugString(sStream.str().c_str());
}
}
Связанный контент
Windows developer