microsoft / WindowsAppSDK

The Windows App SDK empowers all Windows desktop apps with modern Windows UI, APIs, and platform features, including back-compat support, shipped via NuGet.
https://docs.microsoft.com/windows/apps/windows-app-sdk/
MIT License
3.84k stars 320 forks source link

App Lifecycle #111

Closed andreww-msft closed 3 years ago

andreww-msft commented 4 years ago
Summary Status

Customer / job-to-be-done: App developers using WPF, WinForms, or Win32 C++ need to register for certain activation types like file associations and startup activation, need to selectively single-instance their apps, and need to use power states to selectively suspend their apps.

Problem: Non-packaged apps use a different set of APIs to achieve all of the above, and they also don't have access to power state notifications

How does our solution make things easier? All types of apps will benefit from the same single API regardless of app type, apps can now listen to power states, and WIn32 apps have a built-in easy way to single instance.

βœ… Problem validation
❌ Docs
πŸ”„ Dev spec
❌ Solution validation
❌ Implementation
❌ Samples

Summary

Provide a core set of functionality related to app activation and lifecycle. The initial release focuses on the following 4 main feature areas:

  1. Activation.
  2. Selective Multi-instancing.
  3. Restart and Recovery.
  4. Power/State Notifications.

Rationale

This aligns with Project Reunion's roadmap:

Scope

Capability Priority
This proposal will allow developers to use the same mechanism to register for different activation kinds regardless of app technology (classic Win32, Windows Forms, WPF, UWP or WinUI). Must
This proposal will allow developers to use the same mechanism to get rich activation event arguments regardless of app technology. Must
This proposal will allow users to activate any app by double-clicking it in file explorer, or typing in a path in a command window. Must
This proposal will allow developers to use a consistent mechanism to single-instance or multi-instance their apps, based on app-defined logic. Must
This proposal will allow developers to use a consistent mechanism to take part in app and system restart and recovery. Must
This proposal will allow developers to use a consistent mechanism to get power and system state notifications. Must
This proposal will allow apps to better tune their operations to improve power/battery usage. Must
This proposal will allow developers to take part in system resource management Could
This proposal will allow developers to take part in app suspend/resume behavior Could

Important Notes

Rich Activation Behaviors

The activation part of the AppLifecycle component is focused on the following 4 functional requirements:

CreateProcess activation

In the Win32 world, it is normal to activate an app by calling CreateProcess (or ShellExecute), specifying the filesystem path to the executable file. When the user double-clicks an executable file in File Explorer, this uses that same mechanism. When the user types in an executable file name into a command window, the same thing happens. In contrast, up until now, when a UWP or Desktop Bridge app is activated, this is done via the app's Application User Model ID (AUMID). For example, when an app calls Launcher.LaunchUriAsync, it specifies the target AUMID -- or a file or protocol, which causes the platform to look up registered AUMIDs in its State Repository database, before activating the app.

The Windows a-la-carte features introduced in 2004 include the ability to activate an app via CreateProcess on its path, as well as by the UWP AUMID lookup. The gap here is that the CreateProcess mechanism only works for Win32 apps -- as part of Reunion, we will bring this to packaged (Desktop Bridge and UWP) apps also.

With this, you will be able to CreateProcess a UWP app by specifying the path to its executable, or navigate to the filesystem location and double-click it there.

Activation kinds

UWP supports \~40 different activation kinds. Traditional Win32 apps have a much smaller set available -- the most common being file-type associations and protocol handling. In UWP both of these are formalized into specific activation contracts (ActivationKind.File and ActivationKind.Protocol) and when the app is activated in this way, the platform passes in rich FileActivatedEventArgs or ProtocolActivatedEventArgs. The same applies for command-line activation, activation at user login (startup activation), and so on.

Of the many different kinds of activation that UWP supports, 6 of the most commonly-used kinds are also supported for Desktop Bridge apps, and almost the same set is supported for Windows a-la-carte apps. We will support this core set of activation kinds for unpackaged Win32 apps also, plus command-line and restart activation. So the initial list is as follows:

How should we ask developers to specify their registrations for the various activation kinds? For the activation kinds that are supported in Win32 -- such as file and protocol -- apps commonly write registry keys. Conversely, UWP apps write morally equivalent extension entries in their regular MSIX manifest.

