在 Windows 10 中微软为 UWP 引入了 App Service (即应用服务)这一新特性用以提供应用间交互功能。提供 App Service 的应用能够接收来自其它应用传入的参数进行处理后返回数据。

创建应用服务

要使应用支持提供 App Service 非常简单。只需正确配置应用的清单文件后添加服务相关的代码即可。

配置应用清单文件

  • 打开项目中的 Package.appxmanifest 文件。
  • 切换到 Declarations 选项卡。
  • 在左侧 Available Declarations 中选择 App Service,点击 Add 将其添加到 Supported Declarations 列表。
  • 然后在右侧设置该 App Service 相关属性。

一般情况下,需要设置的是 Name 和 Entry point 两项。Name 是应用所提供的服务名称,Entry point 即入口点,是实现了应用服务功能的后台任务类。

也可以手动编辑 Package.appxmanifest 文件,添加 App Service 相关配置:

  • 打开项目中的 Package.appxmanifest 文件,按下 F7 切换到代码编辑模式。
  • 在清单文件中找到 <Applications></Applications>节点,一般情况下,该节点只包含一个 <Application/> 节点。(也可以包含多个,也就是在一个应用包中封入多个应用,但这需要额外权限,一般开发者无权这么做。)
  • 在 <Application></Application>节点中继续找到 <Extensions></Extensions> 节点(如果没有则手动添加)。
  • 在 <Extensions></Extensions> 中添加以下代码:
<uap:Extension Category="windows.appService" EntryPoint="入口点">
<uap:AppService Name="服务名称" />
</uap:Extension>

添加应用服务代码

  • 在当前 UWP 解决方案中添加一个 Windows 运行时组件(Windows Universal) 项目。注意,项目类型务必是 Windows 运行时组件 (也就是 Windows Runtime Component)而不是类库。
  • 在 UWP 解决方案的主项目中添加对新建的 Windows 运行时组件项目的引用。
  • 在新建项目中的 Class1.cs 文件中添加以下代码:
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
  • 在 Class1.cs 文件中编写实现 IBackgroundTask 接口的后台任务类,假设我们要提供一个生成 GUID 的 App Service:
public sealed class GUIDProviderTask : IBackgroundTask
{
private BackgroundTaskDeferral backgroundTaskDeferral;
private AppServiceConnection appServiceconnection; public void Run(IBackgroundTaskInstance taskInstance)
{
this.backgroundTaskDeferral = taskInstance.GetDeferral(); taskInstance.Canceled += OnTaskCanceled; var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
appServiceconnection = details.AppServiceConnection;
appServiceconnection.RequestReceived += OnRequestReceived;
} private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
//获取 deferral
var requestDeferral = args.GetDeferral(); try
{
//读取服务接收到的消息
var message = args.Request.Message; var result = new ValueSet(); //检测消息中是否包含约定的内容
if (message.ContainsKey("requestguid"))
{
//在响应结果中填入生成的 GUID
result.Add("guid", GenerateGUID());
} //服务发回响应结果
await args.Request.SendResponseAsync(result);
}
catch (Exception ex)
{
var result = new ValueSet(); //将异常写入响应结果
result.Add("exception", ex); //服务发回响应
await args.Request.SendResponseAsync(result);
}
finally
{
//通过 deferral 通知系统服务已经完成响应
requestDeferral.Complete();
}
} private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
if (this.backgroundTaskDeferral != null)
{
// 通知服务 deferral 完成。
this.backgroundTaskDeferral.Complete();
}
} private string GenerateGUID()
{
return Guid.NewGuid().ToString();
}
}

以上代码就是实现一个返回 GUID 的 App Service 的完整代码。Run() 在后台任务创建时被调用。因为后台任务会在 Run 完成后被结束,所以在代码中需要获取 deferral 以保证后台任务保持活跃以便响应发送到服务的请求。

OnTaskCanceled() 在后台任务取消时被调用。导致后台任务取消的因素很多样,有可能是客户端应用释放了 AppServiceConnection;客户端应用挂起了;操作系统关闭或进入睡眠,或者操作系统没有足够的资源运行后台任务。

在 Run 方法中,通过 taskInstance 获得 AppServiceTriggerDetailsAppServiceTriggerDetails 表示 App Service 触发器的详情。该类包含三个属性可用于获取服务名称、调用者包名和服务连接,即 AppServiceConnecton。代码中通过 AppServiceTriggerDetails 获得当前对服务调用建立的 AppServiceConnecton

AppServiceConnection

