2021年的UWP(6)——長生命週期Desktop Extension向UWP的反向通知

上一篇咱們討論了UWP和Desktop Extension間的雙向通信,適用於Desktop Extension中存在用戶交互的場景。本篇咱們討論最後一種狀況,與前者不一樣的是,Desktop Extension和UWP保持相同的生命週期,同時規避AppServiceConnection可能被Windows回收的限制,在任意時刻可以反向通知UWP的場景。
首先回顧以前總結的四個場景分類:html

  • 執行後當即退出
  • 等待request,處理完後退出
  • 一或多個request/response週期
  • 與UWP相同生命週期,且保持由Desktop Extension發起通知的能力

在長生命週期Desktop Extension向UWP的反向通知場景中,有如下特徵:git

  1. 通知發起方是Desktop Extension
  2. 經過request傳遞參數
  3. 不關心返回結果
  4. Desktop Extension和UWP相同生命週期

示意圖以下: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

相關文章
相關標籤/搜索