Apps that write regkeys on install sometimes don't clean them up on uninstall, and this is a primary source of winrot. This is one of the main benefits of MSIX. Therefore, the proposal is to encourage app developers to specify their activation registrations in an XML manifest rather than in the registry.

An additional advantage of the manifest approach is that it also provides an easy way for the app to get identity. The proposal is that we ask an app to craft some subset of an MSIX manifest -- sufficient for the activation extension registrations, plus identity. We will not require that the app is MSIX-packaged, merely that they have a simple manifest. While this will be a subset of the full MSIX manifest, it must use the same schemas. The platform can then use the same Deployment Extension Handlers (DEHs) on deployment to register these extensions.

Activation and ActivatedEventArgs

Traditional Win32 apps expect to get their arguments passed into WinMain in the form of a string array. Windows Forms apps expect to call Environment.GetCommandLineArgs to return a string array. ulti-instanced UWP and Desktop Bridge apps can call the AppInstance.GetActivatedEventArgs API to return strongly-typed objects for each activation kind. We will provide a converged GetActivatedEventArgs which will get all args, regardless of activation kind -- including both traditional command-line activation and also the richer UWP activation objects. This will be available to all apps.

Activation broker

Two of the proposed activation kinds present particular problems: ToastNotification and ShareTarget. Both of these will likely require some additional component at runtime to act as a broker between the source and target of the activation.

The a-la-carte model has paved the way for apps of all kinds to get a system-recognized identity. This then provides several benefits when using modern features -- including the activation kinds under consideration for the undocked AppLifecycle component. We would like to use the a-la-carte model for undocked activation. However, there are a few issues:

Identity

Both UWP and Desktop Bridge apps have an identity that is registered on the platform, and on which multiple APIs in the platform rely. Unpackaged Win32 apps do not have such an identity, and therefore cannot use any of the APIs that require identity.

In the Windows a-la-carte model, the app creates an XML manifest specifying its identity, and registers this with the system during install (or at runtime) via new PackageManager APIs.

There is an initiative in the Cobalt timeframe to enable the Store to serve up unpackaged Win32 apps in addition to UWP and Desktop Bridge apps. This is part of a general strategy of de-fragmenting the user experience of apps. Right now, there are several places where packaged apps are treated differently from unpackaged apps. A user experience example is the Apps & features page, where the options are differentiated. A more critical example is in the API surface where many WinRT APIs require the caller to have identity.

Crucially, some WinRT APIs behave differently depending on whether or not the caller has identity. Given that even unpackaged Win32 apps are already using WinRT APIs, we cannot apply identity unless the app specifically registers for identity. Instead, if an app wants to call an API, we should simply document whether or not that API requires identity, and guide the app developer to creating the appropriate manifest if they need it.

Single/Multi-Instancing

In the Win32 world, apps are multi-instance by default. That is, if the user launches Notepad 3 times in a row, they get 3 separate instances of the app. Conversely, in the UWP world, apps are single-instance by default: if the user launches Maps times in a row, the first request causes the Maps app to launch, and the second and subsequent requests cause the platform to make another activation call into the first (and only) instance.

Since Windows OS version 1803, a UWP app can opt in to multi-instancing via a manifest declaration. This feature also includes APIs to allow an app to declare itself to be multi-instanced, and yet to choose dynamically for each instance that is activated whether in fact it wants to redirect that activation to an existing instance instead. In this way, the app has the freedom to choose among several options:

Win32 apps have existing mechanisms (most commonly, named mutexes and named pipes) which they can use to achieve single-instancing in this context. There's also a Visual Basic Application Model which Windows Forms apps (whether written in Visual Basic or not) can use for single-instancing. The undocked AppLifecycle component will include a consistent, platform-supported API for selective multi/single-instancing. This updates the UWP mechanism and enables it for use by Desktop Bridge and unpackaged Win32 apps.

The specific multi-instance redirection APIs that we will provide for all apps are all based on the existing WinRT AppInstance class. The proposal is to expose equivalents to almost all AppInstance methods and properties from the new AppLifecycle class.

The existing APIs allow an app to intercept each activation, find any other running instances, and choose which instance to handle the new activation (either the current instance, or any other). The app doesn't control the activation event args -- these are simply directed to the chosen instance. In the Win32 world, apps have the opportunity to intercept the args, and therefore have the opportunity to modify/suppress/replace these args before forwarding them on. So, we propose to enhance the existing WinRT APIs to allow each activation to pass in additional payload to the target instance (at a lower priority, possibly deferred to a later release). This payload is in addition to the ActivatedEventArgs that the system originally passed in, and which the system will pass on to the target activated instance.

The multi-instancing part of the AppLifecycle component is focused on the following functional requirements:

App Restart and Recovery

The CoreApplication WinRT class exposes the RequestRestartAsync method, which allows a UWP app to terminate and restart itself immediately, on request, and to provide an arbitrary command-line string for the restarted instance.

A related API exists in the Win32 world: RegisterApplicationRestart and the matching UnregisterApplicationRestart API. This is intended for an app to register itself to be restarted if it was running when a system update occurs, or if it crashed or hung. This behavior is currently not available to UWP apps.

The use-cases for these 2 APIs are different, but related enough that it makes sense to offer both from the same place in the undocked AppLifecycle.

Related to RegisterApplicationRestart, there are 2 further Win32 APIs that enable an app to save state or perform other clean-up operations prior to termination. As part of this recovery, the app can choose to update the command-line to be used when the app is restarted:

These APIs will also be included in the Converged AppLifecycle component.

Improved Power Usage

For UWP apps, a major factor in improving device power usage and battery life is that the platform can suspend an app if the user is not actively engaged in it. UWP apps are familiar with the suspend/resume aspects of app lifecycle. That said, the suspend/resume behavior is not without problems. Power usage and battery life is an important factor for users, and there are 2 main aspects in this spec:

  1. System state changes: notifications that the system sends to an app when interesting battery/power changes occur (eg, switching between AC and DC, critical power level changes, critical battery level changes, etc). In addition to power state, we'll send notifications for other interesting events such as user inactive, screen off, and so on -- since these can also be used by an app to better tune its work. All of these are simply informative notifications: the app can do whatever it likes with the information.

  2. Suspend/resume or resource throttling: for UWP apps, the platform has heuristics to determine when to suspend or resume an app, including whether the main window is minimized, whether certain critical background triggers are fired, and so on. The pattern here is more than a simple notification, specifically: a. There is a notification that suspension is imminent. b. There is an opportunity for the app to defer suspension for a finite period so that it can complete some arbitrary work. After this period, the app will be suspended. c. There is a notification that the app is being resumed from suspension.

Note: it will likely be difficult for many Win32 apps to adopt UWP-style suspend/resume, but more apps might want to take part in resource throttling, and all apps could listen for interesting power/system-state notifications if they wish.

The proposed features for this part of the AppLifecycle component are as follows:

Power state changes

There are several existing APIs which apps can use to detect changes in battery/power status, in order to participate in improved battery life.

These APIs cover most if not all of the battery/power state change scenarios that apps would care about -- but there's no single API that is consumable by all app types. There's also no single API that's undocked from the OS. The proposal is to incorporate a clone of the W.S.P.PowerManager API in the undocked AppLifecycle component, and augment it with additional notifications based on the Win32 PowerSettingRegisterNotification API.

Suspend/resume and throttling

In UWP-style suspend/resume behavior, the constraints of the UWP app-container allow the system to suspend the app safely. Conversely, traditional Win32 apps can't always reliably be suspended because they might be using system-wide resources (file handles, named pipes, etc), so suspending the app would lock these resources, and the platform has no mechanism to deal with this scenario. Such apps would not opt in to UWP suspend/resume.

What does suspend/resume actually mean? When an app is suspended it remains in memory, but its threads are not scheduled to run -- this allows the system to restore it quickly when needed.

When does an app get suspended? In the UWP world, the most obvious manifestation is when the app's main window is minimized this is usually (but not always) following by the app getting suspended. The system can use its own heuristics and policies to decide when is a good time to suspend an app, including (but not limited to) when the app has no foreground, un-obscured or un-minimized windows.

