zoukankan      html  css  js  c++  java
  • 2021年的UWP(6)——长生命周期Desktop Extension向UWP的反向通知

    上一篇我们讨论了UWP和Desktop Extension间的双向通讯,适用于Desktop Extension中存在用户交互的场景。本篇我们讨论最后一种情况,与前者不同的是,Desktop Extension和UWP保持相同的生命周期,同时规避AppServiceConnection可能被Windows回收的限制,在任意时刻能够反向通知UWP的场景。
    首先回顾之前总结的四个场景分类:

    • 执行后立即退出
    • 等待request,处理完后退出
    • 一或多个request/response周期
    • 与UWP相同生命周期,且保持由Desktop Extension发起通知的能力

    在长生命周期Desktop Extension向UWP的反向通知场景中,有以下特征:

    1. 通知发起方是Desktop Extension
    2. 通过request传递参数
    3. 不关心返回结果
    4. Desktop Extension和UWP相同生命周期

    示意图如下:

    在我们接下来的Sample工程中,将通过Desktop Extension来监听全局键盘事件。在用户按下W, A, S, D四个键时打印在UWP的界面上。其实UWP程序在前台运行的状态下,也是可以捕获键盘事件的。但在最小化的状态下,就只能依靠Desktop Extension来实现了。
    在上一篇《2020年的UWP(5)——UWP和Desktop Extension的双向交互》中,我们提到了AppServiceConnection在UWP程序处于最小化时,会被Windows回收导致失去连接。而在长生命周期的Desktop Extension中,我们规避该限制的方式,是在每次从Desktop Extension发起通知时,均创建新的AppServiceConnection对象,这一点非常重要。
    整体的工程结构和之前的三篇保持一致,分为ReverseNotification.FrontUWP,ReverseNotification.Desktop以及打包用的ReverseNotification.Package工程。

    我们先从FrontUWP工程讲起,AppServiceHandler.cs是我创建的帮助Class,用来处理AppServiceConnectoin的Connected和RequestReceived事件。

            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连接的起点,即源头。

            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的界面上。

            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的引用。

    我们转到Desktop这一边,ReverseNotificatio.Desktop是一个WinForms的程序,通过RegisterHotKey这个Win32的API来监听热键。如何实现监听热键我不做过多介绍,具体请参考示例代码。

            [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》。

    我想强调的是,不要在长生命周期的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="ImagesSquare150x150Logo.png"
            Square44x44Logo="ImagesSquare44x44Logo.png">
            <uap:DefaultTile Wide310x150Logo="ImagesWide310x150Logo.png" />
            <uap:SplashScreen Image="ImagesSplashScreen.png" />
          </uap:VisualElements>
          <Extensions>
            <uap:Extension Category="windows.appService">
              <uap:AppService Name="ReverseNotificationAppService" />
            </uap:Extension>
            <desktop:Extension Category="windows.fullTrustProcess" Executable="ReverseNotification.DesktopReverseNotification.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

  • 相关阅读:
    c++检测本机网络
    ShellExecuteEx 阻塞和异步调用进程的两种方法
    QImage 转base64
    C 位域运算
    Linq 取差集 交集等
    Linq 筛选出一条数据
    Linq查询出结果集中重复数据
    使AspNetPager控件中文显示分页信息
    C盘瘦身,可以让你的电脑C盘恢复到刚安装时的大小
    Linq Distinct List 去重复
  • 原文地址:https://www.cnblogs.com/manupstairs/p/14582794.html
Copyright © 2011-2022 走看看