上一篇我们讨论了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发起通知时,均创建新的AppConnection对象,这一点非常重要。
整体的工程结构和之前的三篇保持一致,分为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方法来触发。这里是AppConnectionService连接的起点,即是源头。

        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="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

2021年的UWP(6)——长生命周期Desktop Extension向UWP的反向通知的更多相关文章

  1. [翻译] 编写高性能 .NET 代码--第二章 GC -- 将长生命周期对象和大对象池化

    将长生命周期对象和大对象池化 请记住最开始说的原则:对象要么立即回收要么一直存在.它们要么在0代被回收,要么在2代里一直存在.有些对象本质是静态的,生命周期从它们被创建开始,到程序停止才会结束.其它对 ...

  2. 2020年的UWP(4)——UWP和等待Request的Desktop Extension

    上一篇我们讨论了UWP和Desktop Extension交互中,Desktop Extension执行后立即退出的场景.下图是提到的四种场景分类: 执行后立即退出 等待request,处理完后退出 ...

  3. android开发3:四大基本组件的介绍与生命周期

    android开发3:四大基本组件的介绍与生命周期 Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver ...

  4. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少分配率, 最重要的规则,缩短对象的生命周期,减少对象层次的深度,减少对象之间的引用,避免钉住对象(Pinning)

    减少分配率 这个几乎不用解释,减少了内存的使用量,自然就减少GC回收时的压力,同时降低了内存碎片与CPU的使用量.你可以用一些方法来达到这一目的,但它可能会与其它设计相冲突. 你需要在设计对象时仔细检 ...

  5. ASP.NET Core :容器注入(二):生命周期作用域与对象释放

    //瞬时生命周期 ServiceCollection services = new ServiceCollection(); services.AddTransient<TestServiceI ...

  6. 对于Android Service 生命周期进行全解析

    应用程序组件有一个生命周期——一开始Android实例化他们响应意图,直到结束实例被销毁.在这期间,他们有时候处于激活状态,有时候处于非激 活状态:对于活动,对用户有时候可见,有时候不可见.组件生命周 ...

  7. Android组件生命周期(二)

    引言 应用程序组件有一个生命周期——一开始Android实例化他们响应意图,直到结束实例被销毁.在这期间,他们有时候处于激活状态,有时候处于非激活状态:对于活动,对用户有时候可见,有时候不可见.组件生 ...

  8. Android组件生命周期(一)

    引言 应用程序组件有一个生命周期——一开始Android实例化他们响应意图,直到结束实例被销毁.在这期间,他们有时候处于激活状态,有时候处于非激活状态:对于活动,对用户有时候可见,有时候不可见.组件生 ...

  9. Android开发之旅(二)服务生命周期和广播接收者生命周期

    引言 应用程序组件有一个生命周期——一开始Android实例化他们响应意图,直到结束实例被销毁.在这期间,他们有时候处于激活状态,有时候处于非激活状态:对于活动,对用户有时候可见,有时候不可见.组件生 ...

随机推荐

  1. 如何在ASP.NET Core 中使用IHttpClientFactory

    利用IHttpClientFactory可以无缝创建HttpClient实例,避免手动管理它们的生命周期. 当使用ASP.Net Core开发应用程序时,可能经常需要通过HttpClient调用Web ...

  2. computer network layers architecture (TCP/IP)

    computer network layers architecture (TCP/IP) 计算机网络分层架构 TCP/IP 协议簇 OSI 模型(7 层) TCP/IP (4 层) Applicat ...

  3. linux cd root directory

    linux cd root directory https://www.cyberciti.biz/faq/how-to-change-directory-in-linux-terminal/ htt ...

  4. HTML页面顶部出现空白部分(#65279字符?)解决办法

    1.在火狐下面用Firebug,选择body,点编辑html的时候,看到是多出了一个这个代表的意思,还真不知道,搜索后了解到是一种中文的编码规则,   UTF-8不需要BOM来表明字节顺序.   制作 ...

  5. uni-app小白入门自学笔记(二)

    码文不易啊,转载请带上本文链接呀,感谢感谢 https://www.cnblogs.com/echoyya/p/14429616.html 目录 码文不易啊,转载请带上本文链接呀,感谢感谢 https ...

  6. Linux进程管理工具Supervisor的安装配置

    目录 Linux进程管理工具Supervisor的安装配置 简介 安装Python包管理工具 安装Supervisor 配置 配置文件参数说明 配置进程管理 启动supervisor 控制进程 交互终 ...

  7. Java基础语法:注释

    书写注释是一个非常好的习惯. 注释并不会被执行,是给我们写代码的人看的. Java中的注释有三种: 单行注释(Line comment) 多行注释(Block comment) 文档注释(JavaDo ...

  8. Synchronized 轻量级锁会自旋?好像并不是这样的。

    本来是在写面霸系列的,写着写着就写到了这一题: Synchronized 原理知道不? 而关于 Synchronized 我去年还专门翻阅 JVM HotSpot 1.8 的源码来研究了一波,那时候我 ...

  9. pytorch(04)简单的线性回归

    线性回归 线性回归是分析一个变量与另外一个变量之间关系的方法 因变量:y 自变量:x 关系:线性 y = wx+b 分析:求解w,b 求解步骤: 确定模型,Model:y = wx+b 选择损失函数, ...

  10. 如何在 ASP.NET Core 中写出更干净的 Controller

    你可以遵循一些最佳实践来写出更干净的 Controller,一般我们称这种方法写出来的 Controller 为瘦Controller,瘦 Controller 的好处在于拥有更少的代码,更加单一的职 ...