作者:
Yochay Kiriaty
在前兩篇文章(part 1
和part 2
)中你已經學習了應用程式生命週期中的不同事件——Launching, Deactivated, Closing和Activated以及它們之間的區別。在這些知識和這個代碼
的基礎上,我們繼續向前探索。
儲存臨時資料和導航到正確的頁面
回憶一下,我們程式的第二頁包含兩個文字框,一個用來允許使用者輸入一個將被儲存到連絡人資訊中的電話號碼,另一個讓使用者可以輸入一條待發送的簡訊息。當我們在墓碑狀態的程式中使用啟動器(Launchers)和選取器(Choosers)時這將會變得十分有用。
在開始前首先要明確一下問題。最簡單的方式是做一個小實驗:
- 在Visual Studio中,開啟你的程式。
- 導航至第二個頁面並在兩個文字框中輸入一些資訊。
- 這將會使你的程式被停用(關於此請參見這個系列的第二部分
)。
- 按一次返回鍵以回到你的應用程式。這會導致你的模擬器顯示黑屏。
- 按下F5,或者用任何方法重啟你的Visual Studio偵錯工作階段以重新啟用你的程式。(再強調 一次,如果你錯過了某些內容,請看這個系列的第二部分)
- 現在,你的程式應該處於運行狀態並能看到程式的第二頁。然而,文字框中的內容卻是空的。在停用程式之前你所輸入的內容已經不在了。它們消失了!
從這個小實驗中你可以得知你在程式中輸入的資料在程式停用(記住,你的程式被終止了)時不會自動被儲存。在重新啟用時就意味著重新開始了程式的一個新執行個體。這表明所有控制項在預設情況下是沒有資料的,除非你為它們載入資料。
有關墓碑程式最重要的
是使用者可以不返回到你的應用程式
,因此你的程式可能不會被重新啟用
。如果你希望它發生,那麼你需要將要恢複的任何資料儲存到磁碟。當然作為一個開發人員這完全取決於你自己和你的工作職責去儲存和擷取應用程式的資料。
我們來區分一下這兩種要儲存的資料的類型(摘自MSDN
):
- 持久資料
——被多個程式的所有執行個體共用的資料。持久資料的儲存和載入都在隔離儲存區 (Isolated Storage)區
。在不同應用程式執行時各個程式的設定就是持久化資料的一個例子。
- 臨時資料
——描述一個應用程式單個執行個體狀態的資料。臨時資料存放區在PhoneApplicationService
類的State
字典中。一個墓碑程式在重新被啟用時會恢複它的臨時狀態。
在下篇文章中我們會講解持久資料如何與隔離儲存區 (Isolated Storage)區一同工作。現在讓我們重點關注一下如何管理臨時資料和使用State字典。
在SDK中的一個新類就是PhoneApplicationService
。它提供了對應用程式生命週期中的各種狀態的訪問。這包含程式閑置行為的管理和程式狀態的管理。這個類在墓碑這個遊戲中扮演了主角,因為它對外公開了Launching, Deactivate, Activated和Closing事件,它們在App.xaml.cs檔案中有對應的方法(你已經見過了)。這個類還包括一個唯讀類型為IDictionary
的State屬性。這個字典的重要性在於當你的程式被墓碑化處理後它會被Windows Phone作業系統持久化在你的應用程式中。當應用程式被重新啟用,存在字典中的對象會被返回。如果你只是想讓應用程式從墓碑狀態返回那麼你不用將這些臨時對象儲存到磁碟上。因此,如果你想使用State字典,要確保只在其中儲存臨時資料——那些你不介意丟失或只對當前應用程式執行個體有用的資料。在我們的例子中,我們儲存電話號碼和簡訊息的內容。
請注意你儲存到這個字典中的對象必須是可序列化的
(serializable),否則你會在停用事件觸發時得到一個關於作業系統無法禁用或恢複使用你的對象的異常。
MSDN中的最佳實務
建議你在
OnNavigatedFrom
事件中將臨時的頁面資料儲存在State字典中,並且在OnNavigatedTo
事件中載入資料。
我更新了程式從而可以儲存電話號碼和短訊息到這個字典中,並在每次導航到頁面時載入它。如果一個“完整的”新的程式執行個體(意思是Launching事件被觸發)啟動時,State字典是空的。
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)<br /> {<br /> Util.Trace("***** in DetailsPage: OnNavigatedFrom ( " + DateTime.Now.Ticks + " *****)");<br /> //try to locate the phone number from previous save and simply override it<br /> if (true == PhoneApplicationService.Current.State.ContainsKey(PhoneNumberKey))<br /> {<br /> //clear prev value<br /> PhoneApplicationService.Current.State.Remove(PhoneNumberKey);<br /> }<br /> PhoneApplicationService.Current.State.Add(PhoneNumberKey, this.PhoneNumberTxt.Text);<br /> //try to locate the SMS Messagefrom previous save and simply override it<br /> if (true == PhoneApplicationService.Current.State.ContainsKey(SmsMessageKey))<br /> {<br /> //clear prev value<br /> PhoneApplicationService.Current.State.Remove(SmsMessageKey);<br /> }<br /> PhoneApplicationService.Current.State.Add(SmsMessageKey, this.MessageTxt.Text);<br /> }<br /> // Step 2<br /> protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)<br /> {<br /> Util.Trace("***** in DetailsPage: OnNavigatedFrom ( " + DateTime.Now.Ticks + " *****)");<br /> //try to locate the phone number from previous run<br /> if (true == PhoneApplicationService.Current.State.ContainsKey(PhoneNumberKey))<br /> {<br /> string s = PhoneApplicationService.Current.State[PhoneNumberKey] as string;<br /> if (!String.IsNullOrEmpty(s))<br /> {<br /> Util.Trace("***** in DetailsPage: OnNavigatedTo: Found phone number ( " + DateTime.Now.Ticks + " *****)");<br /> this.PhoneNumberTxt.Text = s;<br /> }<br /> }<br /> // Step 2<br /> //try to locate the phone SMS MSG from previous run<br /> if (true == PhoneApplicationService.Current.State.ContainsKey(SmsMessageKey))<br /> {<br /> Util.Trace("***** in DetailsPage: OnNavigatedTo: Found Sms Msg ( " + DateTime.Now.Ticks + " *****)");<br /> string s = PhoneApplicationService.Current.State[SmsMessageKey] as string;<br /> if (!String.IsNullOrEmpty(s))<br /> {<br /> this.MessageTxt.Text = s;<br /> }<br /> }<br /> }
注意以下的變化,頁面2的資訊在頁面之間導航切換時被儲存。也就是你在頁面2按下返回鍵,應用程式返回到頁面1的時候。如果你在頁面1點擊“導航到下一頁”按鈕,你的程式會導航到頁面2。OnNavigateTo事件(在頁面2中)會被觸發,同時上面的代碼會使控制項載入資料。這非常重要因為如果你仔細觀察Visual Studio輸出視窗的資訊將會發現每次你導航到頁面2時,它的建構函式都會執行。這說明每次在頁面2中按下返回鍵時,該頁會被銷毀,因此你每次從頁面1導航到頁面2時,頁面2都重新被建立。如果想觀察這個行為,可以在兩個頁面中不斷的切換,同時注意每次從頁面1導航到頁面2時頁面2的建構函式都會被調用。如果在程式中將OnNavigatedFrom方法中的內容注釋掉,那麼在頁面間切換時,你會看到第二頁的資訊沒有被儲存。
現在將程式變為墓碑你會看到電話號碼和簡訊息已被儲存,不只是在頁面切換時,還在離開程式後返回時發生。導航到頁面2,輸入一個電話號碼和一些文本資訊,按下Windows鍵使程式變為墓碑。你應該可以能看到Deactivated事件的跟蹤資訊然後程式被終止掉。再按下返回鍵同時不要忘記在Visual Studio中按F5重啟偵錯工作階段。你的程式將會返回到墓碑狀態同時你會看到Activated事件被觸發,然後是頁面2的建構函式。接著你會看到OnNavigatedTo的資訊,如果一切順利,你會看到"Found phone number"和"Found Sms Msg"兩行資訊,正如顯示的那樣。
現在一切都很好,我還希望解釋一下墓碑化Windows Phone應用程式的工作方式。不過現在,是時候開始我們的遊戲了,同時在實戰中展示一些很酷的內容。
首先, 我添加了一個助手類Logger用來記錄所有的跟蹤資訊並把它們顯示在首頁面(頁面1)的文字框中。Logger的目的向你證明你的應用程式最終被終止掉同時State字典的的確確是在工作。這個日誌類還可以讓你在模擬器的非偵錯模式下運行應用程式,同時可以擷取跟蹤資訊並顯示在日誌文字框中。
Logger類
Logger類實現了一個非常簡單的單例模式
(不過可能不是安全執行緒的)。這個類設計成單例主要是當你的應用程式結束時只有一個類的執行個體從記憶體中被移除,除此之外還有相關的事件和在不同事件中載入的資料。這個類有一個string成員log,還有一個DataTime成員用來儲存Logger對象的建立時間。通過Logger類你可以向日誌添加一些新行還可以擷取整個日誌。這非常有利於調試,這也正是Logger的職責所在。每次你使用Util類添加一個跟蹤時,你都可以將它加入到Logger中。
通常單例的實現沒有公用的建構函式。然而如果你要將這個類儲存到State子典中,這意味著Logger類必須是可序列化的,因此必須有一個公用的預設(空的)建構函式。否則你在停用或啟用程式時會得到一個異常。
為了“查看”日誌,我在MainPage.xaml.cs中加入了OnNavigatedTo並將日誌中的文本資訊載入到首頁面的logTextBox
控制項。因此每次你導航到程式的首頁面中都會看到記錄檔被列印出來。這可以使你看到程式的跟蹤資訊而不需要在VS中偵錯工具(感謝的Jaime Rodriguez
的建議)。現在來試試。
- 首先需要部署你的程式到模擬器。你可以在Visual Studio的方案總管面板中右擊你的項目,或者從產生菜單中點擊“部署”。
- 在模擬器中,點擊電話右上方白色的箭頭導航到應用程式列表。
- 你應該會看到一個非常短的應用程式列表。從列表中找到你的程式。如果你使用了本篇文章中的代碼,則應用程式的名字是LWP.AppLifeCycle。
- 應用程式啟動後,你會在首頁面文字框中看到跟蹤資訊。你可能想改變電話的設定(通過點擊扳手按鈕)來調整模擬器的縮放等級到100%。日誌文字框中的文本很小從而可以顯示儘可能多的記錄而無需捲軸。
- 在日誌文字框中你會看到應用程式建立和首頁面建構函式的跟蹤資訊。
- 點擊Next按鈕。
- 在第二頁中,輸入一個電話號碼或一些資訊。
- 然後點擊Windows鍵停用你的程式。在模擬器中,你應該會看到起始介面。
- 點擊返回鍵回到你的應用程式。這會重新啟用你的應用程式並恢複程式到最後一個瀏覽過的頁面,也就是第二頁,如果一切順利,你會看到在步驟7中輸入過的資訊。
- 在第二頁中,點擊返回鍵回到第一頁。在日誌文字框中看到的跟蹤資訊應與一致:
跟蹤資訊中最有意思的一段是在虛線中的,下面的程式碼片段來自應用程式的Activatedd事件。
if (true == PhoneApplicationService.Current.State.ContainsKey(LoggerKey))<br /> {<br /> Logger logger = (PhoneApplicationService.Current.State[LoggerKey] as Logger);<br /> long timeDef = Logger.Instance.CreationTime.Ticks - logger.CreationTime.Ticks;<br /> Util.Trace("-------------------------------------/n--> Time difference between Loggers = " + timeDef<br /> + "/n" + logger.GetLog()<br /> + "/n-------------------------------------");<br /> }
在這段代碼中,你會看到我們在State字典中檢索Logger對象。找個對象在Deactivated事件觸發時被放到了State字典中。假設Logger對象被找到,我們建立一個臨時的叫logger的對象(注意這不是我們在當前程式中真正使用的logger對象)。然後對比從字典中擷取的logger對象和剛剛啟用的應用程式中的logger對象的建立時間。正如你看到的,存在一個時間差。我們在停用程式時儲存到State字典中的logger比新logger的時間長。在代碼中你可以看出我們不能真正的初始化舊的logger;它的建立時間和日誌都被恢複並列印到文字框中。所以虛線中的每一行資訊都取決於應用程式上一次運行時停用事件的觸發情況。
虛線後的第一行顯示了第二頁的建構函式,不出所料,這表明“新的”應用程式被啟用並從我們停用的應用程式中返回到第二頁。
總結
現在你已經在實踐中見過了全部的4個事件:Launching, Deactivated, Activated和Closing。我希望你能清楚程式在何時沒有運行,在何時終止以及如果你的資料沒有儲存它在何時會丟失。並且在你返回程式時,無論觸發的是Activated事件或是Launching事件,你都會得到一個程式的新執行個體(我們的單例小實驗已經證實了這一點)。
State字典可以在Deactivated和Activated事件中儲存臨時資料,而且在我們要討論選取器和啟動器的下篇文章中它被證實是一個非常有用的工具。