前言
如果在以往,您有用过之前的 Mobile 操作系统,像是 WM5.x、WM6.x ,是允许你在同时间执行很多应用程序;而应用程序的默认行为,在 Form 的右上角是一个『 X 』的按钮,按钮按下去之后,应用程序是躲到了背景,仍在继续在执行;而到了Windows Phone 7,这样的行为模式变更了,在前景一次只能执行一个应用程序,而原先的应用程序发生了什么事?这就是本篇要跟各位介绍的;而第二个部分是在应用程序中,可 能会存在好几个页面,而彼此间要怎么传递数据呢?
这都是今天会谈论到的议题,那么接下来就开始今天的介绍
议程
Application life cycle
Page Navigation
在页面中传递数据
Idle detection
Application life cycle
由于在 Windows Phone 7 中,应用程序的运作方式跟以往的 Mobile 系列不同,所以在开发应用程序时要留意有关生命周期的事件,以便在需要的地方加以处理;事件的种类会有
Launching
Closing
Activated
Deactivated
而这些事件是在甚么时候会发生呢?下面先来看看第一种状况
程序生命周期
应用程序『第一次的启动』一定是由首页的 Tile 或是由应用程序行表中启动,而启动之后便会产生新的应用程序实例,接着就会进入到 Launching 事件中;在 Launching 事件中您可以做一些初始化的动作,需要特别注意的是在 Launching 事件中, 不适合去做长时间的动作,因为 Launching 事件是发生在页面显示之前,所以在Launching 事件没有完成之前,页面都是看不到的,整个屏幕都会是黑黑的一片,所以执行长时间的作业的话,是很容易被误认为应用程序停止响应或是其他的异常情形,这是 不好的。
经过Launching 的事件之后,应用程序的第一个页面就会显示出来,这时候会进入到应用程序执行中 ( Running ) 的状态,而在应用程序的第一个页面时,如果使用者按下返回键,这个时候就会直接引发 Closing 的事件,Closing 事件之后就会把应用程序整个关闭了。
那么,如果在应用程序的第一个页面中,使用者按下了开始钮 ( ) ,那这时候呢?关闭应用程序吗?不,这时候应用程序会进入tombstoning,之后移到背景,让我们来看看下一张图
程序生命周期2
当在第一个页面中,使用者按下开始钮,这个时候应用程序便会进入 Deactivated 的事件,之后便进入 tombstoning 的状态,也就是整个应用程序会停止运作,这跟之前的 Mobile 5.x/6.x 是有很大的不同的。而在 Deactivated 事件之后,使用者这时候可能会执行其他的应用程序或进行其他的操作,之后可能会按下返回键回到应用程序的执行,这个时候就会进入 Activated 事件, Activated 事件处理完毕之后,便会回到执行中的状态;那在这两个事件中,要处理甚么呢?您可以在这个事件中去储存一些暂时性的数据,而这些数据同时又是属于整个应用 程序会使用到的,就可以在这些事件中去处理。
Deactivated 事件还有个地方需特别注意,所有在 Deactivated 事件中处理的事情,必需要在 10 秒钟之内处理完毕,不然的话系统会强制的中止你的应用程序,而假设发生这种状况的话,程序是被整个关闭,按下返回键是不会回到应用程序中的,这点必须特别 留意。
举个简单的例子,例如说一个很简单的游戏程序,程序中会有总分数的纪录,像是下面左图样子,现在是 50 分,那如果不小心按到开始钮或是其他的原因,离开了应用程序,再返回的时候,糟糕..这时候会像下面右图一样,变成 0 分了;如果没有适当的去处理这个部份,那对使用者来说是会觉得很疑惑,而且不是一个好的应用程序的。
程序生命周期3
这个时候就可以处理 Deactivated、Activated 事件,在相关的事件中去做储存的事件,举个简单的例子;笔者首先在 App.xaml.cs 中加入一个 HighScore 的全局变量
public static int HighScore;
之后在 App.xaml.cs 中处理相关的事件 ( Application life cycle 相关的事件在 App.xaml.cs 中都可以找到 )
- // Code to execute when the application is activated (brought to foreground)
- // This code will not execute when the application is first launched
- private void Application_Activated(object sender, ActivatedEventArgs e)
- {
- object tmp = 0;
- if (PhoneApplicationService.Current.State.TryGetValue("Score", out tmp))
- {
- App.HighScore = (int)tmp;
- }
- else
- App.HighScore = 0;
- }
- // Code to execute when the application is deactivated (sent to background)
- // This code will not execute when the application is closing
- private void Application_Deactivated(object sender, DeactivatedEventArgs e)
- {
- PhoneApplicationService.Current.State["Score"] = App.HighScore;
- }
最后,在 MainPage.xaml.cs 中,Loaded 事件中,把值给读出来显示
- private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
- {
- tbScore.Text = App.HighScore.ToString();
- }
这样子,不管是不小心误触到其他按键或者是其他原因离开了应用程序都可以正常的保留住想要保留的数据了。
而在上面的程序代码中,您会看到 PhoneApplicationService.Curent.State ,这是一个实做 IDictionary 的类别,使用时要加入 Microsoft.Phone.Shell 的命名空间,之后就可以它用来储存一些应用程序中的数据。
注:应用程序执行中,用户按下开始钮,或是执行 Lanucher/Chooser 、拍照等等,或是一段时间没有使用而进入锁定;只要是离开应用程序本身,就会开始进入 Deactivated 事件
Page Navigation
在刚刚我们看过了应用程序的生命周期,那么页面呢?紧接着就来看看在页面显示的过程中,以及在页面中去巡览的时候,应用程序是如何处理这些事件的;在这边需要注意的是件有
Loaded
每一次页面的载入完成时,都会引发 Loaded 事件
Unloaded
当从这个页面要巡览到另外一个页面时,就会引发 Unload 事件
OnNavigatedFrom
当利用 NavigationService ,要从页面离开时会引发 OnNavigatedForm 事件,使用时必须要覆写 Page 事件
OnNavigatedTo
当利用 NavigationService ,寻览到新的页面时,会引发新页面的 OnNavigatedTo 事件,使用时必须要覆写 Page 事件
其中如果要处理 OnNavigatedTo、OnNavigatedForm 事件是必须利用覆写的方式来使用,而事件发生的顺序会是 OnNavigatedTo à Load à OnNavigatedForm à UnLoaded。
而在 Page 的这些事件中,要处理甚么动作呢?在页面相关的事件中,要处理的是必须要储存一些暂时性的数据,以便在巡览的过程中使用;以及在页面中传递数据等动作。当要进行页面的巡览动作,通常会利用 NavigationService 来做,例如
- NavigationService.Navigate(new Uri("/ThirdPage.xaml",
- UriKind.Relative));
利用这个方式就可以巡览到下一个页面,那退回上一个页面呢?这时候可以利用 GoBack 的方式来返回,例如
- NavigationService.GoBack();
那如果不用 GoBack 的方式,直接也利用 Navigate 的方式指定页面名称呢?当然也是可以巡览到指定的页面,但是要注意的是,利用 Navigate 方法时,是会产生一个『新』的目标页面的,这是两个方式不同的地方;举个简单的例子来说;假设在 MainPage 当中,摆放了一个 TextBox ,输入一些文字之后,巡览到 SecondPage ;这时候如果使用 GoBack 的方式(或是按下硬件的返回键),您会发现 TextBox 会记住刚刚输入的文字,而如果是用 Navigate 加上指定页面的方式,您会发现 TextBox 的文字会是默认的初始设定,而不会是刚刚输入的文字。
到这里,相信您对于页面以及应用程序的生命周期有大略的认识与了解,而在这些事件 中,最常需要处理的就是去保存应用程序相关的状态;主要在 Deactivated 以及 Activated 这类事件中处理的是整个应用程序通用性的数据或是状态;而 OnNavigateTo 这类事件中则是处理页面使用的暂时数据或是处理其他传递过来的数据,接下来就来看一下,在各个页面中传递数据是用什么方式进行,以及如何去保存一些应用程 序的状态。
在页面中传递数据
传递数据数据的方式有很多种,可以依照不同的状况去使用,下面笔者大致列出几种方式,您可以依照使用的情境以及需求做调整
利用全局变量的方式
自行宣告全局变量或是在 App 类别中 ( App.xaml.cs ) ,去建立相关的属性 ( property ) 或是字段 ( flied )
例如说,笔者在 App.xaml.cs 中去新增一个字符串变量,大概像这个样子
- public static string SharedString = "";
之后在主要页面 ( main page ) 中,就可以利用下面的方式来储存要传递的数据
- App.SharedString = textBox1.Text;
- NavigationService.Navigate(new Uri("/Page_UseApp.xaml",
- UriKind.Relative));
而接着在新的页面中,就可以在 OnNavigateTo 的事件中去取值,并且把值显示出来,例如
- protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
- {
- base.OnNavigatedTo(e);
- textBox1.Text = App.SharedString;
- }
笔者这边是利用简单的字符串变量来做示范,实际使用时您也可以用自定义类别或是其他的数据类型来使用,这就看您实际的需求;而在 App 类别中的相关数据是整个应用程序都可以共享的。
利用 Url 参数传递
利用像是 SecondPage.xaml?para1=12345¶2=aaaaa 的方式来传递数据,这样的方式跟以往在开发 Web 应用程序的时候是极其类似的;例如说在主要页面中,笔者以下面的方式来呼叫 Navigate 方法
- private void btnUseUrl_Click(object sender, RoutedEventArgs e)
- {
- NavigationService.Navigate(new Uri("/Page_UseUrl.xaml?msg="+ textBox1.Text, UriKind.Relative));
- }
而在目标页面中,就可以利用 NavigationContext 来取值,例如
- protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
- {
- base.OnNavigatedTo(e);
- textBox1.Text = NavigationContext.QueryString["msg"];
- }
在小量的资料传递下,可以采用这种方式将资料传递到另一个页面中。
利用 PhoneApplicationSerivce 中的 State 属性
State 是一个实做 IDictionary 的类别,可以用来保存应用程序的相关数据;使用时感觉跟全局变量的方式有点类似,因为它也是在整个应用程序中都可以去使用的;使用时要特别留意 Key 的命名,不能重复使用;而要使用时,必须要先引用 Microsoft.Phone.Shell 的命名空间,在 main page 的部分大概会利用像是下面这样的方式来做使用
- private void btnUseState_Click(object sender, RoutedEventArgs e)
- {
- PhoneApplicationService.Current.State["msg"] = textBox1.Text;
- NavigationService.Navigate(new Uri("/Page_UseState.xaml", UriKind.Relative));
- }
而在目标页面中,取值得方式大致会像这个样子
- protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
- {
- base.OnNavigatedTo(e);
- object data = null;
- if (PhoneApplicationService.Current.State.TryGetValue("msg", out data))
- textBox1.Text = (string)data;
- else
- textBox1.Text = "error";
- }
TryGetValue 是为了防止对应的 Key 值不存在而使用的,或是您也可以利用 try…catch 来做,这个地方要记得要加上适当的错误处理。而像是先前在 life cycle 中提到的部分,如果您是将值保存到 State 中,那么除非应用程序结束,不然在 Deactivated、Activated 事件中,您还是可以去存取到相关的数据。
利用 Isolated storage
永久性的数据应该使用隔离储存区来储存,以便下次程序开启时能够继续的使用;还记得在前几集讨论过的隔离储存区使用吗?记得要引入相关的命名空间,笔者下面举个简单的例子;
- using System.IO.IsolatedStorage;
- using System.IO;
在写入档案部分的程序代码大致会像下面这样子
- private void btnUseStorage_Click(object sender, RoutedEventArgs e)
- {
- IsolatedStorageFile isofile = IsolatedStorageFile.GetUserStoreForApplication();
- if (isofile.FileExists("/data.txt"))
- isofile.DeleteFile("/data.txt");
- StreamWriter sw = new StreamWriter(isofile.CreateFile("/data.txt"), System.Text.Encoding.UTF8);
- sw.WriteLine("Some data from isolated storage");
- sw.Close();
- sw.Dispose();
- isofile.Dispose();
- NavigationService.Navigate(new Uri("/Page_UseStorage.xaml", UriKind.Relative));
- }
而读取的部分,通常来说,使用隔离储存区时可能会放置较多的数据,所以笔者这边在读取时多建立一条线程来做读取的动作,并且延迟 1500ms 来模拟这样的效果,读取动作的程序代码大概会像这样子
- namespace NavigateDemo
- {
- public partial class Page_UseStorage : PhoneApplicationPage
- {
- Thread Readthread = null;
- public Page_UseStorage()
- {
- InitializeComponent();
- }
- protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
- {
- base.OnNavigatedTo(e);
- //將progessbar設定為可見,並且將資料顯示部分設定為隱藏
- textBlock1.Visibility = System.Windows.Visibility.Collapsed;
- textBox1.Visibility = System.Windows.Visibility.Collapsed;
- progressBar1.Visibility = System.Windows.Visibility.Visible;
- //啟動執行續作業
- Readthread = new Thread(ReadStorageFile);
- Readthread.Start();
- }
- private void ReadCompleted(string value)
- {
- progressBar1.Visibility = System.Windows.Visibility.Collapsed;
- textBlock1.Visibility = System.Windows.Visibility.Visible;
- textBox1.Visibility = System.Windows.Visibility.Visible;
- textBox1.Text = value;
- }
- //資料讀取完畢時,更新UI使用的委派事件
- delegate void deReadCompleted(string value);
- private void ReadStorageFile()
- {
- Thread.Sleep(11500);
- IsolatedStorageFile isofile = IsolatedStorageFile.GetUserStoreForApplication();
- if (isofile.FileExists("/data.txt"))
- {
- StreamReader sr = new StreamReader(isofile.OpenFile("/data.txt", FileMode.Open), System.Text.Encoding.UTF8);
- string tmpString = sr.ReadLine();
- sr.Close();
- sr.Dispose();
- this.Dispatcher.BeginInvoke(new deReadCompleted(ReadCompleted), new object[] { tmpString });
- }
- else
- {
- this.Dispatcher.BeginInvoke(new deReadCompleted(ReadCompleted), new object[] { "file not found.." });
- }
- isofile.Dispose();
- }
- }
- }
这边在读取时,利用 progessbar 来显示正在读取中的状态,画面大致会像下面左图,而读取完毕时再将数据显示在画面上
Idle detection
最后我们来看 Idle detection 的部分;什么是 Idle detection 呢?这功能就是在设定系统闲置相关的侦测;例如说,如果装置一段时间没有使用(操作)的话,那么首先系统会将屏幕变暗,以节省电源,而再经过一段时间之 后,便会锁定装置,将屏幕整个关闭,而这时候就会进入了上面生命周期提到的 Deactivated 事件,之后应用程序也进入 tombstoning 的状态。那么当应用程序是用于拨放音乐,当装置锁定的情形下,我们仍然希望应用程序可以继续运作;或者应用程序是利用装置上的 sensor ( 例如 accelerometer ) 来进行,在应用程序执行过程中,可能长时间都不会有使用触控屏幕的情形,但这时候不希望系统进入待机的状态,那么这时候就要设定 Idle detection 了。
在开始之前,要先提醒各位,在 Idle detection 的部分,MarketPlace 递交应用程序时是有一些规定的,请一定要确认 Windows Phone 7 Application Certification Requirements 中的相关规定,不然应用程序是不能够上架的。您可以在文件中的 6.3 节『 Applicatins Running under a Locked Screen 』中找到相关的资料。
好,了解该注意的事项之后,首先来看看侦测闲置的模式;在 Windows Phone 7 中,Idle detection 有两种
ApplicationIdleDetectinMode
UserIdleDetectionMode
我们先来看 ApplicationIdleDetection 的部分;ApplicationIdleDetection 是应用程序闲置状态侦测,例如经过一段时间没有使用的话,装置会进入锁定,并且引发应用程序的 Deactivated 事件,随后应用程序进入 tombstoning 状态;ApplicationIdleDetectionMode 便是设定装置进入锁定时,应用程序会不会进入 tombstoning 状态,如果设定为关闭,那么将不会引发应用程序的 Deacticated 事件,也不会将应用程序进入 tombstoning ;好处是甚么呢?大约有下列几点
应用程序仍然在执行中
当用户返回应用程序时,由于没有进入 tombstoning 的状态,能够快速回复
而要注意的地方约略如下
应用程序仍然在执行,所以会继续的消耗电池的电力;请特别注意,装置同样会进入锁定状态,只是应用程序不会停止
所有有关 UI 的更新动作应该要停止,以节省电力的消耗
所有动画、Timer 等动作应该要停止
Sensor 将会停止回报(例如 accelerometer 将会停止回报目前的数值)
在改变闲置侦测模式时,永远要先询问使用者是否同意
那么问题来了,要怎么去知道目前 ApplicationIdleDetectionMode 的状态,以及怎么知道目前装置是不是要被锁定了,进而做相关的处理动作呢?
这里我们借用一下 MSDN 网站上的图片来做说明
程序生命周期4
最外层的部分是 PhoneApplicationFrame ,装载了整个应用程序,包含 Page、Page 中显示的内容、 System tray(page 最上方显示时间、讯号状态的状态栏)、 Application bar 等;在一个应用程序中只会有一个 frame ,也是整个应用程序最上层的容器;frame 会回报目前页面的方向、目前可用(可供应用程序使用)的空间有多少等等,以便让各种应用程序有相同的行为与特性,而 Obscured、UnObscured 事件,这两个事件便是发生在 PhoneApplicationFrmae 中,接下来我们来看一下程序代码的部分
- using Microsoft.Phone.Shell;
- Pprivate void SetAppIdleDetectionDisable()
- {
- //將應用程式閒置狀態偵測關閉
- PhoneApplicationService.Current.ApplicationIdleDetectionMode = IdleDetectionMode.Disabled;
- PhoneApplicationFrame root = (App.Current.RootVisual) as PhoneApplicationFrame;
- if (root != null)
- {
- root.Obscured += new EventHandler(root_Obscured);
- root.Unobscured += new EventHandler(root_Unobscured);
- }
- else
- MessageBox.Show("Error");
- }
在程序代码中可以看到,在把闲置状态侦测关闭之后,接着就是取得 PhoneApplicationFrame ,而 PhoneApplicationFrame 时也是透过 App 类别来取得,取得之后由于在相关的事件必须要有对应的处理动作,因此必须要挂载相关的事件;其中 Obscured 事件便是当进入锁定时会引发的事件,在这个事件中,可以去做将 Storyboard、UI 的更新动作停止的相关动作,例如下面这边以一个 Timer 为例子,在这个事件中会进行关闭的动作
- void root_Obscured(object sender, ObscuredEventArgs e)
- {
- Debug.WriteLine("Unobscured");
- if (e.IsLocked)
- {
- //當應用程式被Lock screen覆蓋時要處理的動作,停止動畫(storyboard)、UI更新等動作
- timer.Stop();
- }
- }
这样子就可以达到在装置进入锁定时,能够把一些不需要用到的部分关闭,以节省电力的使用。看完了关闭之后,那如果要重新把闲置状态侦测给开启呢?设定回 Enable 就可以了?这个动作没有错,但是目前的 Windows Phone 7 版本尚未支持,目前闲置模式关闭之后,要重新启动唯一的方式就是整个应用程序必须要重新开启才行,这部分要特别留意。而 MSDN 中有提到,建议还是可以在应用程序中加入相关的程序代码,但同时要做错误处理,例如说
- private void SetAppIdleDetectionEnable()
- {
- if (PhoneApplicationService.Current.ApplicationIdleDetectionMode != IdleDetectionMode.Enabled)
- {
- try
- {
- PhoneApplicationService.Current.ApplicationIdleDetectionMode = IdleDetectionMode.Enabled;
- }
- catch (InvalidOperationException ex)
- {
- //platform not souported
- MessageBox.Show("Can't enable application idledection");
- }
- }
- }
这样在未来的更新中,系统支持上来之后,你的应用程序功能就可以立刻的正常运作了。
接下来来看 UserIdleDetectionMode 的部分,这个部分是侦测使用者闲置的状态,使用的方式跟刚刚 ApplicationIdleDetection 是极其类似的,主要的差异性笔者大致列一下
以目前来说,使用者闲置是指『当用户没有触碰屏幕操作,或是点选硬件按键时』,Sensor 的部分目前即使有改变(例如说转向等等),也是视为闲置中,这个部分在未来的更新中可能会有变更
当设定为 Disable 时,装置永远不会进入锁定
UserIdleDetectionMode 是支持 Disable 以及 Enable 的
在关闭的时候,程序代码的部分大致会像下面这样
- private void SetUserIdleDetectionDisable()
- {
- PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
- }
跟先前操作 ApplicationIdleDetection 的部分几乎是相同的,而重新启动的部分也是相当的类似
- private void SetUserIdleDetectionEnable()
- {
- if (PhoneApplicationService.Current.UserIdleDetectionMode != IdleDetectionMode.Enabled)
- {
- try
- {
- PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Enabled;
- }
- catch (Exception ex)
- {
- //platform not souported
- MessageBox.Show("Can't enable user idledection");
- }
- }
- }
这样便可以达到停止闲置状态的侦测,这对于一些单纯利用 Sensor 来进行操作的应用程序是相当有用的。