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

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

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

一、应用的状态

1)应用的运行状态

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

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

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() + "%");

        }

    }

你可能感兴趣的:(windows phone)