Windows phone 8 是一個單任務操作系統,任何時候都只有一個應用處於活躍狀態,這里的多任務是指對后台任務的支持。本節我們先講講應用程序的運行狀態,然后看看支持的后台任務,包括:后台代理、后台音頻、后台文件傳輸、后台輔助線程等。
快速導航:
一、應用的狀態
二、后台代理
三、后台音頻
四、后台文件傳輸
五、后台輔助線程
一、應用的狀態
1)應用的運行狀態
我們通過圖解來分析應用的運行狀態,啟動並置於前台界面的應用是唯一處於運行狀態的,其他的操作,比如win鍵,后退導出應用,打開選擇器和啟動器時都會讓當前運行的應用進入休眠狀態,如果系統內存不足,處於休眠狀態的應用可能會被系統邏輯刪除。下面的圖示演示了這個過程。
2)如何恢復狀態
當應用處於休眠狀態時,它的狀態信息仍然保留在內存中,用戶下次切換進去后不會有任何變化。但是當應用被邏輯刪除后,這些狀態信息就會丟失,比如表單填寫的內容都會消失,為了避免這種情況,我們需要手動保留狀態信息。
首先,我們在mainpage定義一些頁面表單控件:
<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#][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#]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#]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]<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>
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#]//把安裝文件夾下的文件拷貝到本地文件夾 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#]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]<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>
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]<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>
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]<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>
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/]
版權聲明:本文的版權歸作者與博客園共有。轉載時須注明原文出處以及作者,並保留原文指向型鏈接,不得更改原文內容。否則作者將保留追究其法律責任。