上一篇咱們討論了UWP和Desktop Extension間的雙向通信,適用於Desktop Extension中存在用戶交互的場景。本篇咱們討論最後一種狀況,與前者不一樣的是,Desktop Extension和UWP保持相同的生命週期,同時規避AppServiceConnection可能被Windows回收的限制,在任意時刻可以反向通知UWP的場景。
首先回顧以前總結的四個場景分類:html
在長生命週期Desktop Extension向UWP的反向通知場景中,有如下特徵:git
示意圖以下:github
在咱們接下來的Sample工程中,將經過Desktop Extension來監聽全局鍵盤事件。在用戶按下W, A, S, D四個鍵時打印在UWP的界面上。其實UWP程序在前臺運行的狀態下,也是能夠捕獲鍵盤事件的。但在最小化的狀態下,就只能依靠Desktop Extension來實現了。
在上一篇《2020年的UWP(5)——UWP和Desktop Extension的雙向交互》中,咱們提到了AppServiceConnection在UWP程序處於最小化時,會被Windows回收致使失去鏈接。而在長生命週期的Desktop Extension中,咱們規避該限制的方式,是在每次從Desktop Extension發起通知時,均建立新的AppConnection對象,這一點很是重要。
總體的工程結構和以前的三篇保持一致,分爲ReverseNotification.FrontUWP,ReverseNotification.Desktop以及打包用的ReverseNotification.Package工程。windows
咱們先從FrontUWP工程講起,AppServiceHandler.cs是我建立的幫助Class,用來處理AppServiceConnectoin的Connected和RequestReceived事件。app
public void OnBackgroundActivated(AppServiceTriggerDetails details) { Connected?.Invoke(this, new AppServiceConnectionConnectedEventArgs(details.AppServiceConnection)); Connection = details.AppServiceConnection; Connection.RequestReceived += Connection_RequestReceived; } private void Connection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { RequestReceived?.Invoke(this, args); }
而OnBackgroundActivated事件則是在App.xaml.cs中,經過override UWP Application對象的OnBackgroundActivated方法來觸發。這裏是AppServiceConnection鏈接的起點,即源頭。async
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) { base.OnBackgroundActivated(args); if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details) { if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName) { var deferral = args.TaskInstance.GetDeferral(); args.TaskInstance.Canceled += (sender, e) => { deferral?.Complete(); }; AppServiceHandler.Instance.OnBackgroundActivated(details); } } }
以上這些在前面幾篇中都有說起,這裏再也不贅述。在UWP工程的MainPage中,咱們記錄了UWP進程的process id,Desktop Extension段會讀取該值,用以檢測UWP process的Exit事件,在UWP被關閉時釋放資源。同時經過RequestReceived事件來將Desktop Extension反向通知的HotKey的值,經過HotKeyList綁定顯示到UWP的界面上。ide
protected async override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); Process process = Process.GetCurrentProcess(); ApplicationData.Current.LocalSettings.Values["processId"] = process.Id; if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0)) { await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync(); } AppServiceHandler.Instance.RequestReceived += Instance_RequestReceived; } private async void Instance_RequestReceived(object sender, Windows.ApplicationModel.AppService.AppServiceRequestReceivedEventArgs e) { var message = e.Request.Message; if (message.TryGetValue("HotKey", out object keyCode)) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () => { HotKeyList.Add(keyCode.ToString()); }); } }
最後不要忘記給FrontUWP工程添加對Windows Desktop Extension for the UWP的引用。this
咱們轉到Desktop這一邊,ReverseNotificatio.Desktop是一個WinForms的程序,經過RegisterHotKey這個Win32的API來監聽熱鍵。如何實現監聽熱鍵我不作過多介紹,具體請參考示例代碼。spa
[DllImport("user32.dll")] public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
同時爲了使用AppServiceConnection,添加了對Win10 API的引用,主要是WindowsRuntime和Windows.winmd這兩個文件。前者經過Nuget添加,後者請參考《遷移桌面程序到MS Store(4)——桌面程序調用Win10 API》。3d
我想強調的是,不要在長生命週期的Desktop Extension進程中,去維護一個全局的AppServiceConnection,某軟的文檔並無提到細節,但也明確指出AppServiceConnection在UWP進入suspended狀態時,可能被釋放。咱們要作的事情,是在每一次的熱鍵響應事件中,建立新的AppServiceConnection去發送消息。
private async void hotkeys_HotkeyPressed(int ID) { var key = Enum.GetName(typeof(VirtualKey), ID); var message = new ValueSet { { "HotKey", key } }; var connection = new AppServiceConnection { PackageFamilyName = Package.Current.Id.FamilyName, AppServiceName = "ReverseNotificationAppService" }; connection.ServiceClosed += Connection_ServiceClosed; var status = await connection.OpenAsync(); if (status == AppServiceConnectionStatus.Success) { var response = await connection.SendMessageAsync(message); } }
不能保存已建立的AppServiceConnection來重複使用,有時會形成不便。但這也正是我將Desktop Extension分爲4個場景的緣由,針對不一樣的用途來建立特定類型的background process。
ReverseNotification.Package做爲打包工程,咱們須要注意添加對FrontUWP和Desktop的引用。以及編輯Package.appxmanifest文件,提供對AppService和Desktop Extension的支持。
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$"> <uap:VisualElements DisplayName="ReverseNotification.Package" Description="ReverseNotification.Package" BackgroundColor="transparent" Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png"> <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" /> <uap:SplashScreen Image="Images\SplashScreen.png" /> </uap:VisualElements> <Extensions> <uap:Extension Category="windows.appService"> <uap:AppService Name="ReverseNotificationAppService" /> </uap:Extension> <desktop:Extension Category="windows.fullTrustProcess" Executable="ReverseNotification.Desktop\ReverseNotification.Desktop.exe"/> </Extensions> </Application>
至此對Desktop Extension的一系列討論告一段落。牽涉的內容較多,很難在一篇文章中解釋清楚,我將以前的連接羅列在下方,供各位參考:
《遷移桌面程序到MS Store(9)——APPX With Desktop Extension》對Desktop Extension作了基礎介紹。
《2020年的UWP(2)——In Process App Service》詳細介紹瞭如何使用AppService。
《2020年的UWP(3)——UWP和desktop extension的簡單交互》介紹了單向的一次性使用場景。
《2020年的UWP(4)——UWP和等待Request的Desktop Extension》background process會有一個較短的生命週期,等待Reqeust執行完成後退出。
《2020年的UWP(5)——UWP和Desktop Extension的雙向交互》一般用在同時展示UWP和WPF界面時使用。
Github:
https://github.com/manupstairs/UWPSamples/tree/master/UWPSamples/DataExchangeUWP/ReverseNotification