UWP集成Cortana

Windows 10中引入了Cortana语音助手,同时开放了一些新的API以供开发者接入。

最近在做Cortana接入,实现一些简单的交互操作。期间遇到了一些问题,目前关于UWP的资料也比较少,还好最后问题都解决了。

目前Windows 10中Cortana支持前台集成(跳转到应用)和后台集成(在Cortana中完成任务),下面将针对两种模式分别介绍。

 

1、VCD文件介绍

VCD文件是一个命令配置文件,可以在VCD文件中定义命令等信息。VCD文件有以下几个元素组成:

VoiceCommands:VoiceCommands的xmlns属性值必须为 http://schemas.microsoft.com/voicecommands/1.2,WP的是1.0 WP8.1的是1.1(没记错的话),VoiceCommands有1-15个CommandSet,对应着不同的语言

CommandSet:是针对不同语言定义的一组命令,xml:lang=“zh-cn”标识这组命令对应着中文。

CommandPrefix:是CommandSet的子节点,定义了命令的前缀,通常命令组成是以 CommandPrefix开头+Command来实现命令。

Command:定义了单个命令。

ListenFor:表示需要接听的命令,如 搜索{scenery} 可以识别所有  CommandPrefix+搜索+景区 的命令。{scenery}可以在PhraseTopic中定义类型。可以同时定义多组ListenFor,如

搜索{scenery}
我要去{scenery}
{scenery}的门票

Feedback:表示Cortana识别命令后,在执行应用的代码之前给用户的一个反馈。

VoiceCommandService、Navigate:两个可选,但是必须有一个。Navigate用于启动App来执行操作(前台集成),VoiceCommandService用于在Cortana中完成任务(后台集成)

PhraseTopic:是CommandSet的子节点,有点类似占位符的意思,可以定义PhraseTopic的场景来Subject来提高识别率。比如 我要去{city}


City/State

更多关于VCD文件的介绍可以查看MSDN中的文档:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/dn706593.aspx

2、后台集成

后台集成可以不打开应用,直接在Cortana中完成任务。下面将针对实例场景来说明如何后台集成Cortana。

首先新建一个UWP应用CortanaDemo,新增一个Windows Runtime Component工程CortanaService。新建一个CortanaCommandService,继承IBackgroundTask接口。在CortanaDemo中引用CortanaService,打开Package.appxmanifest文件,在Application节点添加如下节点:

1 <Extensions>
2         <uap:Extension Category="windows.appService" EntryPoint="CortanaDemo.CortanaService.CortanaCommandService">
3           <uap:AppService Name="CortanaCommandService" />
4         uap:Extension>
5         <uap:Extension Category="windows.personalAssistantLaunch"/>
6       Extensions>

同时在CortanaDemo中新增一个VCD文件CortanaCommand.xml ,定义一个命令:

xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
  <CommandSet xml:lang="zh-cn" Name="AdventureWorksCommandSet_zh-cn">
    <CommandPrefix>我要玩CommandPrefix>
    <Example>我要玩Example>
 
    <Command Name="ScenerySearch">
      <Example>搜索 苏州乐园 Example>
      <ListenFor>搜索{destination}ListenFor>
      <ListenFor>我要去{destination}ListenFor>
      <ListenFor>{destination}的门票ListenFor>
      <ListenFor>{destination}ListenFor>
      <ListenFor>查找{destination}ListenFor>
      <Feedback> 正在搜索{destination}... Feedback>
      <VoiceCommandService Target="TCTVoiceCommandService"/>
    Command>
    <PhraseTopic Label="destination" Scenario="Natural Language">
      <Subject>City/StateSubject>
      <Subject>City/StateSubject>
    PhraseTopic>
    <PhraseTopic Label="from" Scenario="Natural Language">
      <Subject>City/StateSubject>
    PhraseTopic>
    <PhraseTopic Label="to" Scenario="Natural Language">
      <Subject>City/StateSubject>
    PhraseTopic>
    <PhraseTopic Label="date" Scenario="Natural Language">
      <Subject>Date/TimeSubject>
    PhraseTopic>
  CommandSet>
VoiceCommands>

同时在App.xaml.cs中添加如下代码:

var vcdStorageFile = await Package.Current.InstalledLocation.GetFileAsync(@"CortanaCommand.xml");

await Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(vcdStorageFile);

到此,项目就搭建完成了。下面来实现IBackgroundTask接口。

