Silverlight for windows phone 7 Application life cycle & Navigation

 

前言

如果在以往,您有用過之前的 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,之後移到背景,讓我們來看看下一張圖

當在第一個頁面中,使用者按下開始鈕,這個時候應用程式便會進入 Deactivated 的事件,之後便進入 tombstoning 的狀態,也就是整個應用程式會停止運作,這跟之前的 Mobile 5.x/6.x 是有很大的不同的。而在 Deactivated 事件之後,使用者這時候可能會執行其他的應用程式或進行其他的操作,之後可能會按下返回鍵回到應用程式的執行,這個時候就會進入 Activated 事件, Activated 事件處理完畢之後,便會回到執行中的狀態;那在這兩個事件中,要處理甚麼呢?您可以在這個事件中去儲存一些暫時性的資料,而這些資料同時又是屬於整個應用程式會使用到的,就可以在這些事件中去處理。

Deactivated 事件還有個地方需特別注意,所有在 Deactivated 事件中處理的事情,必需要在 10 秒鐘之內處理完畢,不然的話系統會強制的中止你的應用程式,而假設發生這種狀況的話,程式是被整個關閉,按下返回鍵是不會回到應用程式中的,這點必須特別留意。

舉個簡單的例子,例如說一個很簡單的遊戲程式,程式中會有總分數的紀錄,像是下面左圖樣子,現在是 50 分,那如果不小心按到開始鈕或是其他的原因,離開了應用程式,再返回的時候,糟糕..這時候會像下面右圖一樣,變成 0 分了;如果沒有適當的去處理這個部份,那對使用者來說是會覺得很疑惑,而且不是一個好的應用程式的。

這個時候就可以處理 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 事件

在剛剛我們看過了應用程式的生命週期,那麼頁面呢?緊接著就來看看在頁面顯示的過程中,以及在頁面中去巡覽的時候,應用程式是如何處理這些事件的;在這邊需要注意的是件有

  • 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&para2=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 網站上的圖片來做說明

最外層的部分是 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<ObscuredEventArgs>(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 來進行操作的應用程式是相當有用的。

今天的介紹就到這邊了,希望大家對於應用程式的生命週期以及相關的應用有初步的了解,趕緊動手來試試看吧!

[範例影片觀賞]

範例影片
觀看影片 影片下載

  

[範例檔案下載]