代码中实现 App Service 的核心就是 AppServiceConnection。 AppServiceConnection 类表示到一个 App Service 端点的连接。

该类包含两种事件和四种方法:

事件

方法

  • Close 关闭到服务端点的连接。该方法在 C++/Javascript 中使用。

  • Dispose 关闭到到服务端点的连接。该方法在 C#/VB 中使用。

  • OpenAsync 打开到服务端点的连接。

  • SendMessageAsync 向服务另一端点发送消息。

属性

AppServiceConnection 类还拥有以下两个属性:

获得 AppServiceConnection 之后就可以侦听 RequestReceived 来对服务请求进行响应处理了。

在上述示例代码中,服务接收请求,检查请求消息中是否包含预先约定的键 "requestguid",如果包含该键,则生成一个 GUID,并作为响应结果发回调用者。

由于将响应结果发回调用者的方法 SendResponseAsync 是一个异步方法,所以事件处理方法 OnRequestedReceived 带有 async 关键字。

而为了能够在事件处理方法 OnRequestedReceived 中正常使用异步方法,需要首先获取一个 deferral。deferral 确保了对 OnRequestedReceived 的调用不会在请求消息处理完成之前结束。需要注意的是,SendResponseAsync 发回响应与对 OnRequestedReceived 的调用完成无关,SendResponseAsync 不负责通知 OnRequestedReceived 的完成,而是 deferral 负责通知客户端的 SendMessageAsync 方法已经处理完事件响应 OnRequestedReceived 。( SendMessageAsync 是客户端用于发送请求的方法,下文详述。)

App Service 使用 ValueSet 来交换信息。通过 ValueSet 可传递的数据大小受系统资源限制决定。在传递的 ValueSet 中没有预定义的键值,开发者需自行约定服务所需的键值协议。客户端调用者必须遵守这一协议构造 ValueSet

服务端端点关闭时,服务会返回一个 AppServiceClosedStatus 枚举指示对服务的调用是否成功。AppServiceClosedStatus 可能返回四种结果:完成、取消、系统资源不足和未知情况。服务端可以在响应结果的 ValueSet 中附加详细的状态信息,例如异常信息一并发回给客户端调用者。

最后,对 SendResponseAsync 方法的调用将响应结果发回客户端调用者。

隐藏提供服务的应用

假设你开发了一个专门提供某种服务的应用,只希望它的后台服务提供功能,不希望主程序出现在开始菜单的所有程序当中,只需简单修改应用的清单文件即可:

  • 在解决方案浏览器中右键单击 Package.appxmanifest 选择查看代码 ()
  • 在打开的清单文件中找到 <Applications> 节点中的 <Application> 节点,再找到 <uap:VisualElements> 节点。
  • 在 <uap:VisualElements> 节点中加入属性:
AppListEntry = "none"  

使用应用服务

要让客户端应用能够使用由服务提供方应用提供的 App Service,需要首先知道服务提供方应用的 PFN 包名(Package Family Name)。

获取服务提供应用包名

MSDN 文档上列举了两种获取 PFN 包名的方法:

  • 从服务提供应用项目内(例如,从 App.xaml.cs 中的 public App())调用Windows.ApplicationModel.Package.Current.Id.FamilyName,可以通过输出到 Visual Studio 的输出窗口进行观察记录,或展示在应用界面上等。

  • 部署解决方案(生成>部署解决方案)并记下输出窗口中的完整程序包名称(查看>输出)。 注意部署时输出的是完整包名,必须从输出窗口中的字符串中去除平台信息,以获取 PFN 包名。 例如,如果输出窗口中呈现的完整程序包名称为 9fe3058b-3de0-4e05-bea7-84a06f0ee4f0_1.0.0.0_x86__yd7nk54bq29ra,需去除其中的 1.0.0.0_x86__,留下 9fe3058b-3de0-4e05-bea7-84a06f0ee4f0_yd7nk54bq29ra 作为所需的 PFN 包名。

还有一种简单的推荐方法:

  • 在解决方案浏览器中双击 Package.appxmanifest 文件打开清单文件设计器,切换到 Packaging 选项卡,查看 Package family name 一栏中的值。

编写调用服务的客户端应用

创建一个新的 Windows 通用应用项目作为客户端调用者应用, 打开需要调用 App Service 的代码文件,在代码顶部加入以下引用的命名空间:

using Windows.ApplicationModel.AppService; 

假设我们的客户端应用需要通过 App Service 获得一个新的 GUID 并将其显示在文本框上,我们现在应用的页面上添加一个按钮和一个文本框,并将文本框命名为 textBox