public async void Run(IBackgroundTaskInstance taskInstance)
        {
//异步任务需要获取Deferral。 serviceDeferral
= taskInstance.GetDeferral(); taskInstance.Canceled += OnTaskCanceled; var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails; if (triggerDetails != null && triggerDetails.Name == "CortanaCommandService") { try { voiceServiceConnection = VoiceCommandServiceConnection.FromAppServiceTriggerDetails( triggerDetails); voiceServiceConnection.VoiceCommandCompleted += OnVoiceCommandCompleted; VoiceCommand voiceCommand = await voiceServiceConnection.GetVoiceCommandAsync(); // perform the appropriate command. switch (voiceCommand.CommandName) { case "ScenerySearch": var destination = voiceCommand.Properties["destination"][0]; await SearchSceneryByKey(destination); break; default: LaunchAppInForeground(); break; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Handling Voice Command failed " + ex.ToString()); } } }

 

private async Task SearchSceneryByKey(string destination)
{
#region 查询
//通过destination来从网络查询数据.....

#endregion

var destinationsContentTiles = new List();
foreach (var scenery in result.Item2)
{
destinationsContentTiles.Add(new VoiceCommandContentTile { Title = scenery.sceneryName, TextLine1 = "" + scenery.tcPrice, TextLine2 = scenery.address, ContentTileType = VoiceCommandContentTileType.TitleWithText });
}

var userMessage = new VoiceCommandUserMessage
{
DisplayMessage = string.Format("{0}有关的景点", destination),
SpokenMessage = string.Format("找到了{0}条信息,请问您想查看哪一条?", result.Item2.Count)
};
var repeat = new VoiceCommandUserMessage
{
DisplayMessage = "请再说一遍",
SpokenMessage = "不好意思,没听清楚"
};
var response = VoiceCommandResponse.CreateResponseForPrompt(userMessage, repeat, destinationsContentTiles);
var resultd = await voiceServiceConnection.RequestDisambiguationAsync(response);

var selectedScenery = resultd.SelectedItem.AppContext as resScenerysModel;

SceneryVMLocator.SceneryVM.CurrentScenery = new resScenerysModel
{
sceneryId = selectedScenery.sceneryId
};

userMessage = new VoiceCommandUserMessage
{
DisplayMessage = "正在显示景点详情...",
SpokenMessage = "正在显示景点详情..."
};

response = VoiceCommandResponse.CreateResponse(userMessage);

response.AppLaunchArgument = InternalUrlBuilder.Build(ProductType.Scenery, InternalUrlBuilder.Action.details, selectedScenery.sceneryId);

await voiceServiceConnection.RequestAppLaunchAsync(response);
}

这点代码有以下功能:

1、获取命令类型

2、查询网络数据

3、展示并询问用户需要查看哪一条

4、启动应用显示详情

至此基本完成了Cortana后台集成。

VoiceCommandServiceConnection有以下几个方法:

1、GetVoiceCommandAsync:获取命令

2、ReportFailureAsync:返回失败

3、ReportProgressAsync:向用户报告进度,如果需要长耗时操作,需要向用户返回当前的操作信息。

4、ReportSuccessAsync:返回操作成功而不需要接收返回。

5、RequestAppLaunchAsync:从Cortana中启动应用。

6、RequestConfirmationAsync:向用户发送Yes/No请求

7、RequestDisambiguationAsync:当含有多条数据时,供用户选择,如返回若干条景区信息,询问用户需要查看哪一条。

 

不知道大家有没有发现代码中有两处被加粗加大了。这两处是我在开发时遇到的坑。

1、ContentTileType = VoiceCommandContentTileType.TitleWithText。

在添加数据时,可以展示多种类型的列表,如仅标题、图片等,具体信息可以查看VoiceCommandContentTileType。

在开发的时候,只需要展示一些基本的景区信息,而没有展示景区图片,所以最初只定义了

new VoiceCommandContentTile { Title = scenery.sceneryName, TextLine1 = "" + scenery.tcPrice, TextLine2 = scenery.address}

在部署操作的时候,Cortana经常返回失败,偶尔会成功展示几次信息,而去没有任何报错信息。当时对着Cortana喊了一下午,各种调试都不行(旁边的iOS开发已经默默的戴上了耳机)。后来设置了ContentTileType = VoiceCommandContentTileType.TitleWithText才成功,具体原因还不清楚,不知道是不是ContentTileType默认值是带icon的,导致image找不到才提示错误。

2、

当用户选择要查看哪个景区的时候,此时下单流程比较麻烦,需要回到客户端中进行操作,此时可以调用RequestAppLaunchAsync来启动客户端,在App.xaml.cs里override OnActivated方法来接收启动参数

protected async override void OnActivated(IActivatedEventArgs args)
        {
            base.OnActivated(args);

            shell = Window.Current.Content as AppShell;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (shell == null)
            {
                // Create a AppShell to act as the navigation context and navigate to the first page
                shell = new AppShell();

                // Set the default language
                shell.Language = Windows.Globalization.ApplicationLanguages.Languages[0];

                shell.AppFrame.NavigationFailed += OnNavigationFailed;
            }

            // Place our app shell in the current Window
            Window.Current.Content = shell;

            //读写文件一般比较快,几乎不会影响启动性能
            CurrentCity = await IsolatedStorageHelper.Instance.GetLastCity();
            Window.Current.Activate();
            switch (args.Kind)
            {
                case ActivationKind.VoiceCommand:
                    {
                        break;
                    }
                case ActivationKind.Protocol:
                    {
                        var command = args as ProtocolActivatedEventArgs;
                        Windows.Foundation.WwwFormUrlDecoder decoder = new Windows.Foundation.WwwFormUrlDecoder(command.Uri.Query);
                        var destination = decoder.GetFirstValueByName("LaunchContext");
                        new MessageDialog(destination).ShowAsync();
                        InternalJumper.initNoticeUrl(destination);
                        break;
                    }

            }

          
        }

RequestAppLaunchAsync的启动类型是Protocol,可以把args强制转换为ProtocolActivatedEventArgs来取到Uri参数。

那么问题来了,在开发过程中,每次调用RequestAppLaunchAsync来启动客户端的时候,应用都会闪退,各种google,bing也搜不到资料,直到与微软的demo一行行代码对比后才发现需要添加一个权限才能实现启动

 

重要的事情要说三遍、三遍、三遍。

添加后应用完美启动。

 

备注:

VoiceCommandContentTile的Title要有区分性,调用RequestDisambiguationAsync的时候会报错。

 

2、前台启动

待续...

转载于:https://www.cnblogs.com/NailClipper/articles/4765771.html

你可能感兴趣的:(操作系统,shell,移动开发)