以下步骤带您完成创建可使用计划任务注册后台代理的简单应用程序。
创建可使用计划任务的应用程序的步骤
在 Visual Studio 中,创建一个新的“Windows Phone 应用程序”项目。此模板在“Silverlight for Windows Phone”类别中。
接下来,向解决方案中添加一个计划任务项目。从“文件”菜单中,选择“添加->新项目...”。在“添加新项目”对话框中,选择“Windows Phone 计划任务代理”。保留默认名称 ScheduledTaskAgent1,然后单击“确定”。
下一步,在您的前台应用程序项目中,您需要添加对该代理项目的引用。在“解决方案资源管理器”中,单击您的前台应用程序项目以将其选中。接下来,从“项目”菜单中,选择“添加引用...”。在“添加引用”对话框中,选择“属性”标签。选择代理项目 ScheduledTaskAgent1,然后单击“确定”。
在“解决方案资源管理器” 中,双击 ScheduledTaskAgent1 项目下的 ScheduledAgent.cs 以打开该文件。您将看到该文件包含单个类 ScheduledAgent 的定义,该类是从基类 ScheduledTaskAgent 继承的。对于此示例,为文件顶部的 Shell 命名空间和 System 命名空间添加一个 using 指令。
using Microsoft.Phone.Scheduler; using Microsoft.Phone.Shell; using System;
有一个在 OnInvoke(ScheduledTask) 类中实现的方法。当启动计划任务时操作系统调用该方法。这是您放置在运行后台代理时您希望执行的代码的位置。每个应用程序一次只能注册一个 ScheduledTaskAgent,但您可以作为资源密集型代理和定期代理计划此代理。如果应用程序使用 ResourceIntensiveTask 和 PeriodicTask,则可以检查传递给 OnInvoke 方法的 ScheduledTask 对象的类型,以确定代理调用的任务并在必要时确定代码执行的分支。如果您只使用一种类型的代理,则不需要检查 ScheduledTask 对象类型。在此示例中,代理从 OnInvoke 启动 ShellToast 对象,指示代理调用的计划任务类型。此 Toast 将在代理运行时使代理可见。但是,代理在前台应用程序运行时不显示。
完成计划任务代码之后,应该调用 NotifyComplete()()()() 以让操作系统知道您不再需要运行。这样便允许操作系统尝试计划其他代理。
protected override void OnInvoke(ScheduledTask task) { //TODO: Add code to perform your task in background string toastMessage = ""; // If your application uses both PeriodicTask and ResourceIntensiveTask // you can branch your application code here. Otherwise, you don't need to. if (task is PeriodicTask) { // Execute periodic task actions here. toastMessage = "Periodic task running."; } else { // Execute resource-intensive task actions here. toastMessage = "Resource-intensive task running."; } // Launch a toast to show that the agent is running. // The toast will not be shown if the foreground application is running. ShellToast toast = new ShellToast(); toast.Title = "Background Agent Sample"; toast.Content = toastMessage; toast.Show(); // If debugging is enabled, launch the agent again in one minute. #if DEBUG_AGENT ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(60)); #endif // Call NotifyComplete to let the system know the agent is done working. NotifyComplete(); }
提供了 LaunchForTest(String, TimeSpan) 方法以便您运行代理的频率高于实际设备上运行的计划。此方法仅用于应用程序开发目的。除了在使用开发工具部署的应用程序中之外,此方法不起任何作用。您应该从生产应用程序中删除对此方法的调用。在此示例中,该调用位于 #if 块中,以便您能够在调试和生产功能之间轻松切换。若要启用该调用,请在 ScheduledAgent.cs 文件的顶部放置以下代码行。
#define DEBUG_AGENT
在接下来的步骤中,修改前台应用程序以允许用户启用和禁用定期代理和资源密集型代理。首先,打开 MainPage.xaml 文件并在名为“ContentPanel”的 Grid 元素中粘贴以下 XAML 代码。
<StackPanel> <StackPanel Orientation="Vertical" Name="PeriodicStackPanel" Margin="0,0,0,40"> <TextBlock Text="Periodic Agent" Style="{StaticResource PhoneTextTitle2Style}"/> <StackPanel Orientation="Horizontal"> <TextBlock Text="name: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding Name}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="is enabled" VerticalAlignment="Center" Style="{StaticResource PhoneTextAccentStyle}"/> <CheckBox Name="PeriodicCheckBox" IsChecked="{Binding IsEnabled}" Checked="PeriodicCheckBox_Checked" Unchecked="PeriodicCheckBox_Unchecked"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="is scheduled: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding IsScheduled}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="last scheduled time: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding LastScheduledTime}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="expiration time: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding ExpirationTime}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="last exit reason: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding LastExitReason}" /> </StackPanel> </StackPanel> <StackPanel Orientation="Vertical" Name="ResourceIntensiveStackPanel" Margin="0,0,0,40"> <TextBlock Text="Resource-intensive Agent" Style="{StaticResource PhoneTextTitle2Style}"/> <StackPanel Orientation="Horizontal"> <TextBlock Text="name: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding Name}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="is enabled" VerticalAlignment="Center" Style="{StaticResource PhoneTextAccentStyle}"/> <CheckBox Name="ResourceIntensiveCheckBox" IsChecked="{Binding IsEnabled}" Checked="ResourceIntensiveCheckBox_Checked" Unchecked="ResourceIntensiveCheckBox_Unchecked"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="is scheduled: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding IsScheduled}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="last scheduled time: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding LastScheduledTime}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="expiration time: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding ExpirationTime}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="last exit reason: " Style="{StaticResource PhoneTextAccentStyle}"/> <TextBlock Text="{Binding LastExitReason}" /> </StackPanel> </StackPanel> </StackPanel>
该代码添加两组控件,分别用于每种代理类型。大多数控件都是文本块,这些文本块将绑定到表示后台代理的 ScheduledTask 对象,从而允许您查看这些对象的属性。此外,还为每种代理类型添加一个复选框,该复选框显示并允许用户切换每种代理的启用状态。为将用于切换代理打开和关闭的 Checked 和 Unchecked 事件添加处理程序。
在 MainPage.xaml.cs 中,向页面的顶部添加 Microsoft.Phone.Scheduler 命名空间的 using 指令。
using Microsoft.Phone.Scheduler;
创建两个将表示每种代理类型的类变量。这些是将绑定到 UI 的对象。计划操作服务通过其 Name 属性唯一标识计划任务。创建两个变量,这些变量包含将用于代理的名称。创建一个布尔变量,以跟踪最终用户是否已禁用后台代理。在类定义中添加这些代码行。
public partial class MainPage : PhoneApplicationPage { PeriodicTask periodicTask; ResourceIntensiveTask resourceIntensiveTask; string periodicTaskName = "PeriodicAgent"; string resourceIntensiveTaskName = "ResourceIntensiveAgent"; public bool agentsAreEnabled = true;
接下来,实现一个名为 StartPeriodicAgent 的帮助器方法。此方法首先使用 Find(String) 方法获取对具有指定名称的 PeriodicTask 的引用。如果计划任务对象不为 null,则应该调用 Remove(String) 向系统注销此代理。不能直接更新代理。必须删除,然后再添加。下面,创建一个新的 PeriodicTask 对象并在构造函数中分配其名称。接下来,设置 Description 属性。该属性是定期代理所必需的,用于在设备的后台任务“设置”页面中向用户介绍该代理。下面,调用 Add(ScheduledAction) 以向系统注册定期代理。如果用户已禁用应用程序的后台代理,则调用 Add 时会引发 InvalidOperationException,因此应该将该调用放置在 try 块内。如果调用成功,则设置关联 UI 元素的数据上下文,以更新数据绑定并向用户显示对象属性。如果调用引发异常,则验证异常的消息字符串。如果字符串为“BNS Error: The action is disabled(BNS 错误: 该操作已被禁用)”,则您应该警告用户后台代理已被禁用。
包含 LaunchForTest(String, TimeSpan) 方法以在调用该方法一分钟后启动代理。与之前对该方法的调用一样,将其放在 #if 块内,以允许应用程序在调试和生产模式之间轻松切换。若要启用该方法,请在 MainPage.xaml.cs 的顶部包含以下行。
#define DEBUG_AGENT
将以下方法粘贴到 MainPage 类定义中。
private void StartPeriodicAgent() { // Variable for tracking enabled status of background agents for this app. agentsAreEnabled = true; // Obtain a reference to the period task, if one exists periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask; // If the task already exists and background agents are enabled for the // application, you must remove the task and then add it again to update // the schedule if (periodicTask != null) { RemoveAgent(periodicTaskName); } periodicTask = new PeriodicTask(periodicTaskName); // The description is required for periodic agents. This is the string that the user // will see in the background services Settings page on the device. periodicTask.Description = "This demonstrates a periodic task."; // Place the call to Add in a try block in case the user has disabled agents. try { ScheduledActionService.Add(periodicTask); PeriodicStackPanel.DataContext = periodicTask; // If debugging is enabled, use LaunchForTest to launch the agent in one minute. #if(DEBUG_AGENT) ScheduledActionService.LaunchForTest(periodicTaskName, TimeSpan.FromSeconds(60)); #endif } catch (InvalidOperationException exception) { if (exception.Message.Contains("BNS Error: The action is disabled")) { MessageBox.Show("Background agents for this application have been disabled by the user."); agentsAreEnabled = false; PeriodicCheckBox.IsChecked = false; } if (exception.Message.Contains("BNS Error: The maximum number of ScheduledActions of this type have already been added.")) { // No user action required. The system prompts the user when the hard limit of periodic tasks has been reached. } PeriodicCheckBox.IsChecked = false; } catch (SchedulerServiceException) { // No user action required. PeriodicCheckBox.IsChecked = false; } }
接下来,实现资源密集型任务的启动帮助器方法。该方法与当前代理的方法相同,只是它使用 ResourceIntensiveTask 类计划代理并使用不同的名称。
private void StartResourceIntensiveAgent() { // Variable for tracking enabled status of background agents for this app. agentsAreEnabled = true; resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask; // If the task already exists and background agents are enabled for the // application, you must remove the task and then add it again to update // the schedule. if (resourceIntensiveTask != null) { RemoveAgent(resourceIntensiveTaskName); } resourceIntensiveTask = new ResourceIntensiveTask(resourceIntensiveTaskName); // The description is required for periodic agents. This is the string that the user // will see in the background services Settings page on the device. resourceIntensiveTask.Description = "This demonstrates a resource-intensive task."; // Place the call to Add in a try block in case the user has disabled agents. try { ScheduledActionService.Add(resourceIntensiveTask); ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask; // If debugging is enabled, use LaunchForTest to launch the agent in one minute. #if(DEBUG_AGENT) ScheduledActionService.LaunchForTest(resourceIntensiveTaskName, TimeSpan.FromSeconds(60)); #endif } catch (InvalidOperationException exception) { if (exception.Message.Contains("BNS Error: The action is disabled")) { MessageBox.Show("Background agents for this application have been disabled by the user."); agentsAreEnabled = false; } ResourceIntensiveCheckBox.IsChecked = false; } catch (SchedulerServiceException) { // No user action required. ResourceIntensiveCheckBox.IsChecked = false; } }
添加一个布尔类变量 ignoreCheckBoxEvents。该变量将用于在初始化页面时关闭 CheckBox 事件。
bool ignoreCheckBoxEvents = false;
现在,为 CheckBox 控件的 Checked 和 Unchecked 事件添加事件处理程序。这些处理程序调用在上面步骤中创建的启动和停止帮助器方法。如果 ignoreCheckBoxEvents 为 true,则处理程序返回且不执行任何操作。
private void PeriodicCheckBox_Checked(object sender, RoutedEventArgs e) { if (ignoreCheckBoxEvents) return; StartPeriodicAgent(); } private void PeriodicCheckBox_Unchecked(object sender, RoutedEventArgs e) { if (ignoreCheckBoxEvents) return; RemoveAgent(periodicTaskName); } private void ResourceIntensiveCheckBox_Checked(object sender, RoutedEventArgs e) { if (ignoreCheckBoxEvents) return; StartResourceIntensiveAgent(); } private void ResourceIntensiveCheckBox_Unchecked(object sender, RoutedEventArgs e) { if (ignoreCheckBoxEvents) return; RemoveAgent(resourceIntensiveTaskName); }
RemoveAgent 帮助器方法只是在 try 块中调用 Remove(String) 以便异常不会导致应用程序退出。
private void RemoveAgent(string name) { try { ScheduledActionService.Remove(name); } catch (Exception) { } }
最后一步是重写 PhoneApplicationPage 类的 OnNavigatedTo(NavigationEventArgs) 方法。当用户导航到此页面时,将会调用此方法。将 ignoreCheckBoxEvents 设置为 true,以便 CheckBox 事件处理程序在本质上被禁用。然后,使用 Find(String) 方法查看是否已向系统注册应用程序的定期代理和资源密集型代理,并更新 CheckBox 控件以反映应用程序的当前状态。最后,将 ignoreCheckBoxEvents 设置回 false。
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { ignoreCheckBoxEvents = true; periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask; if (periodicTask != null) { PeriodicStackPanel.DataContext = periodicTask; } resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask; if (resourceIntensiveTask != null) { ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask; } ignoreCheckBoxEvents = false; }