在页面的后台代码中声明一个 AppServiceConnection :

private AppServiceConnection guidService;  

然后为按钮的单击事件添加以下代码:

private async void button_Click(object sender, RoutedEventArgs e)
{
if (guidService == null)
{
guidService = new AppServiceConnection();
guidService.AppServiceName = "GuidProviderService";
guidService.PackageFamilyName = "0e37a0ad-6f9f-41f6-ac5f-ac93c00b9474_21qyshkbc51y2"; var status = await this.guidService.OpenAsync();
if (status != AppServiceConnectionStatus.Success)
{
button.Content = "Failed to connect";
return;
}
} var message = new ValueSet();
message.Add("requestguid", null); AppServiceResponse response = await this.guidService.SendMessageAsync(message); if (response.Status == AppServiceResponseStatus.Success)
{
if (response.Message != null)
textBox.Text = (string) response.Message["guid"];
}
}

以上代码很简单,首先检查 AppServiceConnection 实例是否存在,不存在则创建一个新的实例。创建时需要指定目标服务的名称和提供服务应用的包名,这两个值和上文在服务提供应用的清单文件中指定的一致。

创建好 AppServiceConnection 的实例,并设置好服务名称和包名属性后,调用 OpenAsync() 方法开启到服务的连接。该方法返回一个 AppServiceConnectionStatus 枚举指示打开连接的结果。

连接成功建立后,创建一个 ValueSet 并在其中填入一个名为 requestguid 的键,该键名遵守上文服务中定义的协议规则。由于本示例中服务仅对键名进行检查,键值无需填写。

之后调用 SendMessageAsync() 方法将之前创建的 ValueSet 的作为参数发送到服务,并等待响应。注意该方法是个异步方法,需要携带 await 关键字进行等待,所以按钮的单击事件处理代码也要添加 async 关键字。

服务端发回的响应状态可以通过 Status 属性进行检测,如果枚举值为 AppServiceResponseStatus.Success 则表示成功响应。接下来只需检查响应消息是否包含键guid,如果有则读取其键值即可。

如果所有代码编写无误,部署以上两个应用,并运行客户端应用,则当点击按钮时,客户端会正确地从服务端获得一个 GUID 并显示在文本框上。

调试

要调试客户端应用很简单,只需像一般调试 UWP 应用一样直接在 Visual Studio 中启动调试即可。或从开始菜单启动客户端应用,再通过 Visual Studio 附加调试器到启动的客户端进程。(注意进程列表中可能会出现两个窗口标题为客户端应用名称的进程,需选择应用自身的进程,而非 ApplicationFrameHost.exe,该进程是 UWP 应用的视图框架宿主进程。)

应用服务的调试要稍微麻烦一些,步骤如下:

  1. 确保整个解决方案都已经生成并部署(即服务提供应用和客户端调用者应用都已生成部署)。
  2. 打开服务提供应用项目(注意,是应用的项目,不是后台任务的项目)的项目属性设置,切换到调试(Debug)选项卡,在 启动动作(Start action) 中勾选 不启动应用,但在应用启动时开始调试(Do not launch, but debug my code when it starts)。
  3. 右键单击服务提供应用项目(注意,是应用的项目,不是后台任务的项目),选择设置为启动项目(Set as StartUp Project)。
  4. 在后台进程的服务代码中设端点。
  5. 在当前 Visual Studio 窗口中按下 F5 启动调试,此时应用应该不会启动,调试也不会立即启动,而是等待来自外部对服务的请求激活后台任务后才开始调试。
  6. 从开始菜单启动客户端调用者应用,点击按钮触发对服务的调用,此时 Visual Studio 会开始进行调试,并停在第 4 步中设置的断点处。

总结

虽然目前看来 UWP 提供的应用间交互能力相对较弱,但借助 App Service 还是能实现很多场景下的应用的。比如服务为其它应用提供一个 token 用于访问公用资源、服务生成一个一次性的安全 URL 等等。


本博客为个人博客备份用,本文原文地址:http://validvoid.net/uwp-app-service/

