在 UWP 应用中创建、使用、调试 App Service (应用服务)

原文:在 UWP 应用中创建、使用、调试 App Service (应用服务)

在 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 选择查看代码 (View Code)
  • 在打开的清单文件中找到 <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/

时间: 2024-10-29 03:40:22

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

如何在SharePoint 2013中创建WCF REST Service

SharePoint 2013为开发者提供了丰富的REST API,方便了我们在客户端操作List中的数据.当然我们也可以在SharePoint 2013中创建自定义的REST Service,比如通过REST Service去操作数据库.本篇博客将介绍怎样在SharePoint 2013创建WCF REST Service. SharePoint 中 创建WCF Service 因为无法在SharePoint 2013 Project中添加WCF Service Template,所以预先创建

iOS9中如何在日历App中创建一个任意时间之前开始的提醒(三)

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 四.创建任意时间之前开始的提醒 现在我们找到了指定源中的指定日历,剩下要做的就是在日历中创建一个事件,并且设置其对于的提醒.我们可以创建2种类型的事件:单一的和循环的(或者称之为重复的).这里为了简单起见(KISS),我们只创建单一的事件: -(void)addAlarmToCalendar:(EKCalendar*)calendar inStore:(EKE

Android App在线程中创建handler的方法讲解_Android

相关概念1.Handler:可以看做是一个工具类,用来向消息队列中插入消息的; 2.Thread:所有与Handler相关的功能都是与Thread密不可分的,Handler会与创建时所在的线程绑定; 3.Message:消息; 4.MessageQueue:消息队列,对消息进行管理,实现了一个Message链表; 5.Looper:消息循环,从MessageQueue中取出Message进行处理: 6.HandlerThread:继承Thread,实例化时自动创建Looper对象,实现一个消息循

Android App在线程中创建handler的方法讲解

相关概念 1.Handler:可以看做是一个工具类,用来向消息队列中插入消息的; 2.Thread:所有与Handler相关的功能都是与Thread密不可分的,Handler会与创建时所在的线程绑定; 3.Message:消息; 4.MessageQueue:消息队列,对消息进行管理,实现了一个Message链表; 5.Looper:消息循环,从MessageQueue中取出Message进行处理: 6.HandlerThread:继承Thread,实例化时自动创建Looper对象,实现一个消息

如何用 React Native 创建一个iOS APP?

诚然,React Native 结合了 Web 应用和 Native 应用的优势,可以使用 JavaScript 来开发 iOS 和 Android 原生应用.在 JavaScript 中用 React 抽象操作系统原生的 UI 组件,代替 DOM 元素来渲染等. React Native 使你能够使用基于 JavaScript 和 React 一致的开发体验在本地平台上构建世界一流的应用程序体验.React Native 把重点放在所有开发人员关心的平台的开发效率上--开发者只需学习一种语言就

为什么c程序中创建了数据文件,再次打开源代码运行时数据没了?

问题描述 为什么c程序中创建了数据文件,再次打开源代码运行时数据没了? 为什么c程序中创建了数据文件,再次打开源代码运行时数据没了? http://blog.csdn.net/hackbuteer1/article/details/6573488# 就是这个通讯录的代码,在里面创建通讯录后,再次打开运行显示通讯录时就提示通讯录为空. 解决方案 写入文件是否错误,用winhex或者ultraedit看看写文件本身有没有写对. 如果没有,就是写的问题,如果对的,那么就是读的问题.再具体调试有问题的程

在 Visual Basic .NET 和 Visual C# .NET 中创建控件数组

visual|创建|控件|数组 在 Visual Basic .NET 和 Visual C# .NET 中创建控件数组 Matthew A. StoeckerVisual Studio TeamMicrosoft Corporation 2002 年 1 月 摘要:本文介绍如何使用 Visual Basic .NET 和 Visual C# .NET 创建和管理控件数组. 目录 简介 前提 创建项目 实现集合 公开控件数组 创建公共事件处理程序 测试项目 总结 简介数组为使用共享公共功能的控件

如何在Microsoft Visual Studio 2005中创建控制台应用程序

在 Visual Studio 2005 中创建控制台应用程序 在 Visual Studio 2005 中的"文件" 菜单上,指向"新建" 并单击"项目". 在"新建项目" 对话框中,选择一种语言,然后在"项目类型" 框中选择"Windows". 在"模板" 框中,选择"控制台应用程序" . 在"位置" 框中,键入指向应用程序

在Excel中创建与使用标签套打模板方法

  在Excel中创建与使用标签套打模板方法         1.在日常资料.设备的标识分类管理中,经常用到标签,常见的是空白不干胶标签纸. 2.当标签数量较多时,填写费时费力,容易刮花墨迹.因此,利用打印机批量打印标签,是实现高效办公.轻松办公的常用手段. 3.Microsoft Excel 拥有优秀的数据处理能力,功能丰富又不失灵活,利用它来进行标签套打具备独特的优势. 本文介绍一种在Excel中套打标签的方法/工具,生成带有标签背景的输出界面,实现数据的批量录入与打印. 工具/原料 单据扫