Windows phone 8 是一个单任务操作系统,任何时候都只有一个应用处于活跃状态,这里的多任务是指对后台任务的支持。本节我们先讲讲应用程序的运行状态,然后看看支持的后台任务,包括:后台代理、后台音频、后台文件传输、后台辅助线程等。

快速导航:
一、应用的状态
二、后台代理
三、后台音频
四、后台文件传输
五、后台辅助线程

一、应用的状态

1)应用的运行状态

我们通过图解来分析应用的运行状态,启动并置于前台界面的应用是唯一处于运行状态的,其他的操作,比如win键,后退导出应用,打开选择器和启动器时都会让当前运行的应用进入休眠状态,如果系统内存不足,处于休眠状态的应用可能会被系统逻辑删除。下面的图示演示了这个过程。


2)如何恢复状态

当应用处于休眠状态时,它的状态信息仍然保留在内存中,用户下次切换进去后不会有任何变化。但是当应用被逻辑删除后,这些状态信息就会丢失,比如表单填写的内容都会消失,为了避免这种情况,我们需要手动保留状态信息。
    首先,我们在mainpage定义一些页面表单控件:

[XAML]

  1. <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  2. <TextBox Text="{Binding TextBox1Text, Mode=TwoWay}" Height="72"
  3. HorizontalAlignment="Left" Margin="20,20,0,0" Name="textBox1"
  4. VerticalAlignment="Top" Width="440" />
  5. <CheckBox IsChecked="{Binding CheckBox1IsChecked, Mode=TwoWay}"
  6. Content="CheckBox" Height="71" Name="checkBox1" Margin="20,100,0,0"
  7. VerticalAlignment="Top"/>
  8. <Slider Value="{Binding Slider1Value, Mode=TwoWay}" Height="84" Name="slider1"
  9. Width="440" Margin="20,180,0,0" VerticalAlignment="Top"/>
  10. <RadioButton IsChecked="{Binding RadioButton1IsChecked, Mode=TwoWay}"
  11. Content="RadioButton 1" Height="71" Name="radioButton1"
  12. GroupName="RadioButtonGroup" Margin="20,260,0,0" VerticalAlignment="Top"/>
  13. <RadioButton IsChecked="{Binding RadioButton1IsChecked, Mode=TwoWay}"
  14. Content="RadioButton 2" Height="71" Name="radioButton2"
  15. GroupName="RadioButtonGroup" Margin="20,340,0,0" VerticalAlignment="Top"/>
  16. </Grid>

我们需要实现在应用逻辑删除后能将其状态保持到页面的State字典中,但是需要我们的数据源支持序列化,所以我们定义与表单关联的ViewModel如下:

[C#]

  1. [DataContract]
  2. public class ViewModel : INotifyPropertyChanged
  3. {
  4. private string _textBox1Text;
  5. private bool _checkBox1IsChecked;
  6. private bool _radioButton1IsChecked;
  7. private bool _radioButton2IsChecked;
  8. private double _slider1Value;
  9.  
  10. [DataMember]
  11. public string TextBox1Text
  12. {
  13. get { return _textBox1Text; }
  14. set
  15. {
  16. _textBox1Text = value;
  17. NotifyPropertyChanged("TextBox1Text");
  18. }
  19. }
  20.  
  21. [DataMember]
  22. public bool CheckBox1IsChecked
  23. {
  24. get { return _checkBox1IsChecked; }
  25. set
  26. {
  27. _checkBox1IsChecked = value;
  28. NotifyPropertyChanged("CheckBox1IsChecked");
  29. }
  30. }
  31.  
  32. [DataMember]
  33. public double Slider1Value
  34. {
  35. get { return _slider1Value; }
  36. set
  37. {
  38. _slider1Value = value;
  39. NotifyPropertyChanged("Slider1Value");
  40. }
  41. }
  42.  
  43. [DataMember]
  44. public bool RadioButton1IsChecked
  45. {
  46. get { return _radioButton1IsChecked; }
  47. set
  48. {
  49. _radioButton1IsChecked = value;
  50. NotifyPropertyChanged("RadioButton1IsChecked");
  51. }
  52. }
  53.  
  54. [DataMember]
  55. public bool RadioButton2IsChecked
  56. {
  57. get { return _radioButton2IsChecked; }
  58. set
  59. {
  60. _radioButton2IsChecked = value;
  61. NotifyPropertyChanged("RadioButton2IsChecked");
  62. }
  63. }
  64.  
  65. public event PropertyChangedEventHandler PropertyChanged;
  66.  
  67. private void NotifyPropertyChanged(string propertyName)
  68. {
  69. if (null != PropertyChanged)
  70. PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  71. }
  72.  
  73. }

我需要对mainpage代码添加页面导航入、导航出的事件。导航出页面的时候,如果不是向后导航,则存储状态。导航入的时候,我们需要判断页面是否为逻辑删除后正在恢复的状态,如果是,则通过状态字典恢复状态。mainpage代码如下:

[C#]

  1. public partial class MainPage : PhoneApplicationPage
  2. {
  3. // 构造函数
  4. public MainPage()
  5. {
  6. InitializeComponent();
  7. _isNewPageInstance = true;
  8. }
  9.  
  10. ViewModel _viewModel = null;
  11.  
  12. /// <summary>
  13. /// 新实例还是现有实例
  14. /// </summary>
  15. bool _isNewPageInstance = false;
  16.  
  17. private void Button_Click_1(object sender, RoutedEventArgs e)
  18. {
  19. NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
  20. }
  21.  
  22. protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
  23. {
  24. //如果不是向后导航,则保存状态
  25. if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
  26. {
  27. State["ViewModel"] = _viewModel;
  28.  
  29. }
  30. }
  31.  
  32. protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  33. {
  34. if (_isNewPageInstance)
  35. {
  36. if (_viewModel == null)
  37. {
  38. if (State.Count > 0)
  39. {
  40. _viewModel = (ViewModel)State["ViewModel"];
  41. }
  42. else
  43. {
  44. _viewModel = new ViewModel();
  45. }
  46. }
  47. DataContext = _viewModel;
  48. }
  49. _isNewPageInstance = false;
  50. }
  51.  
  52. }

然后我们添加一page1页面,该页添加一个返回按钮。用于测试。为了达到调试时即时进入逻辑删除的效果,我们需要设置下。右键项目文件,点属性,在调试选项卡勾选“在调试期间取消激活时逻辑删除”。

二、后台代理

后台代理可以在应用退出以后独立在系统后台运行,它包含两种类型的代理,分别是定期代理和资源密集型代理,前者用于频繁执行小任务,后者用于在系统空闲时执行耗时大任务。要使用后台代理,我们需要添加一个名为Windows phone 计划任务代理的项目,并在应用的项目中添加对其的引用,现在我们要实现在后台代理中弹出Toast,我们需要如下修改ScheduledAgent.cs的OnInvoke方法,代码如下

[C#]

  1. protected override void OnInvoke(ScheduledTask task)
  2. {
  3. string toastMessage = "";
  4.  
  5. if (task is PeriodicTask)
  6. {
  7. toastMessage = "定期代理正在运行";
  8. }
  9. else
  10. {
  11. toastMessage = "资源密集型代理正在运行";
  12. }
  13.  
  14. // 用于向用户显示Toast,如果当前任务的前台正在运行,则不显示
  15. ShellToast toast = new ShellToast();
  16. toast.Title = "标题";
  17. toast.Content = toastMessage;
  18. toast.Show();
  19.  
  20. // 在调试的时候需要及时执行查看效果
  21. #if DEBUG_AGENT
  22. ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(15));
  23. #endif
  24.  
  25. NotifyComplete();
  26. }

接着,我们在应用项目的mainpage中调用代理,代码如下:

[XAML]

  1. <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  2. <StackPanel>
  3. <StackPanel Orientation="Vertical" Name="PeriodicStackPanel" Margin="0,0,0,40">
  4. <TextBlock Text="定期代理" Style="{StaticResource PhoneTextTitle2Style}"/>
  5. <StackPanel Orientation="Horizontal">
  6. <TextBlock Text="名称: " Style="{StaticResource PhoneTextAccentStyle}"/>
  7. <TextBlock Text="{Binding Name}" />
  8. </StackPanel>
  9. <StackPanel Orientation="Horizontal">
  10. <TextBlock Text="是否可用" VerticalAlignment="Center" Style="{StaticResource PhoneTextAccentStyle}"/>
  11. <CheckBox Name="PeriodicCheckBox" IsChecked="{Binding IsEnabled}" Checked="PeriodicCheckBox_Checked" Unchecked="PeriodicCheckBox_Unchecked"/>
  12. </StackPanel>
  13. <StackPanel Orientation="Horizontal">
  14. <TextBlock Text="是否已计划: " Style="{StaticResource PhoneTextAccentStyle}"/>
  15. <TextBlock Text="{Binding IsScheduled}" />
  16. </StackPanel>
  17. <StackPanel Orientation="Horizontal">
  18. <TextBlock Text="上次计划运行时间: " Style="{StaticResource PhoneTextAccentStyle}"/>
  19. <TextBlock Text="{Binding LastScheduledTime}" />
  20. </StackPanel>
  21. <StackPanel Orientation="Horizontal">
  22. <TextBlock Text="计划结束时间: " Style="{StaticResource PhoneTextAccentStyle}"/>
  23. <TextBlock Text="{Binding ExpirationTime}" />
  24. </StackPanel>
  25. <StackPanel Orientation="Horizontal">
  26. <TextBlock Text="上一次代理运行退出的原因: " Style="{StaticResource PhoneTextAccentStyle}"/>
  27. <TextBlock Text="{Binding LastExitReason}" />
  28. </StackPanel>
  29. </StackPanel>
  30. <StackPanel Orientation="Vertical" Name="ResourceIntensiveStackPanel" Margin="0,0,0,40">
  31. <TextBlock Text="资源密集型代理" Style="{StaticResource PhoneTextTitle2Style}"/>
  32. <StackPanel Orientation="Horizontal">
  33. <TextBlock Text="名称: " Style="{StaticResource PhoneTextAccentStyle}"/>
  34. <TextBlock Text="{Binding Name}" />
  35. </StackPanel>
  36. <StackPanel Orientation="Horizontal">
  37. <TextBlock Text="是否可用" VerticalAlignment="Center" Style="{StaticResource PhoneTextAccentStyle}"/>
  38. <CheckBox Name="ResourceIntensiveCheckBox" IsChecked="{Binding IsEnabled}" Checked="ResourceIntensiveCheckBox_Checked" Unchecked="ResourceIntensiveCheckBox_Unchecked"/>
  39. </StackPanel>
  40. <StackPanel Orientation="Horizontal">
  41. <TextBlock Text="是否已计划: " Style="{StaticResource PhoneTextAccentStyle}"/>
  42. <TextBlock Text="{Binding IsScheduled}" />
  43. </StackPanel>
  44. <StackPanel Orientation="Horizontal">
  45. <TextBlock Text="上次计划运行时间: " Style="{StaticResource PhoneTextAccentStyle}"/>
  46. <TextBlock Text="{Binding LastScheduledTime}" />
  47. </StackPanel>
  48. <StackPanel Orientation="Horizontal">
  49. <TextBlock Text="计划结束时间: " Style="{StaticResource PhoneTextAccentStyle}"/>
  50. <TextBlock Text="{Binding ExpirationTime}" />
  51. </StackPanel>
  52. <StackPanel Orientation="Horizontal">
  53. <TextBlock Text="上一次代理运行退出的原因: " Style="{StaticResource PhoneTextAccentStyle}"/>
  54. <TextBlock Text="{Binding LastExitReason}" />
  55. </StackPanel>
  56. </StackPanel>
  57. </StackPanel>
  58. </Grid>

[C#]

  1. public partial class MainPage : PhoneApplicationPage
  2. {
  3. /// <summary>
  4. /// 定期代理
  5. /// </summary>
  6. PeriodicTask periodicTask;
  7.  
  8. /// <summary>
  9. /// 资源密集型代理
  10. /// </summary>
  11. ResourceIntensiveTask resourceIntensiveTask;
  12.  
  13. string periodicTaskName = "PeriodicAgent";
  14. string resourceIntensiveTaskName = "ResourceIntensiveAgent";
  15.  
  16. public bool agentsAreEnabled = true;
  17.  
  18. // 构造函数
  19. public MainPage()
  20. {
  21. InitializeComponent();
  22. }
  23. //启动定期代理
  24. private void StartPeriodicAgent()
  25. {
  26. agentsAreEnabled = true;
  27.  
  28. // 获取当前名称的定期代理,如果存在则移除
  29. periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;
  30. if (periodicTask != null)
  31. {
  32. RemoveAgent(periodicTaskName);
  33. }
  34.  
  35. periodicTask = new PeriodicTask(periodicTaskName);
  36. periodicTask.Description = "这是一个定期代理的描述信息。";
  37. try
  38. {
  39. ScheduledActionService.Add(periodicTask);
  40. PeriodicStackPanel.DataContext = periodicTask;
  41.  
  42. //在调试的时候需要及时执行查看效果
  43. #if(DEBUG_AGENT)
  44. ScheduledActionService.LaunchForTest(periodicTaskName, TimeSpan.FromSeconds(60));
  45. #endif
  46. }
  47. catch (InvalidOperationException exception)
  48. {
  49. if (exception.Message.Contains("BNS Error: The action is disabled"))
  50. {
  51. MessageBox.Show("本应用的后台计划被用户禁用。");
  52. agentsAreEnabled = false;
  53. PeriodicCheckBox.IsChecked = false;
  54. }
  55.  
  56. if (exception.Message.Contains("BNS Error: The maximum number of ScheduledActions of this type have already been added."))
  57. {
  58. MessageBox.Show("定期代理数量达到最大限制。");
  59. }
  60. PeriodicCheckBox.IsChecked = false;
  61. }
  62. catch (SchedulerServiceException)
  63. {
  64. PeriodicCheckBox.IsChecked = false;
  65. }
  66. }
  67.  
  68. private void StartResourceIntensiveAgent()
  69. {
  70. agentsAreEnabled = true;
  71.  
  72. // 获取当前名称的资源密集型代理,如果存在则移除
  73. resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask;
  74. if (resourceIntensiveTask != null)
  75. {
  76. RemoveAgent(resourceIntensiveTaskName);
  77. }
  78.  
  79. resourceIntensiveTask = new ResourceIntensiveTask(resourceIntensiveTaskName);
  80. resourceIntensiveTask.Description = "这是一个资源密集型代理的描述信息。";
  81.  
  82. try
  83. {
  84. ScheduledActionService.Add(resourceIntensiveTask);
  85. ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask;
  86.  
  87. //在调试的时候需要及时执行查看效果
  88. #if(DEBUG_AGENT)
  89. ScheduledActionService.LaunchForTest(resourceIntensiveTaskName, TimeSpan.FromSeconds(15));
  90. #endif
  91. }
  92. catch (InvalidOperationException exception)
  93. {
  94. if (exception.Message.Contains("BNS Error: The action is disabled"))
  95. {
  96. MessageBox.Show("本应用的后台计划被用户禁用。");
  97. agentsAreEnabled = false;
  98.  
  99. }
  100. ResourceIntensiveCheckBox.IsChecked = false;
  101. }
  102. catch (SchedulerServiceException)
  103. {
  104. ResourceIntensiveCheckBox.IsChecked = false;
  105. }
  106. }
  107.  
  108. bool ignoreCheckBoxEvents = false;
  109. private void PeriodicCheckBox_Checked(object sender, RoutedEventArgs e)
  110. {
  111. if (ignoreCheckBoxEvents)
  112. return;
  113. StartPeriodicAgent();
  114. }
  115. private void PeriodicCheckBox_Unchecked(object sender, RoutedEventArgs e)
  116. {
  117. if (ignoreCheckBoxEvents)
  118. return;
  119. RemoveAgent(periodicTaskName);
  120. }
  121. private void ResourceIntensiveCheckBox_Checked(object sender, RoutedEventArgs e)
  122. {
  123. if (ignoreCheckBoxEvents)
  124. return;
  125. StartResourceIntensiveAgent();
  126. }
  127. private void ResourceIntensiveCheckBox_Unchecked(object sender, RoutedEventArgs e)
  128. {
  129. if (ignoreCheckBoxEvents)
  130. return;
  131. RemoveAgent(resourceIntensiveTaskName);
  132. }
  133.  
  134. /// <summary>
  135. /// 删除代理
  136. /// </summary>
  137. private void RemoveAgent(string name)
  138. {
  139. try
  140. {
  141. ScheduledActionService.Remove(name);
  142. }
  143. catch (Exception)
  144. {
  145. }
  146. }
  147.  
  148. protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  149. {
  150. ignoreCheckBoxEvents = true;
  151.  
  152. periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;
  153.  
  154. if (periodicTask != null)
  155. {
  156. PeriodicStackPanel.DataContext = periodicTask;
  157. }
  158.  
  159. resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask;
  160. if (resourceIntensiveTask != null)
  161. {
  162. ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask;
  163. }
  164.  
  165. ignoreCheckBoxEvents = false;
  166.  
  167. }
  168.  
  169. }

三、后台音频

通过后台音频的功能我们可以实现在系统后台播放音乐的功能,由于后台音频代理只能访问本地文件夹,所以我们务必要先把需要播放的音乐文件拷贝到本地文件夹中。本示例是把安装文件夹的音频文件拷贝到本地文件夹,代码如下:

[C#]

  1. //把安装文件夹下的文件拷贝到本地文件夹
  2. private void CopyToIsolatedStorage()
  3. {
  4. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  5. {
  6. string[] files = new string[] { "Ring01.wma", "Ring02.wma", "Ring03.wma" };
  7.  
  8. foreach (var _fileName in files)
  9. {
  10. if (!storage.FileExists(_fileName))
  11. {
  12. string _filePath = "Audio/" + _fileName;
  13. StreamResourceInfo resource = Application.GetResourceStream(new Uri(_filePath, UriKind.Relative));
  14.  
  15. using (IsolatedStorageFileStream file = storage.CreateFile(_fileName))
  16. {
  17. int chunkSize = 4096;
  18. byte[] bytes = new byte[chunkSize];
  19. int byteCount;
  20.  
  21. while ((byteCount = resource.Stream.Read(bytes, 0, chunkSize)) > 0)
  22. {
  23. file.Write(bytes, 0, byteCount);
  24. }
  25. }
  26. }
  27. }
  28.  
  29. string[] icons = new string[] { "Ring01.jpg", "Ring02.jpg", "Ring03.jpg" };
  30.  
  31. foreach (var _fileName in icons)
  32. {
  33. if (!storage.FileExists(_fileName))
  34. {
  35. string _filePath = "Images/" + _fileName;
  36. StreamResourceInfo iconResource = Application.GetResourceStream(new Uri(_filePath, UriKind.Relative));
  37.  
  38. using (IsolatedStorageFileStream file = storage.CreateFile( _fileName))
  39. {
  40. int chunkSize = 4096;
  41. byte[] bytes = new byte[chunkSize];
  42. int byteCount;
  43.  
  44. while ((byteCount = iconResource.Stream.Read(bytes, 0, chunkSize)) > 0)
  45. {
  46. file.Write(bytes, 0, byteCount);
  47. }
  48. }
  49. }
  50. }
  51.  
  52. }
  53. }

我们需要在解决方案中添加Windows phone 音频播放代理项目,并在应用项目中添加对其的引用。修改AudioPlayer.cs代码如下:

[C#]

  1. public class AudioPlayer : AudioPlayerAgent
  2. {
  3. private static volatile bool _classInitialized;
  4.  
  5. private static List<AudioTrack> _playList = new List<AudioTrack>
  6. {
  7. new AudioTrack(new Uri("Ring01.wma", UriKind.Relative),"曲目1","艺术家1","专辑1",new Uri("Ring01.jpg", UriKind.Relative)),
  8. new AudioTrack(new Uri("Ring02.wma", UriKind.Relative),"曲目2","艺术家2","专辑2",new Uri("Ring02.jpg", UriKind.Relative)),
  9. new AudioTrack(new Uri("Ring03.wma", UriKind.Relative),"曲目3","艺术家3","专辑3",new Uri("Ring03.jpg", UriKind.Relative))
  10. };
  11.  
  12. /// <summary>
  13. /// 当前播放位置
  14. /// </summary>
  15. static int currentTrackNumber = 0;
  16.  
  17. /// <remarks>
  18. /// AudioPlayer 实例可共享同一进程。
  19. /// 静态字段可用于在 AudioPlayer 实例之间共享状态
  20. /// 或与音频流代理通信。
  21. /// </remarks>
  22. public AudioPlayer()
  23. {
  24. if (!_classInitialized)
  25. {
  26. _classInitialized = true;
  27. // 订阅托管异常处理程序
  28. Deployment.Current.Dispatcher.BeginInvoke(delegate
  29. {
  30. Application.Current.UnhandledException += AudioPlayer_UnhandledException;
  31. });
  32. }
  33. }
  34.  
  35. /// 出现未处理的异常时执行的代码
  36. private void AudioPlayer_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
  37. {
  38. if (System.Diagnostics.Debugger.IsAttached)
  39. {
  40. // 出现未处理的异常;强行进入调试器
  41. System.Diagnostics.Debugger.Break();
  42. }
  43. }
  44.  
  45. /// <summary>
  46. /// playstate 更改时调用,但 Error 状态除外(参见 OnError)
  47. /// </summary>
  48. /// <param name="player">BackgroundAudioPlayer</param>
  49. /// <param name="track">在 playstate 更改时播放的曲目</param>
  50. /// <param name="playState">播放机的新 playstate </param>
  51. /// <remarks>
  52. /// 无法取消播放状态更改。即使应用程序
  53. /// 导致状态自行更改,假定应用程序已经选择了回调。
  54. ///
  55. /// 值得注意的 playstate 事件
  56. /// (a) TrackEnded: 播放器没有当前曲目时激活。代理可设置下一曲目。
  57. /// (b) TrackReady: 音轨已设置完毕,现在可以播放。
  58. ///
  59. /// 只在代理请求完成之后调用一次 NotifyComplete(),包括异步回调。
  60. /// </remarks>
  61. protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState)
  62. {
  63. switch (playState)
  64. {
  65. case PlayState.TrackEnded:
  66. player.Track = GetPreviousTrack();
  67. break;
  68. case PlayState.TrackReady:
  69. player.Play();
  70. break;
  71. case PlayState.Shutdown:
  72. // TODO: 在此处理关机状态(例如保存状态)
  73. break;
  74. case PlayState.Unknown:
  75. break;
  76. case PlayState.Stopped:
  77. break;
  78. case PlayState.Paused:
  79. break;
  80. case PlayState.Playing:
  81. break;
  82. case PlayState.BufferingStarted:
  83. break;
  84. case PlayState.BufferingStopped:
  85. break;
  86. case PlayState.Rewinding:
  87. break;
  88. case PlayState.FastForwarding:
  89. break;
  90. }
  91.  
  92. NotifyComplete();
  93. }
  94.  
  95. /// <summary>
  96. /// 在用户使用应用程序/系统提供的用户界面请求操作时调用
  97. /// </summary>
  98. /// <param name="player">BackgroundAudioPlayer</param>
  99. /// <param name="track">用户操作期间播放的曲目</param>
  100. /// <param name="action">用户请求的操作</param>
  101. /// <param name="param">与请求的操作相关联的数据。
  102. /// 在当前版本中,此参数仅适合与 Seek 操作一起使用,
  103. /// 以指明请求的乐曲的位置</param>
  104. /// <remarks>
  105. /// 用户操作不自动对系统状态进行任何更改;如果用户操作受支持,
  106. /// 执行用户操作(如果这些操作受支持)。
  107. ///
  108. /// 只在代理请求完成之后调用一次 NotifyComplete(),包括异步回调。
  109. /// </remarks>
  110. protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param)
  111. {
  112. switch (action)
  113. {
  114. case UserAction.Play:
  115. if (player.PlayerState != PlayState.Playing)
  116. {
  117. player.Play();
  118. }
  119. break;
  120. case UserAction.Stop:
  121. player.Stop();
  122. break;
  123. case UserAction.Pause:
  124. player.Pause();
  125. break;
  126. case UserAction.FastForward:
  127. player.FastForward();
  128. break;
  129. case UserAction.Rewind:
  130. player.Rewind();
  131. break;
  132. case UserAction.Seek:
  133. player.Position = (TimeSpan)param;
  134. break;
  135. case UserAction.SkipNext:
  136. player.Track = GetNextTrack();
  137. break;
  138. case UserAction.SkipPrevious:
  139. AudioTrack previousTrack = GetPreviousTrack();
  140. if (previousTrack != null)
  141. {
  142. player.Track = previousTrack;
  143. }
  144. break;
  145. }
  146.  
  147. NotifyComplete();
  148. }
  149.  
  150. /// <summary>
  151. /// 实现逻辑以获取下一个 AudioTrack 实例。
  152. /// 在播放列表中,源可以是文件、Web 请求,等等。
  153. /// </summary>
  154. /// <remarks>
  155. /// AudioTrack URI 确定源,它可以是:
  156. /// (a) 独立存储器文件(相对 URI,表示独立存储器中的路径)
  157. /// (b) HTTP URL(绝对 URI)
  158. /// (c) MediaStreamSource (null)
  159. /// </remarks>
  160. /// <returns>AudioTrack 实例,或如果播放完毕,则返回 null</returns>
  161. private AudioTrack GetNextTrack()
  162. {
  163. // TODO: 添加逻辑以获取下一条音轨
  164. if (++currentTrackNumber >= _playList.Count) currentTrackNumber = 0;
  165. AudioTrack track = _playList[currentTrackNumber];
  166.  
  167. // 指定曲目
  168.  
  169. return track;
  170. }
  171.  
  172. /// <summary>
  173. /// 实现逻辑以获取前一个 AudioTrack 实例。
  174. /// </summary>
  175. /// <remarks>
  176. /// AudioTrack URI 确定源,它可以是:
  177. /// (a) 独立存储器文件(相对 URI,表示独立存储器中的路径)
  178. /// (b) HTTP URL(绝对 URI)
  179. /// (c) MediaStreamSource (null)
  180. /// </remarks>
  181. /// <returns>AudioTrack 实例,或如果不允许前一曲目,则返回 null</returns>
  182. private AudioTrack GetPreviousTrack()
  183. {
  184. // TODO: 添加逻辑以获取前一条音轨
  185. if (--currentTrackNumber < 0) currentTrackNumber = _playList.Count - 1;
  186. AudioTrack track = _playList[currentTrackNumber];
  187.  
  188. // 指定曲目
  189.  
  190. return track;
  191. }
  192.  
  193. /// <summary>
  194. /// 每次播放出错(如 AudioTrack 未正确下载)时调用
  195. /// </summary>
  196. /// <param name="player">BackgroundAudioPlayer</param>
  197. /// <param name="track">出现错误的曲目</param>
  198. /// <param name="error">出现的错误</param>
  199. /// <param name="isFatal">如果为 true,则播放不能继续并且曲目播放将停止</param>
  200. /// <remarks>
  201. /// 不保证在所有情况下都调用此方法。例如,如果后台代理程序
  202. /// 本身具有未处理的异常,则不会回调它来处理它自己的错误。
  203. /// </remarks>
  204. protected override void OnError(BackgroundAudioPlayer player, AudioTrack track, Exception error, bool isFatal)
  205. {
  206. if (isFatal)
  207. {
  208. Abort();
  209. }
  210. else
  211. {
  212. NotifyComplete();
  213. }
  214.  
  215. }
  216.  
  217. /// <summary>
  218. /// 取消代理请求时调用
  219. /// </summary>
  220. /// <remarks>
  221. /// 取消请求后,代理需要 5 秒钟完成其工作,
  222. /// 通过调用 NotifyComplete()/Abort()。
  223. /// </remarks>
  224. protected override void OnCancel()
  225. {
  226.  
  227. }
  228. }

最后,我们在mainpage中添加对播放的控制。

[XAML]

  1. <Grid x:Name="LayoutRoot" Background="Transparent">
  2. <Grid.RowDefinitions>
  3. <RowDefinition Height="Auto"/>
  4. <RowDefinition Height="*"/>
  5. </Grid.RowDefinitions>
  6.  
  7. <!--TitlePanel 包含应用程序的名称和页标题-->
  8. <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
  9. <TextBlock x:Name="ApplicationTitle" Text="后台音频" Style="{StaticResource PhoneTextNormalStyle}"/>
  10. </StackPanel>
  11.  
  12. <!--ContentPanel - 在此处放置其他内容-->
  13. <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  14. <Button x:Name="button2" Content="〈" HorizontalAlignment="Left" Margin="13,10,0,0" VerticalAlignment="Top" Click="Button_Click_2"/>
  15. <Button x:Name="button1" Content="▶" HorizontalAlignment="Left" Margin="75,10,0,0" VerticalAlignment="Top" Click="Button_Click_1"/>
  16. <Button x:Name="button3" Content="〉" HorizontalAlignment="Left" Margin="136,10,0,0" VerticalAlignment="Top" Click="Button_Click_3" />
  17. <TextBlock x:Name="textblock1" HorizontalAlignment="Left" Margin="22,87,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
  18. <Image x:Name="imge1" HorizontalAlignment="Left" Height="100" Margin="22,142,0,0" VerticalAlignment="Top" Width="100"/>
  19.  
  20. </Grid>
  21. </Grid>

[C#]

  1. public partial class MainPage : PhoneApplicationPage
  2. {
  3. // 构造函数
  4. public MainPage()
  5. {
  6. InitializeComponent();
  7. BackgroundAudioPlayer.Instance.PlayStateChanged += new EventHandler(Instance_PlayStateChanged);
  8. }
  9.  
  10. //刚加载时确定播放状态
  11. protected override void OnNavigatedTo(NavigationEventArgs e)
  12. {
  13. if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState)
  14. {
  15. button1.Content = "■";
  16. textblock1.Text = "曲目:" + BackgroundAudioPlayer.Instance.Track.Title
  17. + " 艺术家:" + BackgroundAudioPlayer.Instance.Track.Artist
  18. + " 专辑:" + BackgroundAudioPlayer.Instance.Track.Album
  19. + " 曲目长度:" +BackgroundAudioPlayer.Instance.Track.Duration.Minutes + ":" + BackgroundAudioPlayer.Instance.Track.Duration.Seconds;
  20.  
  21. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  22. {
  23. var stream = storage.OpenFile(BackgroundAudioPlayer.Instance.Track.AlbumArt.OriginalString, System.IO.FileMode.Open);
  24. var bitmapImage = new BitmapImage();
  25. bitmapImage.SetSource(stream);
  26. imge1.Source = bitmapImage;
  27. stream.Close();
  28. }
  29. }
  30. else
  31. {
  32. button1.Content = "▶";
  33. textblock1.Text = "未播放曲目";
  34. }
  35. }
  36.  
  37. void Instance_PlayStateChanged(object sender, EventArgs e)
  38. {
  39. switch (BackgroundAudioPlayer.Instance.PlayerState)
  40. {
  41. case PlayState.Playing:
  42. button1.Content = "■";
  43. button2.IsEnabled = true;
  44. button3.IsEnabled = true;
  45. break;
  46.  
  47. case PlayState.Paused:
  48. case PlayState.Stopped:
  49. button1.Content = "▶";
  50. break;
  51. }
  52.  
  53. if (null != BackgroundAudioPlayer.Instance.Track && BackgroundAudioPlayer.Instance.PlayerState!= PlayState.Stopped)
  54. {
  55. textblock1.Text = "曲目:" + BackgroundAudioPlayer.Instance.Track.Title
  56. + " 艺术家:" + BackgroundAudioPlayer.Instance.Track.Artist
  57. + " 专辑:" + BackgroundAudioPlayer.Instance.Track.Album
  58. + " 曲目长度:" + BackgroundAudioPlayer.Instance.Track.Duration.Minutes + ":" + BackgroundAudioPlayer.Instance.Track.Duration.Seconds;
  59.  
  60. using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
  61. {
  62. var stream = storage.OpenFile(BackgroundAudioPlayer.Instance.Track.AlbumArt.OriginalString, System.IO.FileMode.Open);
  63. var bitmapImage = new BitmapImage();
  64. bitmapImage.SetSource(stream);
  65. imge1.Source = bitmapImage;
  66. stream.Close();
  67. }
  68. }
  69. }
  70.  
  71. //播放/暂停
  72. private void Button_Click_1(object sender, RoutedEventArgs e)
  73. {
  74. if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState)
  75. BackgroundAudioPlayer.Instance.Pause();
  76. else
  77. BackgroundAudioPlayer.Instance.Play();
  78. }
  79.  
  80. //向前
  81. private void Button_Click_2(object sender, RoutedEventArgs e)
  82. {
  83. BackgroundAudioPlayer.Instance.SkipPrevious();
  84. button2.IsEnabled = false;
  85. }
  86. //向后
  87. private void Button_Click_3(object sender, RoutedEventArgs e)
  88. {
  89. BackgroundAudioPlayer.Instance.SkipNext();
  90. button3.IsEnabled = false;
  91. }
  92. }

四、后台文件传输

后台文件传输允许我们实现下载上传文件的功能,他限制系统中同时运行的传输任务不能超过两个,并且下载的文件只能存放在本地文件夹的/shared/transfers目录下。下面我们实现一个后台传输任务,下载博客相册中的一张照片。

[XAML]

  1. <Grid x:Name="LayoutRoot" Background="Transparent">
  2. <Grid.RowDefinitions>
  3. <RowDefinition Height="Auto"/>
  4. <RowDefinition Height="*"/>
  5. </Grid.RowDefinitions>
  6.  
  7. <!--TitlePanel 包含应用程序的名称和页标题-->
  8. <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
  9. <TextBlock x:Name="ApplicationTitle" Text="后台传输" Style="{StaticResource PhoneTextNormalStyle}"/>
  10. </StackPanel>
  11.  
  12. <!--ContentPanel - 在此处放置其他内容-->
  13. <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  14. <TextBlock x:Name="textblock1" HorizontalAlignment="Left" Margin="10,198,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
  15. <Button Content="清除传输队列中已完成的任务" HorizontalAlignment="Left" Margin="0,85,0,0" VerticalAlignment="Top" Click="Button_Click_2"/>
  16. </Grid>
  17. <Button x:Name="button1" Content="添加一个后台传输" HorizontalAlignment="Left" Margin="12,10,0,0" Grid.Row="1" VerticalAlignment="Top" Click="Button_Click_1"/>
  18. </Grid>

[C#]

  1. public partial class MainPage : PhoneApplicationPage
  2. {
  3. // 构造函数
  4. public MainPage()
  5. {
  6. InitializeComponent();
  7. }
  8.  
  9. protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  10. {
  11.  
  12. initTransferRequest();
  13. base.OnNavigatedTo(e);
  14. }
  15.  
  16. private void initTransferRequest()
  17. {
  18. //获取第一个后台传输任务
  19. var transferRequest = BackgroundTransferService.Requests.FirstOrDefault();
  20. if (transferRequest == null)
  21. {
  22. textblock1.Text = "无后台传输任务";
  23. button1.IsEnabled = true;
  24. return;
  25. }
  26.  
  27. //当传输状态改变时:
  28. transferRequest.TransferStatusChanged += new EventHandler<BackgroundTransferEventArgs>(transfer_TransferStatusChanged);
  29. //当传输进度改变时:
  30. transferRequest.TransferProgressChanged += new EventHandler<BackgroundTransferEventArgs>(transfer_TransferProgressChanged);
  31. updatesStatus(transferRequest);
  32. button1.IsEnabled = false;
  33. }
  34.  
  35. void transfer_TransferStatusChanged(object sender, BackgroundTransferEventArgs e)
  36. {
  37. updatesStatus(e.Request);
  38. }
  39.  
  40. void transfer_TransferProgressChanged(object sender, BackgroundTransferEventArgs e)
  41. {
  42. updatesStatus(e.Request);
  43. }
  44.  
  45. void updatesStatus(BackgroundTransferRequest transferRequest)
  46. {
  47. textblock1.Text = "传输状态:" + transferRequest.TransferStatus.ToString()
  48. + " 已下载字节:" + transferRequest.BytesReceived
  49. + "总字节:" + transferRequest.TotalBytesToReceive;
  50. }
  51.  
  52. private void Button_Click_1(object sender, RoutedEventArgs e)
  53. {
  54. string fileurlstring = "http://images.cnblogs.com/cnblogs_com/lipan/319399/o_Large.png";
  55. Uri uri = new Uri(Uri.EscapeUriString(fileurlstring), UriKind.RelativeOrAbsolute);
  56. BackgroundTransferRequest transferRequest = new BackgroundTransferRequest(uri);
  57. transferRequest.Method = "GET";
  58.  
  59. using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
  60. {
  61. if (!isoStore.DirectoryExists("/shared/transfers"))
  62. {
  63. isoStore.CreateDirectory("/shared/transfers");
  64. }
  65. }
  66.  
  67. //文件下载后存放位置(为本地文件夹相对位置)
  68. transferRequest.DownloadLocation = new Uri("shared/transfers/1.png", UriKind.RelativeOrAbsolute);
  69. //外接电源、WiFi的可用性对传输的影响
  70. transferRequest.TransferPreferences = TransferPreferences.AllowCellularAndBattery;
  71.  
  72. try
  73. {
  74. //添加到后台传输队列中
  75. BackgroundTransferService.Add(transferRequest);
  76. }
  77. catch (Exception ex)
  78. {
  79. MessageBox.Show("无法添加后台传输请求。" + ex.Message);
  80. }
  81. initTransferRequest();
  82. }
  83.  
  84. //清除传输队列已完成的任务
  85. private void Button_Click_2(object sender, RoutedEventArgs e)
  86. {
  87. foreach (var transferRequest in BackgroundTransferService.Requests)
  88. {
  89. if (transferRequest.TransferStatus == TransferStatus.Completed)
  90. {
  91. try
  92. {
  93. BackgroundTransferService.Remove(transferRequest);
  94. }
  95. catch
  96. {
  97. }
  98. }
  99. }
  100. initTransferRequest();
  101. }
  102.  
  103. }

五、后台辅助线程

后台辅助线程虽然名字这么叫,但是它不能在后台运行,我们可以用它来执行一个任务,并且可以实时获取执行的进度,实现代码如下:

[XAML]

  1. <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  2. <StackPanel>
  3. <StackPanel Orientation="Horizontal"
  4. HorizontalAlignment="Left" VerticalAlignment="Top"
  5. Margin="10" >
  6. <Button x:Name="buttonStart" Content="开始" Click="buttonStart_Click"
  7. Width="200" />
  8. <Button x:Name="buttonCancel" Content="取消" Click="buttonCancel_Click"
  9. Width="200" />
  10. </StackPanel>
  11. <StackPanel Margin="10,50,0,0" Orientation="Horizontal">
  12. <TextBlock Text="进度: " />
  13. <TextBlock x:Name="tbProgress" />
  14. </StackPanel>
  15. </StackPanel>
  16.  
  17. </Grid>

[C#]

  1. public partial class MainPage : PhoneApplicationPage
  2. {
  3.  
  4. private BackgroundWorker bw = new BackgroundWorker();
  5.  
  6. public MainPage()
  7. {
  8. InitializeComponent();
  9.  
  10. bw.WorkerReportsProgress = true;
  11. bw.WorkerSupportsCancellation = true;
  12. bw.DoWork += new DoWorkEventHandler(bw_DoWork);
  13. bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
  14. bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
  15. }
  16. private void buttonStart_Click(object sender, RoutedEventArgs e)
  17. {
  18. if (bw.IsBusy != true)
  19. {
  20. bw.RunWorkerAsync();
  21. }
  22. }
  23. private void buttonCancel_Click(object sender, RoutedEventArgs e)
  24. {
  25. if (bw.WorkerSupportsCancellation == true)
  26. {
  27. bw.CancelAsync();
  28. }
  29. }
  30. private void bw_DoWork(object sender, DoWorkEventArgs e)
  31. {
  32. BackgroundWorker worker = sender as BackgroundWorker;
  33.  
  34. for (int i = 1; i <= 10; i++)
  35. {
  36. if ((worker.CancellationPending == true))
  37. {
  38. e.Cancel = true;
  39. break;
  40. }
  41. else
  42. {
  43. // Perform a time consuming operation and report progress.
  44. System.Threading.Thread.Sleep(500);
  45. worker.ReportProgress(i * 10);
  46. }
  47. }
  48. }
  49. private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  50. {
  51. if (e.Cancelled == true)
  52. {
  53. this.tbProgress.Text = "Canceled!";
  54. }
  55.  
  56. else if (!(e.Error == null))
  57. {
  58. this.tbProgress.Text = ("Error: " + e.Error.Message);
  59. }
  60.  
  61. else
  62. {
  63. this.tbProgress.Text = "Done!";
  64. }
  65. }
  66. private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
  67. {
  68. this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
  69. }
  70. }

Windows phone 8 学习笔记(6) 多任务(转)的更多相关文章

  1. Windows phone 8 学习笔记(6) 多任务

    原文:Windows phone 8 学习笔记(6) 多任务 Windows phone 8 是一个单任务操作系统,任何时候都只有一个应用处于活跃状态,这里的多任务是指对后台任务的支持.本节我们先讲讲 ...

  2. Windows phone 8 学习笔记

    Windows phone 8 学习笔记(1) 触控输入  http://www.apkbus.com/android-138547-1-1.html Windows phone 8 学习笔记(2) ...

  3. Windows phone 8 学习笔记(5) 图块与通知(转)

    基于metro风格的Windows phone 8 应用提到了图块的概念,它就是指启动菜单中的快速启动图标.一般一个应用必须有一个默认图块,还可以有若干个次要图块.另外,通知与图块的关系比较密切,我们 ...

  4. Windows phone 8 学习笔记(7) 设备

    原文:Windows phone 8 学习笔记(7) 设备 本节主要涉及到 Windows phone 8 手机支持的各类设备,包括相机.设备状态,振动装置等.还有各类感应器,包括磁力计.加速度器和陀 ...

  5. Windows phone 8 学习笔记(5) 图块与通知

    原文:Windows phone 8 学习笔记(5) 图块与通知 基于metro风格的Windows phone 8 应用提到了图块的概念,它就是指启动菜单中的快速启动图标.一般一个应用必须有一个默认 ...

  6. Windows phone 8 学习笔记(2) 数据文件操作(转)

    Windows phone 8 应用用于数据文件存储访问的位置仅仅限于安装文件夹.本地文件夹(独立存储空间).媒体库和SD卡四个地方.本节主要讲解它们的用法以及相关限制性.另外包括本地数据库的使用方式 ...

  7. Windows phone 8 学习笔记(8) 定位地图导航

    原文:Windows phone 8 学习笔记(8) 定位地图导航 Windows phone 8 已经不使用自家的bing地图,新地图控件可以指定制图模式.视图等.bing地图的定位误差比较大,在模 ...

  8. Windows phone 8 学习笔记(9) 集成

    原文:Windows phone 8 学习笔记(9) 集成 本节整理了之前并没有提到的Windows phone 8 系统相关集成支持,包括选择器.锁定屏幕的.联系人的访问等.选择器列举了若干内置应用 ...

  9. Windows phone 8 学习笔记(2) 数据文件操作

    原文:Windows phone 8 学习笔记(2) 数据文件操作 Windows phone 8 应用用于数据文件存储访问的位置仅仅限于安装文件夹.本地文件夹(独立存储空间).媒体库和SD卡四个地方 ...

随机推荐

  1. CE_现金银行对账单的手工导入和调节(案例)

    2014-07-14 Created By BaoXinjian

  2. T4批量生成多文件

    http://www.cnblogs.com/zengxiangzhan/p/3250105.html Manager.ttinclude <#@ assembly name="Sys ...

  3. 基于jquery的表单校验插件 - formvalidator使用体验

    下载地址:http://www.formvalidator.net/ 基本样例 <form action="/registration" method="POST& ...

  4. 通过Application传递数据代码

    使用Application传递数据步骤如下:创建新class,取名MyApp,继承android.app.Application父类,并在MyApp中定义需要保存的属性     在整个Android程 ...

  5. Maven详解之仓库------本地仓库、远程仓库

    在Maven中,任何一个依赖.插件或者项目构建的输出,都可以称之为构件. Maven在某个统一的位置存储所有项目的共享的构件,这个统一的位置,我们就称之为仓库.(仓库就是存放依赖和插件的地方) 任何的 ...

  6. chrome调试js工具的使用

    Audits标签页 这个对于优化前端页面.加速网页加载速度很有用哦(相当与Yslow): 点击run按钮,就可以开始分析页面,分析完了就可以看到分析结果了: 它甚至可以分析出页面上样式表中有哪些CSS ...

  7. 配置FileZilla Ftp服务器

    FileZilla是我比较喜欢用的一款FTP服务端和客户端,主要使用在Windows下,她是一款开源的FTP软件,虽然在某些功能上比不上收费软件Ser-u,但她也是一款非常好用的软件,这里主要说一下这 ...

  8. C++学习47 文件的概念 文件流类与文件流对象 文件的打开与关闭

    迄今为止,我们讨论的输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的.在实际应用中,常以磁盘文件作为对象.即从磁盘文件读取数据,将数据输出到磁盘文件.磁盘是计算机的外部存储器 ...

  9. C++学习41 exception类

    C++语言本身或者标准库抛出的异常都是 exception 的子类,称为标准异常(Standard Exception).你可以通过下面的语句来匹配所有标准异常: try{ //可能抛出异常的语句 } ...

  10. photoshop CS 调整选择区域的大小

    网上看到说:矩形选框不能直接调整大小,如果你不想重新画一个可以利用转换路径,然后再调整.这是不对的,矩形选框是可以调整大小的,使用"变换选区"即可.   对应步骤截图如下: 1.画 ...