在 UWP 应用中创建、使用、调试 App Service (应用服务)的更多相关文章

  1. PL/Sql 中创建、调试、调用存储过程

    存储过程的详细建立方法 1.先建存储过程 左边的浏览窗口选择 procedures ,会列出所有的存储过程,右击文件夹procedures单击菜单"new",弹出 template ...

  2. 【转载】在 Visual Studio 2012 中创建 ASP.Net Web Service

    在 Visual Studio 2012 中创建 ASP.Net Web Service,步骤非常简单.如下: 第一步:创建一个“ASP.Net Empty Web Application”项目 创建 ...

  3. 2020年的UWP(2)——In Process App Service

    最早的时候App Service被定义为一种后台服务,类似于极简版的Windows Service.App Service作为Background Task在宿主UWP APP中运行,向其他UWP A ...

  4. Visual Studio 2010中创建ASP.Net Web Service

    转自:http://blog.csdn.net/xinyaping/article/details/7331375 很多人在论坛里说,在Visual Studio 2010中不能创建“ASP.Net ...

  5. (转)在 Visual Studio 2010 中创建 ASP.Net Web Service

    很多人在论坛里说,在Visual Studio 2010中不能创建“ASP.Net Web Service”这种project了,下面跟帖者云云,有的说这是因为微软已经将Web Service整合进W ...

  6. 在 Visual Studio 2010 中创建 ASP.Net Web Service

    第一步:创建一个“ASP.Net Empty Web Application”项目 第二步:在项目中添加“Web Service”新项目 第一步之后,Visual Studio 2010会创建一个仅含 ...

  7. UWP/Win10新特性系列—App Service

    Win10中,新增了一个很实用的新特性叫做App Service,App Service允许App不在前台运行的情况下提供出一个或多个对外服务供其他App使用,这看起来就好像Web开发中的Web Ap ...

  8. 【Azure 应用服务】在 App Service for Windows 中自定义 PHP 版本的方法

    问题描述 在App Service for Windows的环境中,当前只提供了PHP 7.4 版本的选择情况下,如何实现自定义PHP Runtime的版本呢? 如 PHP Version 8.1.9 ...

  9. 【应用服务 App Service】App Service中抓取网络日志

    问题描述 众所周知,Azure App Service是一种PaaS服务,也就是说,IaaS层面的所有内容都由平台维护,所以使用App Service的我们根本无法触碰到远行程序的虚拟机(VM), 所 ...

随机推荐

  1. ubuntu14.10,解决按照最新版Gnome 15.10后,经典Gnome桌面字体问题!

    ubuntu14.10刚安装完毕,我首先按照了经典Gnome桌面,随后我发现ubuntu软件中心里面能找到的软件明显不如先前我安装过的ubuntu了,我觉得有可能是因为我以前安装的ubuntu14.1 ...

  2. linux联网配置(更新)

    重启网络配置:service network restart: 常见问题: linux 虚拟机ifconfig 显示eth1 文件ifcfg-eth0中device为eth0的问题   为什么eth0 ...

  3. windows下安装newman

    1.下载安装node.js,下载地址::https://nodejs.org/en/download/,这里我下载的为v10.15.0-x64.msi,下载后直接安装即可,安装完后可输入node -v ...

  4. kali linux之拒绝服务

    Dos不是DOS(利用程序漏洞或一对一资源耗尽的denial of service拒绝服务) DDoS分布式拒绝服务(多对一的攻击汇聚资源能力,重点在于量大,属于资源耗尽型) 历史 以前:欠缺技术能力 ...

  5. 【bzoj5093】 [Lydsy1711月赛]图的价值 组合数+斯特林数+NTT

    Description "简单无向图"是指无重边.无自环的无向图(不一定连通). 一个带标号的图的价值定义为每个点度数的k次方的和. 给定n和k,请计算所有n个点的带标号的简单无向 ...

  6. redis源码分析(3)sds

    sds是redis中用来处理字符串的数据结构.sds的定义在sds.h中: typedef char *sds; 简洁明了!简明扼要!(X,玩我呢是吧!这特么不就是c中的字符串么?!).像redis这 ...

  7. 对于Discuz 及PHP的一点个人感受

    首先我知道PHP是一种编程语言,PHP这玩意灵活性够了,但总是让人感觉写出来的代码让人如坠五里雾中,一会一个变量,$什么,一会又一个$.对于它是什么类型我有时候结合上下文件,还能找得到,但是有的时候最 ...

  8. jeesite模块解析,功能实现

    做为十分优秀的开源框架,JeeSite拥有着很多实用性的东西. 默认根路径跳转 定义了无Controller的path<->view直接映射 <mvc:view-controller ...

  9. JAVA通过网站域名URL获取该网站的源码(2018

    import java.io.ByteArrayOutputStream;   import java.io.InputStream;   import java.net.HttpURLConnect ...

  10. Oracle Purge和drop的区别

    转自: http://www.cnblogs.com/HondaHsu/archive/2012/09/28/2707487.html 最近发现oracle中出现了这些奇怪的表名,上网查找后发现是or ...