What should an app do in its suspending handler? The key reason an app wants to know when it is about to be suspended is that there's no guarantee that the system will ever resume it, and might terminate the app at any time while it is suspended -- therefore the suspending event is the app's opportunity to save state such that it can pick up again seamlessly when it is next activated. If a suspended UWP app is holding a file open, the system can terminate the app if necessary to release the lock.

Even some UWP apps have found that the suspend/resume behavior can be difficult to work with. One mitigation that the platform offers is the option to take a SuspendingDeferral in the Suspending event handler. The app is given a SuspendingEventArgs which includes a method to request a deferral. On top of that, beyond a simple deferral, an app can request an ExtendedExecution -- including potentially an indefinite extension (although this is rarely granted, by policy).

Given the difficulties that UWP apps experience with suspend/resume, and the added complexities of Win32 apps which perform operations outside the control or awareness of an app-container context, it is most likely that very few complex Win32 apps would be able to use UWP-style suspend/resume, and a trivially simply Win32 app is unlikely to be a significant resource-hog. However, there are likely more apps that could take part in some form of throttling -- and especially if the throttling is under app control. Exactly how this throttling might work is TBD.

Recognition for good citizens

Apps will want to opt in to lifecycle and resource management because they want to be good citizens. To further incentivize apps, we should also surface this to the user in some way. One approach would be to add a "battery-aware" or "good resource citizen" badge in the Apps & features list in Settings, similar to the badge used in the traditional TaskManager (although this badge is conferred simply whenever the app is suspended).

We could apply the badge for any app that opts in to throttling and/or suspension. Note: simply registering for power state change notifications is not enough to qualify: the app could respond to low power/battery states by doing less work -- or it could respond by doing more work.

It has been pointed out that neither TaskManager nor the Apps & Features page in Settings are highly visible, especially to a non-technical user. Two other options present for consideration:

Final implementation TBD.

Open Questions

aeloros commented 3 years ago

This issue was closed because it is fully implemented. If there are bugs in the code, then they should be filed as separate issues. This issue was for the initial implementation which is done.

riverar commented 3 years ago

@aeloros How did activation get handled? Did that "subset of the full MSIX manifest" idea pan out?

aeloros commented 3 years ago

@riverar, Could you clarify?

riverar commented 3 years ago

@aeloros The proposal here refers to potentially creating a "subset of the full MSIX manifest" to give unpackaged apps identity. Did that happen? Or do we need to use MSIX sparse packages? Just asking because my proposal had a dependency on the creation of this new manifest.

aeloros commented 3 years ago

@aeloros The proposal here refers to potentially creating a "subset of the full MSIX manifest" to give unpackaged apps identity. Did that happen? Or do we need to use MSIX sparse packages? Just asking because my proposal had a dependency on the creation of this new manifest.

I don't think anything has been decided yet with regards to identity. It's a fairly complex problem and will take considerable thinking.

riverar commented 3 years ago

@aeloros Hm ok. I guess I misunderstood your "This issue was closed because it is fully implemented" then.

andreww-msft commented 3 years ago

Folks

Although this issue is closed, I wanted to clarify a few points. This was the very first Reunion/Windows App SDK feature document to be published - at a time when we were still very much feeling our way through a whole bunch of new processes around how we would publish docs and specs. We wanted to get the doc out very early, to gather feedback - and we have indeed received a lot of feedback - so many thanks for that!

Reading through all the comments again, it's clear there's been a little confusion: it's worth calling out that the doc was to some extent aspirational - it was a set of proposals rather than a committed detailed design. The more detailed API specs for AppLifecycle features were published much later. This initial doc therefore contains some ideas that we ended up changing as we fine-tuned the designs, and some ideas that got re-shuffled in the schedule, for example, the Restart support is now targeting 1.1. Another example: we originally proposed unpackaged support for 8 activation kinds, but it turns out that Launch and CommandLine are indistinguishable for unpackaged apps, so we combined these, and we deferred ToastNotification and ShareTarget to later releases.

Going forward, I'll leave this document as is, for historical reference. The aforementioned API specs and the docs.microsoft.com documentation should be considered more accurate references. Also, for suggestions for improving or extending the AppLifecycle support, we'll file new issues. For example, I've just created a new issue 1709 for improvements to the instancing redirection APIs. Of course, we're also considering issues that other folks file, such as 126 Loosen import redirection restrictions from Rafael.