Windows phone 8 學習筆記(6) 多任務


Windows phone 8 是一個單任務操作系統,任何時候都只有一個應用處於活躍狀態,這里的多任務是指對后台任務的支持。本節我們先講講應用程序的運行狀態,然后看看支持的后台任務,包括:后台代理、后台音頻、后台文件傳輸、后台輔助線程等。

快速導航:
    一、應用的狀態
    二、后台代理
    三、后台音頻
    四、后台文件傳輸
    五、后台輔助線程

一、應用的狀態

1)應用的運行狀態

我們通過圖解來分析應用的運行狀態,啟動並置於前台界面的應用是唯一處於運行狀態的,其他的操作,比如win鍵,后退導出應用,打開選擇器和啟動器時都會讓當前運行的應用進入休眠狀態,如果系統內存不足,處於休眠狀態的應用可能會被系統邏輯刪除。下面的圖示演示了這個過程。

2)如何恢復狀態

當應用處於休眠狀態時,它的狀態信息仍然保留在內存中,用戶下次切換進去后不會有任何變化。但是當應用被邏輯刪除后,這些狀態信息就會丟失,比如表單填寫的內容都會消失,為了避免這種情況,我們需要手動保留狀態信息。
    首先,我們在mainpage定義一些頁面表單控件:

[XAML]
        <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>
[C#]
    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>
[C#]
    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>
[C#]
    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>
[C#]
    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() + "%");
        }
    }

 

作者:[ Lipan]
出處:[ http://www.cnblogs.com/lipan/]
版權聲明:本文的版權歸作者與博客園共有。轉載時須注明原文出處以及作者,並保留原文指向型鏈接,不得更改原文內容。否則作者將保留追究其法律責任。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM