Windows phone 8 学习笔记(6) 多任务
Windows phone 8 是一个单任务操作系统,任何时候都只有一个应用处于活跃状态,这里的多任务是指对后台任务的支持。本节我们先讲讲应用程序的运行状态,然后看看支持的后台任务,包括:后台代理、后台音频、后台文件传输、后台辅助线程等。
快速导航:
一、应用的状态
二、后台代理
三、后台音频
四、后台文件传输
五、后台辅助线程
一、应用的状态
1)应用的运行状态
我们通过图解来分析应用的运行状态,启动并置于前台界面的应用是唯一处于运行状态的,其他的操作,比如win键,后退导出应用,打开选择器和启动器时都会让当前运行的应用进入休眠状态,如果系统内存不足,处于休眠状态的应用可能会被系统逻辑删除。下面的图示演示了这个过程。
2)如何恢复状态
当应用处于休眠状态时,它的状态信息仍然保留在内存中,用户下次切换进去后不会有任何变化。但是当应用被逻辑删除后,这些状态信息就会丢失,比如表单填写的内容都会消失,为了避免这种情况,我们需要手动保留状态信息。
首先,我们在mainpage定义一些页面表单控件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | < Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> < TextBox Text="{Binding TextBox1Text, Mode=TwoWay}" Height="72" HorizontalAlignment="Left" Margin="20,20,0,0" Name="textBox1" VerticalAlignment="Top" Width="440" /> < CheckBox IsChecked="{Binding CheckBox1IsChecked, Mode=TwoWay}" Content="CheckBox" Height="71" Name="checkBox1" Margin="20,100,0,0" VerticalAlignment="Top"/> < Slider Value="{Binding Slider1Value, Mode=TwoWay}" Height="84" Name="slider1" Width="440" Margin="20,180,0,0" VerticalAlignment="Top"/> < RadioButton IsChecked="{Binding RadioButton1IsChecked, Mode=TwoWay}" Content="RadioButton 1" Height="71" Name="radioButton1" GroupName="RadioButtonGroup" Margin="20,260,0,0" VerticalAlignment="Top"/> < RadioButton IsChecked="{Binding RadioButton1IsChecked, Mode=TwoWay}" Content="RadioButton 2" Height="71" Name="radioButton2" GroupName="RadioButtonGroup" Margin="20,340,0,0" VerticalAlignment="Top"/> </ Grid > |
我们需要实现在应用逻辑删除后能将其状态保持到页面的State字典中,但是需要我们的数据源支持序列化,所以我们定义与表单关联的ViewModel如下:
[C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | [DataContract] public class ViewModel : INotifyPropertyChanged { private string _textBox1Text; private bool _checkBox1IsChecked; private bool _radioButton1IsChecked; private bool _radioButton2IsChecked; private double _slider1Value; [DataMember] public string TextBox1Text { get { return _textBox1Text; } set { _textBox1Text = value; NotifyPropertyChanged( "TextBox1Text" ); } } [DataMember] public bool CheckBox1IsChecked { get { return _checkBox1IsChecked; } set { _checkBox1IsChecked = value; NotifyPropertyChanged( "CheckBox1IsChecked" ); } } [DataMember] public double Slider1Value { get { return _slider1Value; } set { _slider1Value = value; NotifyPropertyChanged( "Slider1Value" ); } } [DataMember] public bool RadioButton1IsChecked { get { return _radioButton1IsChecked; } set { _radioButton1IsChecked = value; NotifyPropertyChanged( "RadioButton1IsChecked" ); } } [DataMember] public bool RadioButton2IsChecked { get { return _radioButton2IsChecked; } set { _radioButton2IsChecked = value; NotifyPropertyChanged( "RadioButton2IsChecked" ); } } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged( string propertyName) { if ( null != PropertyChanged) PropertyChanged( this , new PropertyChangedEventArgs(propertyName)); } } |
我需要对mainpage代码添加页面导航入、导航出的事件。导航出页面的时候,如果不是向后导航,则存储状态。导航入的时候,我们需要判断页面是否为逻辑删除后正在恢复的状态,如果是,则通过状态字典恢复状态。mainpage代码如下:
[C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | public partial class MainPage : PhoneApplicationPage { // 构造函数 public MainPage() { InitializeComponent(); _isNewPageInstance = true ; } ViewModel _viewModel = null ; /// <summary> /// 新实例还是现有实例 /// </summary> bool _isNewPageInstance = false ; private void Button_Click_1( object sender, RoutedEventArgs e) { NavigationService.Navigate( new Uri( "/Page1.xaml" , UriKind.Relative)); } protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { //如果不是向后导航,则保存状态 if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back) { State[ "ViewModel" ] = _viewModel; } } protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { if (_isNewPageInstance) { if (_viewModel == null ) { if (State.Count > 0) { _viewModel = (ViewModel)State[ "ViewModel" ]; } else { _viewModel = new ViewModel(); } } DataContext = _viewModel; } _isNewPageInstance = false ; } } |
然后我们添加一page1页面,该页添加一个返回按钮。用于测试。为了达到调试时即时进入逻辑删除的效果,我们需要设置下。右键项目文件,点属性,在调试选项卡勾选“在调试期间取消激活时逻辑删除”。
二、后台代理
后台代理可以在应用退出以后独立在系统后台运行,它包含两种类型的代理,分别是定期代理和资源密集型代理,前者用于频繁执行小任务,后者用于在系统空闲时执行耗时大任务。要使用后台代理,我们需要添加一个名为Windows phone 计划任务代理的项目,并在应用的项目中添加对其的引用,现在我们要实现在后台代理中弹出Toast,我们需要如下修改ScheduledAgent.cs的OnInvoke方法,代码如下
[C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | protected override void OnInvoke(ScheduledTask task) { string toastMessage = "" ; if (task is PeriodicTask) { toastMessage = "定期代理正在运行" ; } else { toastMessage = "资源密集型代理正在运行" ; } // 用于向用户显示Toast,如果当前任务的前台正在运行,则不显示 ShellToast toast = new ShellToast(); toast.Title = "标题" ; toast.Content = toastMessage; toast.Show(); // 在调试的时候需要及时执行查看效果 #if DEBUG_AGENT ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(15)); #endif NotifyComplete(); } |
接着,我们在应用项目的mainpage中调用代理,代码如下:
[XAML]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | < Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> < StackPanel > < StackPanel Orientation="Vertical" Name="PeriodicStackPanel" Margin="0,0,0,40"> < TextBlock Text="定期代理" Style="{StaticResource PhoneTextTitle2Style}"/> < StackPanel Orientation="Horizontal"> < TextBlock Text="名称: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding Name}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="是否可用" VerticalAlignment="Center" Style="{StaticResource PhoneTextAccentStyle}"/> < CheckBox Name="PeriodicCheckBox" IsChecked="{Binding IsEnabled}" Checked="PeriodicCheckBox_Checked" Unchecked="PeriodicCheckBox_Unchecked"/> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="是否已计划: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding IsScheduled}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="上次计划运行时间: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding LastScheduledTime}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="计划结束时间: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding ExpirationTime}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="上一次代理运行退出的原因: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding LastExitReason}" /> </ StackPanel > </ StackPanel > < StackPanel Orientation="Vertical" Name="ResourceIntensiveStackPanel" Margin="0,0,0,40"> < TextBlock Text="资源密集型代理" Style="{StaticResource PhoneTextTitle2Style}"/> < StackPanel Orientation="Horizontal"> < TextBlock Text="名称: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding Name}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="是否可用" VerticalAlignment="Center" Style="{StaticResource PhoneTextAccentStyle}"/> < CheckBox Name="ResourceIntensiveCheckBox" IsChecked="{Binding IsEnabled}" Checked="ResourceIntensiveCheckBox_Checked" Unchecked="ResourceIntensiveCheckBox_Unchecked"/> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="是否已计划: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding IsScheduled}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="上次计划运行时间: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding LastScheduledTime}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="计划结束时间: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding ExpirationTime}" /> </ StackPanel > < StackPanel Orientation="Horizontal"> < TextBlock Text="上一次代理运行退出的原因: " Style="{StaticResource PhoneTextAccentStyle}"/> < TextBlock Text="{Binding LastExitReason}" /> </ StackPanel > </ StackPanel > </ StackPanel > </ Grid > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | public partial class MainPage : PhoneApplicationPage { /// <summary> /// 定期代理 /// </summary> PeriodicTask periodicTask; /// <summary> /// 资源密集型代理 /// </summary> ResourceIntensiveTask resourceIntensiveTask; string periodicTaskName = "PeriodicAgent" ; string resourceIntensiveTaskName = "ResourceIntensiveAgent" ; public bool agentsAreEnabled = true ; // 构造函数 public MainPage() { InitializeComponent(); } //启动定期代理 private void StartPeriodicAgent() { agentsAreEnabled = true ; // 获取当前名称的定期代理,如果存在则移除 periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask; if (periodicTask != null ) { RemoveAgent(periodicTaskName); } periodicTask = new PeriodicTask(periodicTaskName); periodicTask.Description = "这是一个定期代理的描述信息。" ; try { ScheduledActionService.Add(periodicTask); PeriodicStackPanel.DataContext = periodicTask; //在调试的时候需要及时执行查看效果 #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( "本应用的后台计划被用户禁用。" ); agentsAreEnabled = false ; PeriodicCheckBox.IsChecked = false ; } if (exception.Message.Contains( "BNS Error: The maximum number of ScheduledActions of this type have already been added." )) { MessageBox.Show( "定期代理数量达到最大限制。" ); } PeriodicCheckBox.IsChecked = false ; } catch (SchedulerServiceException) { PeriodicCheckBox.IsChecked = false ; } } private void StartResourceIntensiveAgent() { agentsAreEnabled = true ; // 获取当前名称的资源密集型代理,如果存在则移除 resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask; if (resourceIntensiveTask != null ) { RemoveAgent(resourceIntensiveTaskName); } resourceIntensiveTask = new ResourceIntensiveTask(resourceIntensiveTaskName); resourceIntensiveTask.Description = "这是一个资源密集型代理的描述信息。" ; try { ScheduledActionService.Add(resourceIntensiveTask); ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask; //在调试的时候需要及时执行查看效果 #if(DEBUG_AGENT) ScheduledActionService.LaunchForTest(resourceIntensiveTaskName, TimeSpan.FromSeconds(15)); #endif } catch (InvalidOperationException exception) { if (exception.Message.Contains( "BNS Error: The action is disabled" )) { MessageBox.Show( "本应用的后台计划被用户禁用。" ); agentsAreEnabled = false ; } ResourceIntensiveCheckBox.IsChecked = false ; } catch (SchedulerServiceException) { ResourceIntensiveCheckBox.IsChecked = false ; } } bool ignoreCheckBoxEvents = false ; 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); } /// <summary> /// 删除代理 /// </summary> private void RemoveAgent( string name) { try { ScheduledActionService.Remove(name); } catch (Exception) { } } 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 ; } } |
三、后台音频
通过后台音频的功能我们可以实现在系统后台播放音乐的功能,由于后台音频代理只能访问本地文件夹,所以我们务必要先把需要播放的音乐文件拷贝到本地文件夹中。本示例是把安装文件夹的音频文件拷贝到本地文件夹,代码如下:
[C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | //把安装文件夹下的文件拷贝到本地文件夹 private void CopyToIsolatedStorage() { using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) { string [] files = new string [] { "Ring01.wma" , "Ring02.wma" , "Ring03.wma" }; foreach ( var _fileName in files) { if (!storage.FileExists(_fileName)) { string _filePath = "Audio/" + _fileName; StreamResourceInfo resource = Application.GetResourceStream( new Uri(_filePath, UriKind.Relative)); using (IsolatedStorageFileStream file = storage.CreateFile(_fileName)) { int chunkSize = 4096; byte [] bytes = new byte [chunkSize]; int byteCount; while ((byteCount = resource.Stream.Read(bytes, 0, chunkSize)) > 0) { file.Write(bytes, 0, byteCount); } } } } string [] icons = new string [] { "Ring01.jpg" , "Ring02.jpg" , "Ring03.jpg" }; foreach ( var _fileName in icons) { if (!storage.FileExists(_fileName)) { string _filePath = "Images/" + _fileName; StreamResourceInfo iconResource = Application.GetResourceStream( new Uri(_filePath, UriKind.Relative)); using (IsolatedStorageFileStream file = storage.CreateFile( _fileName)) { int chunkSize = 4096; byte [] bytes = new byte [chunkSize]; int byteCount; while ((byteCount = iconResource.Stream.Read(bytes, 0, chunkSize)) > 0) { file.Write(bytes, 0, byteCount); } } } } } } |
我们需要在解决方案中添加Windows phone 音频播放代理项目,并在应用项目中添加对其的引用。修改AudioPlayer.cs代码如下:
[C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | public class AudioPlayer : AudioPlayerAgent { private static volatile bool _classInitialized; private static List<AudioTrack> _playList = new List<AudioTrack> { new AudioTrack( new Uri( "Ring01.wma" , UriKind.Relative), "曲目1" , "艺术家1" , "专辑1" , new Uri( "Ring01.jpg" , UriKind.Relative)), new AudioTrack( new Uri( "Ring02.wma" , UriKind.Relative), "曲目2" , "艺术家2" , "专辑2" , new Uri( "Ring02.jpg" , UriKind.Relative)), new AudioTrack( new Uri( "Ring03.wma" , UriKind.Relative), "曲目3" , "艺术家3" , "专辑3" , new Uri( "Ring03.jpg" , UriKind.Relative)) }; /// <summary> /// 当前播放位置 /// </summary> static int currentTrackNumber = 0; /// <remarks> /// AudioPlayer 实例可共享同一进程。 /// 静态字段可用于在 AudioPlayer 实例之间共享状态 /// 或与音频流代理通信。 /// </remarks> public AudioPlayer() { if (!_classInitialized) { _classInitialized = true ; // 订阅托管异常处理程序 Deployment.Current.Dispatcher.BeginInvoke( delegate { Application.Current.UnhandledException += AudioPlayer_UnhandledException; }); } } /// 出现未处理的异常时执行的代码 private void AudioPlayer_UnhandledException( object sender, ApplicationUnhandledExceptionEventArgs e) { if (System.Diagnostics.Debugger.IsAttached) { // 出现未处理的异常;强行进入调试器 System.Diagnostics.Debugger.Break(); } } /// <summary> /// playstate 更改时调用,但 Error 状态除外(参见 OnError) /// </summary> /// <param name="player">BackgroundAudioPlayer</param> /// <param name="track">在 playstate 更改时播放的曲目</param> /// <param name="playState">播放机的新 playstate </param> /// <remarks> /// 无法取消播放状态更改。即使应用程序 /// 导致状态自行更改,假定应用程序已经选择了回调。 /// /// 值得注意的 playstate 事件 /// (a) TrackEnded: 播放器没有当前曲目时激活。代理可设置下一曲目。 /// (b) TrackReady: 音轨已设置完毕,现在可以播放。 /// /// 只在代理请求完成之后调用一次 NotifyComplete(),包括异步回调。 /// </remarks> protected override void OnPlayStateChanged(BackgroundAudioPlayer player, AudioTrack track, PlayState playState) { switch (playState) { case PlayState.TrackEnded: player.Track = GetPreviousTrack(); break ; case PlayState.TrackReady: player.Play(); break ; case PlayState.Shutdown: // TODO: 在此处理关机状态(例如保存状态) break ; case PlayState.Unknown: break ; case PlayState.Stopped: break ; case PlayState.Paused: break ; case PlayState.Playing: break ; case PlayState.BufferingStarted: break ; case PlayState.BufferingStopped: break ; case PlayState.Rewinding: break ; case PlayState.FastForwarding: break ; } NotifyComplete(); } /// <summary> /// 在用户使用应用程序/系统提供的用户界面请求操作时调用 /// </summary> /// <param name="player">BackgroundAudioPlayer</param> /// <param name="track">用户操作期间播放的曲目</param> /// <param name="action">用户请求的操作</param> /// <param name="param">与请求的操作相关联的数据。 /// 在当前版本中,此参数仅适合与 Seek 操作一起使用, /// 以指明请求的乐曲的位置</param> /// <remarks> /// 用户操作不自动对系统状态进行任何更改;如果用户操作受支持, /// 执行用户操作(如果这些操作受支持)。 /// /// 只在代理请求完成之后调用一次 NotifyComplete(),包括异步回调。 /// </remarks> protected override void OnUserAction(BackgroundAudioPlayer player, AudioTrack track, UserAction action, object param) { switch (action) { case UserAction.Play: if (player.PlayerState != PlayState.Playing) { player.Play(); } break ; case UserAction.Stop: player.Stop(); break ; case UserAction.Pause: player.Pause(); break ; case UserAction.FastForward: player.FastForward(); break ; case UserAction.Rewind: player.Rewind(); break ; case UserAction.Seek: player.Position = (TimeSpan)param; break ; case UserAction.SkipNext: player.Track = GetNextTrack(); break ; case UserAction.SkipPrevious: AudioTrack previousTrack = GetPreviousTrack(); if (previousTrack != null ) { player.Track = previousTrack; } break ; } NotifyComplete(); } /// <summary> /// 实现逻辑以获取下一个 AudioTrack 实例。 /// 在播放列表中,源可以是文件、Web 请求,等等。 /// </summary> /// <remarks> /// AudioTrack URI 确定源,它可以是: /// (a) 独立存储器文件(相对 URI,表示独立存储器中的路径) /// (b) HTTP URL(绝对 URI) /// (c) MediaStreamSource (null) /// </remarks> /// <returns>AudioTrack 实例,或如果播放完毕,则返回 null</returns> private AudioTrack GetNextTrack() { // TODO: 添加逻辑以获取下一条音轨 if (++currentTrackNumber >= _playList.Count) currentTrackNumber = 0; AudioTrack track = _playList[currentTrackNumber]; // 指定曲目 return track; } /// <summary> /// 实现逻辑以获取前一个 AudioTrack 实例。 /// </summary> /// <remarks> /// AudioTrack URI 确定源,它可以是: /// (a) 独立存储器文件(相对 URI,表示独立存储器中的路径) /// (b) HTTP URL(绝对 URI) /// (c) MediaStreamSource (null) /// </remarks> /// <returns>AudioTrack 实例,或如果不允许前一曲目,则返回 null</returns> private AudioTrack GetPreviousTrack() { // TODO: 添加逻辑以获取前一条音轨 if (--currentTrackNumber < 0) currentTrackNumber = _playList.Count - 1; AudioTrack track = _playList[currentTrackNumber]; // 指定曲目 return track; } /// <summary> /// 每次播放出错(如 AudioTrack 未正确下载)时调用 /// </summary> /// <param name="player">BackgroundAudioPlayer</param> /// <param name="track">出现错误的曲目</param> /// <param name="error">出现的错误</param> /// <param name="isFatal">如果为 true,则播放不能继续并且曲目播放将停止</param> /// <remarks> /// 不保证在所有情况下都调用此方法。例如,如果后台代理程序 /// 本身具有未处理的异常,则不会回调它来处理它自己的错误。 /// </remarks> protected override void OnError(BackgroundAudioPlayer player, AudioTrack track, Exception error, bool isFatal) { if (isFatal) { Abort(); } else { NotifyComplete(); } } /// <summary> /// 取消代理请求时调用 /// </summary> /// <remarks> /// 取消请求后,代理需要 5 秒钟完成其工作, /// 通过调用 NotifyComplete()/Abort()。 /// </remarks> protected override void OnCancel() { } } |
最后,我们在mainpage中添加对播放的控制。
[XAML]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | < Grid x:Name="LayoutRoot" Background="Transparent"> < Grid.RowDefinitions > < RowDefinition Height="Auto"/> < RowDefinition Height="*"/> </ Grid.RowDefinitions > <!--TitlePanel 包含应用程序的名称和页标题--> < StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> < TextBlock x:Name="ApplicationTitle" Text="后台音频" Style="{StaticResource PhoneTextNormalStyle}"/> </ StackPanel > <!--ContentPanel - 在此处放置其他内容--> < Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> < Button x:Name="button2" Content="〈" HorizontalAlignment="Left" Margin="13,10,0,0" VerticalAlignment="Top" Click="Button_Click_2"/> < Button x:Name="button1" Content="▶" HorizontalAlignment="Left" Margin="75,10,0,0" VerticalAlignment="Top" Click="Button_Click_1"/> < Button x:Name="button3" Content="〉" HorizontalAlignment="Left" Margin="136,10,0,0" VerticalAlignment="Top" Click="Button_Click_3" /> < TextBlock x:Name="textblock1" HorizontalAlignment="Left" Margin="22,87,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> < Image x:Name="imge1" HorizontalAlignment="Left" Height="100" Margin="22,142,0,0" VerticalAlignment="Top" Width="100"/> </ Grid > </ Grid > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | public partial class MainPage : PhoneApplicationPage { // 构造函数 public MainPage() { InitializeComponent(); BackgroundAudioPlayer.Instance.PlayStateChanged += new EventHandler(Instance_PlayStateChanged); } //刚加载时确定播放状态 protected override void OnNavigatedTo(NavigationEventArgs e) { if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState) { button1.Content = "■" ; textblock1.Text = "曲目:" + BackgroundAudioPlayer.Instance.Track.Title + " 艺术家:" + BackgroundAudioPlayer.Instance.Track.Artist + " 专辑:" + BackgroundAudioPlayer.Instance.Track.Album + " 曲目长度:" +BackgroundAudioPlayer.Instance.Track.Duration.Minutes + ":" + BackgroundAudioPlayer.Instance.Track.Duration.Seconds; using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) { var stream = storage.OpenFile(BackgroundAudioPlayer.Instance.Track.AlbumArt.OriginalString, System.IO.FileMode.Open); var bitmapImage = new BitmapImage(); bitmapImage.SetSource(stream); imge1.Source = bitmapImage; stream.Close(); } } else { button1.Content = "▶" ; textblock1.Text = "未播放曲目" ; } } void Instance_PlayStateChanged( object sender, EventArgs e) { switch (BackgroundAudioPlayer.Instance.PlayerState) { case PlayState.Playing: button1.Content = "■" ; button2.IsEnabled = true ; button3.IsEnabled = true ; break ; case PlayState.Paused: case PlayState.Stopped: button1.Content = "▶" ; break ; } if ( null != BackgroundAudioPlayer.Instance.Track && BackgroundAudioPlayer.Instance.PlayerState!= PlayState.Stopped) { textblock1.Text = "曲目:" + BackgroundAudioPlayer.Instance.Track.Title + " 艺术家:" + BackgroundAudioPlayer.Instance.Track.Artist + " 专辑:" + BackgroundAudioPlayer.Instance.Track.Album + " 曲目长度:" + BackgroundAudioPlayer.Instance.Track.Duration.Minutes + ":" + BackgroundAudioPlayer.Instance.Track.Duration.Seconds; using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication()) { var stream = storage.OpenFile(BackgroundAudioPlayer.Instance.Track.AlbumArt.OriginalString, System.IO.FileMode.Open); var bitmapImage = new BitmapImage(); bitmapImage.SetSource(stream); imge1.Source = bitmapImage; stream.Close(); } } } //播放/暂停 private void Button_Click_1( object sender, RoutedEventArgs e) { if (PlayState.Playing == BackgroundAudioPlayer.Instance.PlayerState) BackgroundAudioPlayer.Instance.Pause(); else BackgroundAudioPlayer.Instance.Play(); } //向前 private void Button_Click_2( object sender, RoutedEventArgs e) { BackgroundAudioPlayer.Instance.SkipPrevious(); button2.IsEnabled = false ; } //向后 private void Button_Click_3( object sender, RoutedEventArgs e) { BackgroundAudioPlayer.Instance.SkipNext(); button3.IsEnabled = false ; } } |
四、后台文件传输
后台文件传输允许我们实现下载上传文件的功能,他限制系统中同时运行的传输任务不能超过两个,并且下载的文件只能存放在本地文件夹的/shared/transfers目录下。下面我们实现一个后台传输任务,下载博客相册中的一张照片。
[XAML]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | < Grid x:Name="LayoutRoot" Background="Transparent"> < Grid.RowDefinitions > < RowDefinition Height="Auto"/> < RowDefinition Height="*"/> </ Grid.RowDefinitions > <!--TitlePanel 包含应用程序的名称和页标题--> < StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> < TextBlock x:Name="ApplicationTitle" Text="后台传输" Style="{StaticResource PhoneTextNormalStyle}"/> </ StackPanel > <!--ContentPanel - 在此处放置其他内容--> < Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> < TextBlock x:Name="textblock1" HorizontalAlignment="Left" Margin="10,198,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> < Button Content="清除传输队列中已完成的任务" HorizontalAlignment="Left" Margin="0,85,0,0" VerticalAlignment="Top" Click="Button_Click_2"/> </ Grid > < Button x:Name="button1" Content="添加一个后台传输" HorizontalAlignment="Left" Margin="12,10,0,0" Grid.Row="1" VerticalAlignment="Top" Click="Button_Click_1"/> </ Grid > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | public partial class MainPage : PhoneApplicationPage { // 构造函数 public MainPage() { InitializeComponent(); } protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { initTransferRequest(); base .OnNavigatedTo(e); } private void initTransferRequest() { //获取第一个后台传输任务 var transferRequest = BackgroundTransferService.Requests.FirstOrDefault(); if (transferRequest == null ) { textblock1.Text = "无后台传输任务" ; button1.IsEnabled = true ; return ; } //当传输状态改变时: transferRequest.TransferStatusChanged += new EventHandler<BackgroundTransferEventArgs>(transfer_TransferStatusChanged); //当传输进度改变时: transferRequest.TransferProgressChanged += new EventHandler<BackgroundTransferEventArgs>(transfer_TransferProgressChanged); updatesStatus(transferRequest); button1.IsEnabled = false ; } void transfer_TransferStatusChanged( object sender, BackgroundTransferEventArgs e) { updatesStatus(e.Request); } void transfer_TransferProgressChanged( object sender, BackgroundTransferEventArgs e) { updatesStatus(e.Request); } void updatesStatus(BackgroundTransferRequest transferRequest) { textblock1.Text = "传输状态:" + transferRequest.TransferStatus.ToString() + " 已下载字节:" + transferRequest.BytesReceived + "总字节:" + transferRequest.TotalBytesToReceive; } private void Button_Click_1( object sender, RoutedEventArgs e) { string fileurlstring = "http://images.cnblogs.com/cnblogs_com/lipan/319399/o_Large.png" ; Uri uri = new Uri(Uri.EscapeUriString(fileurlstring), UriKind.RelativeOrAbsolute); BackgroundTransferRequest transferRequest = new BackgroundTransferRequest(uri); transferRequest.Method = "GET" ; using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoStore.DirectoryExists( "/shared/transfers" )) { isoStore.CreateDirectory( "/shared/transfers" ); } } //文件下载后存放位置(为本地文件夹相对位置) transferRequest.DownloadLocation = new Uri( "shared/transfers/1.png" , UriKind.RelativeOrAbsolute); //外接电源、WiFi的可用性对传输的影响 transferRequest.TransferPreferences = TransferPreferences.AllowCellularAndBattery; try { //添加到后台传输队列中 BackgroundTransferService.Add(transferRequest); } catch (Exception ex) { MessageBox.Show( "无法添加后台传输请求。" + ex.Message); } initTransferRequest(); } //清除传输队列已完成的任务 private void Button_Click_2( object sender, RoutedEventArgs e) { foreach ( var transferRequest in BackgroundTransferService.Requests) { if (transferRequest.TransferStatus == TransferStatus.Completed) { try { BackgroundTransferService.Remove(transferRequest); } catch { } } } initTransferRequest(); } } |
五、后台辅助线程
后台辅助线程虽然名字这么叫,但是它不能在后台运行,我们可以用它来执行一个任务,并且可以实时获取执行的进度,实现代码如下:
[XAML]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> < StackPanel > < StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10" > < Button x:Name="buttonStart" Content="开始" Click="buttonStart_Click" Width="200" /> < Button x:Name="buttonCancel" Content="取消" Click="buttonCancel_Click" Width="200" /> </ StackPanel > < StackPanel Margin="10,50,0,0" Orientation="Horizontal"> < TextBlock Text="进度: " /> < TextBlock x:Name="tbProgress" /> </ StackPanel > </ StackPanel > </ Grid > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | public partial class MainPage : PhoneApplicationPage { private BackgroundWorker bw = new BackgroundWorker(); public MainPage() { InitializeComponent(); bw.WorkerReportsProgress = true ; bw.WorkerSupportsCancellation = true ; bw.DoWork += new DoWorkEventHandler(bw_DoWork); bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged); bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); } private void buttonStart_Click( object sender, RoutedEventArgs e) { if (bw.IsBusy != true ) { bw.RunWorkerAsync(); } } private void buttonCancel_Click( object sender, RoutedEventArgs e) { if (bw.WorkerSupportsCancellation == true ) { bw.CancelAsync(); } } private void bw_DoWork( object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; for ( int i = 1; i <= 10; i++) { if ((worker.CancellationPending == true )) { e.Cancel = true ; break ; } else { // Perform a time consuming operation and report progress. System.Threading.Thread.Sleep(500); worker.ReportProgress(i * 10); } } } private void bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled == true ) { this .tbProgress.Text = "Canceled!" ; } else if (!(e.Error == null )) { this .tbProgress.Text = ( "Error: " + e.Error.Message); } else { this .tbProgress.Text = "Done!" ; } } private void bw_ProgressChanged( object sender, ProgressChangedEventArgs e) { this .tbProgress.Text = (e.ProgressPercentage.ToString() + "%" ); } } |
出处:[http://www.cnblogs.com/lipan/]
版权声明:本文的版权归作者与博客园共有。转载时须注明原文出处以及作者,并保留原文指向型链接,不得更改原文内容。否则作者将保留追究其法律责任。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架