天天看點

Windows Phone開發(六)-- 多任務之墓碑機制

目前的智能手機,硬體上已經可以媲美幾年前的PC機了,1G記憶體,512M以上記憶體,3.5以上的螢幕,3G,WIFI等等都成為了新的手機的最低标準。而Windows Phone也一改以往WM手機硬體差異大的問題,設定了最低的硬體标準。相對于以前系統,性能上,操作上,流暢度上也有了很大提高。但是電池的發展遠遠跟不上手機的耗電量。大的也就1500MA的電池,最多也就使用1天多,大部分每天都用充電。為了節約電量,各個平台的手機都推出了一些省電的措施。

對于Windows Phone來說,剛推出時和Iphone第一版一樣,不支援多任務,一方面是為了給前台程式提供更多的資源,更流暢的體驗,另一方面也是為了介紹電池的消耗。同僚采用了消息推送機制來完成一部分背景操作,也使用了一種名為墓碑機制來對實作所謂的了“僞多任務”。  到了最新的“芒果”系統,已經支援了多任務,但也不同于以往WM說或PC上的多任務,也改進了墓碑機制,加快了程式間的切換。我們這一篇文章就了解下Windows Phone平台的僞多任務。

一 僞多任務

在我們使用PC和WM系統時,多任務對我們來說是理所應當的。一邊聽歌,一邊上QQ,還能上微網誌發照片。可到了Windows Phone上卻不支援多任務了,對于一個新的智能系統來說,确實無法讓人接受。但是多任務存在一個問題就是如果開的任務過多,系統用起來就會比較卡,在WM系統中這種情況比較常見。為了提供使用者的體驗,目前的一些智能系統都放棄了這種傳統的多任務方式,而采用了僞多任務,有限制的多任務和消息推送這些方法。

所謂的僞多人他并不是真正的多任務,隻是通過一些處理,讓使用者使用起來感覺不到和多任務有什麼差別。在Windows Phone中采用了墓碑機制來實作僞多任務。墓碑這個名字也很恰當。當我們運作了一個程式後,切換到另一個程式,這時因為是單任務,第一個程式會被終止掉,但是采用墓碑機制,儲存了程式目前的狀态和資料,當我們在切換回的時候,雖然是啟動了一個新的執行個體,但是可以通過墓碑來恢複到之前的狀态,是以使用者感覺不到有什麼差別。

墓碑機制是系統自動完成的,但是資料的儲存和恢複則是我們通過代碼控制的。另外墓碑機制也不是萬能的,因為此時程式是終止的,如果有一些下載下傳,播放,計時的功能就無法通過墓碑機制完成到了。Windows Phone 7則通過推送機制來解決部分問題,而WP7.1 SDK中也提供了一些背景操作來解決這些問題,這一篇文章我們主要介紹墓碑機制。

二 程式執行模型

在深入了解墓碑機制之前我們必須弄清楚程式的執行模型。下面這個圖就是Windows Phone 7.1中最新的執行模型。此圖來自于MSDN,但是他的圖有個錯誤,修改了下。

Windows Phone開發(六)-- 多任務之墓碑機制

對于一個Windows Phone程式來說,他有啟動---運作---休眠---墓碑---退出等5個狀态,狀态切換之間存在一些事件和方法。下面就具體介紹一個程式從啟動到終止的過程。

1 啟動一個程式

我們啟動一個程式有多種方法,最普遍的就是在第一屏界面點選程式的Tile圖示或者是在第二屏中點選程式圖示,另外我們可以通過點選一個推送的Toast消息或者通過Launchers 和 Choosers來啟動程式,這些後面會介紹。在啟動程式是,會觸發Application的Launching事件。這個事件我們前面提到過,他是PhoneApplicationService類中注冊的事件,除此之外好包含了三個和程式狀态相關的事件。

程式啟動會會建立一個新的執行個體,為了保證程式能過正常快速的啟動,我們不應該在Launching事件中執行過多的代碼,比如讀取檔案,網絡連接配接等等,這樣會印象使用者體驗,我們可用另起一個線程來執行這些操作。

2 程式運作

程式啟動以後就進入了運作狀态,程式從啟動到進入第一個頁面的過程我們在前面讨論頁面導航的文章中詳細介紹過了。通過從配置檔案獲得要加載的第一個XAML頁面,讀取XAML檔案并生成新的執行個體,通過Frame顯示出這個Page。我們知道在進入一個Page時會執行OnNavigatedTo方法。當這些完成後,程式才真正進入了運作狀态。而OnNavigatedTo方法對于墓碑機制來說非常重要,我們一般在這個方法中進行一些資料恢複的操作。

3 休眠狀态

這個狀态是在WP7.1中新加入的一個狀态,是為了提升多個任務間的切換速度。當我們在程式中點選Win鍵進入到主界面,或者是在程式中使用了Launchers 和Choosers啟動了另一個程式時就會發生(不是所有都會發生)。休眠狀态時,程式停止運作,但是整個程序還是存在于記憶體中。當恢複這個程式時,就不需要建立一個新的執行個體。這樣就加快了程式恢複和切換的速度。而且從休眠狀态恢複時我們一不需要去恢複資料。而在WP7.1中,我們可以長按Back按鈕,出現程式清單,然後選擇要前台執行的程式。

在切換到其他程式,進入到休眠狀态之前,會調用目前頁面的OnNavigatedFrom方法,在這個方法中我們可以儲存目前頁面的一些資料狀态;然後會觸發Application中的Deactivated事件,在這個事件中,我們可以儲存一些目前程式的資料。我們知道,因為程序資源還儲存在記憶體中,是以目前台程式使用時,記憶體不足或者不足以讓程式流暢運作,這時系統就會執行一些操作來釋放記憶體,此時程式就可能從休眠狀态變換為下面介紹的墓碑狀态。

4 墓碑狀态

如上面說的,在WP7.1中,隻有系統資源不夠前台程式使用時,背景程式才可能從休眠狀态進入到墓碑狀态。這也是WP7.1相對于WP7.0最大的改進。在WP7.0中,系統沒有休眠狀态,仍和背景程式都是直接進入到墓碑狀态,而OnNavigatedFrom方法和Deactivated事件都是在進入墓碑轉台前觸發的。一個程式進入到墓碑狀态時,他的程序被終止掉,但是程式的回退棧中的資訊,以及我們儲存的一些資訊會保留在記憶體中。

為了保證系統資源和性能,Windows Phone對墓碑狀态也做了一些限制。目前系統中之允許同時存在5個墓碑程式。當超過了這個數量或者因為前台程式需要更多的資源,那麼系統就會完全終止掉我們的程式,包括閃存記憶體中儲存的回退棧和資料資訊。是以對于我們而言,好需要對這種情況進行處理。

5 程式恢複

當我們使用Back按鈕或者長按Back從任務清單中傳回程式,或者是從Launchers 或Choosers傳回時,程式就會恢複執行。但是如圖上看到的,我們程式可能是從休眠狀态,也可能是從墓碑狀态傳回的。如果是從休眠狀态傳回時我們不需要做任何恢複的操作,而從墓碑程式中傳回,我們就需要恢複程式的狀态和資料,以便使用者感覺程式是被重寫建立了。

程式恢複時會觸發Application類中的Activated 事件,我們可以通過檢查IsApplicationInstancePreserved參數來判斷程式是從休眠狀态還是墓碑狀态傳回的,在此方法中我們可以用來恢複之前在Deactivated事件中儲存的資料。如果是從休眠狀态恢複,不會重新執行構造函數,而從墓碑狀态恢複時會重新執行構造函數。因為Back Stack在墓碑狀态中還是儲存在記憶體中的,是以這是會傳回到我們程式退出時所在的頁面,在進入到頁面之前還是會執行OnNavigatedTo方法,對于墓碑狀态恢複的程式,我們可以在這個方法中來恢目前頁面的狀态資料。

6 程式結束

在Windows Phone的silverlight架構程式中,我們唯一退出程式的方法就是點選Back鍵,當Back Stack中不存在頁面時程式就會退出,目前系統沒有提供任何Exit方法來以代碼的方式結束程式。在程式退出時會觸發Application中最後一個事件,Closing。在這個事件中我們可以釋放一些使用的資源,儲存資料等等,要注意的是,如果一個程式從墓碑狀态被結束,是不會觸發此事件的。MSDN上說關閉程式的時間被限制在10s,超過這個時間程式會被終止掉。

三 資料儲存和恢複

至此,我們對于Windows Phone的執行模型也有了一定了解,即便WP7.1中引入了休眠方式,但是程式還是可能進入到墓碑模式或則被終止掉。是以無論什麼情況,我們都要對資料進行儲存和恢複。不過我們可以通過一些系統狀态來判斷是否需要進行資料儲存和恢複。

在進入到墓碑狀态時,資料會被儲存到記憶體中,系統為我們提供了資料字典來儲存我們的資料。 對于程式來說,系統的狀态和資料我們可以存放到Application.State這個字典這種,而對于單個頁面來說資料可以存放到Page.State的字典中。需要注意的是這些字典中存入的資料必須是可序列化的資料。另外我們也可以把資料儲存到隔離存儲區中。

有了資料儲存的區域下面就是資料儲存的時機,在Application相關的: Launching、Deactivated、Activated和Closing這4個事件中我們可以對系統的資料進行儲存和恢複,而在每個頁面進入和離開都會發生的OnNavigatedTo和OnNavigatedFrom,我們可以用來儲存和恢複頁面的資料。

1 單頁面的情況

Windows Phone開發(六)-- 多任務之墓碑機制
Windows Phone開發(六)-- 多任務之墓碑機制

我們看個列子,Demo5中我們建立了2個頁面。在MianPage中有一個TextBox和三個按鈕。點選Add時,TextBox的值會增加(預設是1),而點選Next按鈕會導航到Page1頁面,點選Run Camera按鈕會調用相機。代碼運作環境是WP7.1 SDK Beta2。我們在Application的四個事件和頁面的2個方法中隻加入運作的Log資訊。

public MainPage()
{
   Debug.WriteLine("MainPage Constructor");
   camera = new CameraCaptureTask();
   camera.Completed += new EventHandler<PhotoResult>(camera_Completed);
   num = 1;
   InitializeComponent();
   txtNum.DataContext = num;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
   txtNum.DataContext = ++num;
   Debug.WriteLine("Add Method,TextBox is {0}", num.ToString());
}
           

我們點選Add讓TextBox值增加。然後點選Win鍵回到住界面,然後點選Back傳回程式,并繼續點Add,程式Log資訊如下:

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Application_Deactivated
Application_Activated
MainPage OnNavigatedTo
Add Method,TextBox is 5
Add Method,TextBox is 6
           

很明顯,因為在WP7.1下,程式進入了休眠狀态,是以我們TextBox不會被清空。讓我們在模拟器上來模仿一下墓碑狀态的情況,需要進行如下設定就可以了。

Windows Phone開發(六)-- 多任務之墓碑機制

然後我們再次運作程式,結果如下:

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Application_Deactivated
The thread '<No Name>' (0xec600c2) has exited with code 0 (0x0).
The thread '<No Name>' (0xee100aa) has exited with code 0 (0x0).
The thread '<No Name>' (0xe6a00ae) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
           

很明顯看到,在執行完Deactivated事件後程式的線程都結束了,而但我點選Back按鈕後,重新加載了程式,并且執行了MainPage的構造函數,而我們TextBox的值也從1開始了。下面看看如何來儲存這些資料。對代碼修改如下:

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    State["num"] = num; //儲存變量
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    num = (int)State["num"]; //恢複變量
}
           

我們在MainPage的兩個方法中儲存和恢複資料,但是運作卻出錯了。因為OnNavigatedTo中的State["num"]為空。因為我們第一次進入頁面是也會執行這個方法,這時還沒有儲存過num,是以就出錯了。在WP7.0中我們可以使用 State.ContainsKey或者State.TryGetValue來避免這種錯誤的發聲,任何時候我們從字典中讀取資料時都必須檢查資料的有效性。而在WP7.1中我們有更好的辦法:

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
    {
        Debug.WriteLine("Save data");
        State["num"] = num; //儲存變量
      }
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
     Debug.WriteLine("MainPage OnNavigatedTo");
     base.OnNavigatedTo(e);
     if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New)
     {
         Debug.WriteLine("Recover data");
         object objNum;
         if (State.TryGetValue("num", out objNum))
         {
             num = (int)objNum;
         }
     }
     txtNum.DataContext = num;
}
           

我們在看看結果:

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xfa800f2) has exited with code 0 (0x0).
The thread '<No Name>' (0xf9500e6) has exited with code 0 (0x0).
The thread '<No Name>' (0xe3700ee) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 5
Add Method,TextBox is 6
           

資料被正常恢複了,我們可以看到,OnNavigatedTo方法雖然被執行了兩次,但是Recover data隻有在從墓碑或休眠狀态傳回時才執行,而在建立時是沒有執行的。因為在WP7.1中開放了NavigationEventArgs 的NavigationMode屬性。當我們建立一個頁面進入時,此屬性為New,而當我們從墓碑狀态或其他頁面傳回時則是Back,很明顯我們隻有在Back時才需要恢複資料。而在OnNavigatedFrom方法中,當我們導航到新的頁面時,此屬性為New,而當我們回退到前一個頁面時,此屬性為Back,我們知道Windows Phone中隻有Back Stack,是以從一個頁面傳回到前一個頁面時,這個頁面就會被回收,是以就沒有必要儲存資料了。而在WP7中我們每次都要進行儲存和恢複操作。

但是這裡還有一個問題,就是當我們從休眠狀态傳回時,是不需要進行資料恢複的,但是此方法還是會被執行,在WP7.1中同樣提供了方法,可以在休眠狀态傳回時不執行這些操作。我們在Application的Activated 事件中可以判斷,我們修App代碼如下:

public static bool IsTombstoning { get; set; }

private void Application_Activated(object sender, ActivatedEventArgs e)
{
   Debug.WriteLine("Application_Activated");
   if (e.IsApplicationInstancePreserved)
   {
       IsTombstoning = false;
   }
   else
   {
       IsTombstoning = true;
   }
}
           

WP7.1中新增了IsApplicationInstancePreserved屬性來判斷是休眠狀态還是墓碑狀态恢複的。我們在App中定義了一個靜态的IsTombstoning 屬性。在MainPage的OnNavigatedTo方法中使用就行了。有一點要注意的是不要在App的構造函數中對IsTombstoning 指派,否則從墓碑狀态傳回後會執行構造函數,另外也不要在頁面中定義一個變量來訓示是否是新的執行個體,因為你需要儲存或恢複這個變量。取消Debug中的墓碑調試,我們看看結果。

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Add Method,TextBox is 4
Add Method,TextBox is 5
Add Method,TextBox is 6
MainPage OnNavigatedFrom
Save data
Application_Deactivated
Application_Activated
MainPage OnNavigatedTo
Add Method,TextBox is 7
           

2 多頁面的情況

前面已經了解了單頁面情況下的資料恢複,那麼如果我們從首頁面切換到其他頁面,這時進入了休眠或墓碑狀态又會怎麼樣呢?因為這是我們目前已經不是在MainPage頁面了。還是在Demo5中,我們點選Add後,點選Next按鈕,進入到Page1,然後點Win鍵,進入主菜單。在Page1中我們沒有進行資料存儲和恢複,我們隻關心MainPage中的資料。我們就看看墓碑情況,運作結果如下:

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Main Pgee Navigate to Page1
Page1 Constructor
MainPage OnNavigatedFrom
Save data
Page1 OnNavigatedTo
Page1 OnNavigatedFrom
Application_Deactivated
The thread '<No Name>' (0xf88012a) has exited with code 0 (0x0).
The thread '<No Name>' (0xe220156) has exited with code 0 (0x0).
The thread '<No Name>' (0xf1d014a) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
Page1 Constructor
Page1 OnNavigatedTo
Bace to MainPage
MainPage Constructor
Page1 OnNavigatedFrom
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4
           

事實證明,我們前面寫的代碼依舊有效,沒有進行任何修改就完成了任務。我們看到在導航到Page1之前就進行了資料儲存。因為OnNavigatedFrom不僅在進入墓碑狀态前回執行,在離開目前頁面時也會執行。是以,即便是在Page1中進入了墓碑狀态,當我們傳回程式,并傳回到MainPage頁面是,還是會執行OnNavigatedTo方法來恢複資料,這時的頁面Mode也是Back。是以我們不需要為多頁面做特殊處理。

我們考慮下,如果從Page1頁面不是GoBack,而是使用Navigate方法重新導航到MainPage呢?這有什麼關系,隻不過是建立了一個頁面的執行個體,我們前一個MainPage還在Back Stack中。那麼資料會恢複到這個MainPage中嗎,當然不會,我們使用的State屬性是每個頁面執行個體特有的。那我我如果想在每個建立的MainPage中都能繼續進行Add操作呢?

對于這種變态的需求,一般我是懶得做,肯定是你設計有問題,但是這裡還是示範下如何實作。除了使用Page的State,前面我們還提到使用Application的State,這個是所有頁面共享的。我們在Page中增加一個New按鈕,然後修改MainPage的方法:

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    Debug.WriteLine("Save data");
    PhoneApplicationService.Current.State["num"] = num; //儲存到全局
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    Debug.WriteLine("Recover data");
    object objNum;
    if (PhoneApplicationService.Current.State.TryGetValue("num", out objNum))
    {
        num = (int)objNum;
        txtNum.DataContext = num;
    }
}
           

為了一個變量能在不同執行個體中同時起作用,并公用,我們修改了頁面的方法。這裡儲存和恢複資料時都不在判斷是否是New或Back,因為任何情況我們都要儲存和恢複,以便在所有頁面中使用。另外我們可以使用隔離資料區來存儲和恢複。結果就是,無論我們在Page1頁面從墓碑傳回後, New一個MainPage,還是Back,或者是先New然後在傳回到前一個MainPage,結果都是在任何頁面都能對這個資料繼續的使用。

當然更好的辦法是在App中定義一個全局的變量,這樣我們不需要在Page的頁面中對這些資料進行儲存和恢複,隻需要在Appliation的事件中進行。這樣代碼會很簡潔。

public static int Number { get; set; }

private void Application_Activated(object sender, ActivatedEventArgs e)
{
   Debug.WriteLine("Application_Activated");
   if (e.IsApplicationInstancePreserved)
   {
       IsTombstoning = false;
   }
   else
   {
       IsTombstoning = true;
       Number = (int)PhoneApplicationService.Current.State["number"];
   }
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
     Debug.WriteLine("Application_Deactivated");
     PhoneApplicationService.Current.State["number"] = Number;
}
           

App中代碼修改如上,我們在Activated和Deactivated中進行資料恢複和儲存,因為Activated必定是在Deactivated執行後執行,是以這裡沒有判斷是否存在。但還是應該寫的嚴謹一些。下面是MainPage中的代碼:

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedFrom");
    base.OnNavigatedFrom(e);
    App.Number = num;
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    Debug.WriteLine("MainPage OnNavigatedTo");
    base.OnNavigatedTo(e);
    num = App.Number;
    txtNum.DataContext = num;
}
           
代碼是非常的簡單,如果不是為了掩飾儲存和恢複,我們甚至可以直接對App.Number來操作。相比前一種方法,代碼簡潔了很多。結果這裡就不貼了。
           

3 使用Chooser的情況

在程式中我們可以使用Launcher或者Chooser來運作系統提供的API功能,比如打電話,發短信,調用相機等等。具體的使用我們後面會介紹。一般使用這些API都會導緻當期那程式變為背景程式。是以也存在資料恢複的問題,但是對于Chooser來說,他會傳回資料,而Lanucher不會傳回資料,那麼這個時候就存在一個新得到的資料和被儲存的資料我們應該用哪一個的問題。

看下面的列子,我們調用相機,傳回後得到圖檔的大小,把大小填入TextBox。

private CameraCaptureTask camera;

public MainPage()
{
   num = 1;
   camera = new CameraCaptureTask();
   camera.Completed += new EventHandler<PhotoResult>(camera_Completed);
   InitializeComponent();
   txtNum.DataContext = num;
}
           

我們先定義相機的Chooser,注意這裡要定義成類變量,而不能是局部變量,并且設定完成事件的處理方法。因為如果是局部變量,當系統進入到墓碑狀态在傳回時,這個對象就沒有了,就沒辦法執行完成事件了,對于Chooser來說也沒有辦法獲得得到傳回的資料了。

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    Debug.WriteLine("Run Camera");
    try
    {
       camera.Show();
    }
    catch (System.InvalidOperationException ex)
    {
       MessageBox.Show(ex.Message);
    }
}

void camera_Completed(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        BitmapImage bmp = new BitmapImage();
        bmp.SetSource(e.ChosenPhoto);
        num = bmp.PixelHeight;
    }
     else
    {
         num = 1024; //模拟器沒法拍照
     }
}
           

以上我們點選Run Camera按鈕時,就調用Show方法調出,拍完照片後吧照片的高度設定到TextBox,因為模拟器拍不了,就手動設定到1024。頁面的資料我們采用第一種,單頁面的儲存方式。

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
Application_Activated
Set carmera num
MainPage OnNavigatedTo
Add Method,TextBox is 1025
           

從上面結果可見程式調用相機時進入休眠狀态 ,傳回後先執行了完成函數,Set carmera num後資料變成了1024。因為是休眠不需要恢複狀态,是以在此加1後變為1025。我們在看看從墓碑狀态傳回的結果。

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xf3802a2) has exited with code 0 (0x0).
The thread '<No Name>' (0xe0a02da) has exited with code 0 (0x0).
The thread '<No Name>' (0xea002da) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
Set carmera num
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4
           

我們發現從墓碑狀态傳回後,雖然也進行了Set carmera num,但是還進行了Recover data,最新得到資料被之前恢複的資料覆寫了。我們看到傳回後先執行了頁面的構造函數,然後執行了相機的完成方法,這裡要注意的是,不要在這裡操作控件,因為此時Load事件還沒有執行,這裡操作控件會抛出一個空引用異常。

獲得的資料和儲存的資料間的選擇是比較容易碰到的問題。我們要根據自己的邏輯來選擇解決的方法,這裡我們使用最新的資料來代替儲存的資料。是以修改恢複資料的部分:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
   Debug.WriteLine("MainPage OnNavigatedTo");
   base.OnNavigatedTo(e);

    //單頁面情況
     if (App.IsTombstoning)
    {
       if (e.NavigationMode != System.Windows.Navigation.NavigationMode.New)
       {
          if (num == 1)
          {
            Debug.WriteLine("Recover data");
            object objNum;
            if (State.TryGetValue("num", out objNum))
            {
               num = (int)objNum;
            }
          }
       }
    }
    txtNum.DataContext = num;
}
           

我們知道如果進入到墓碑狀态傳回後,num的資料會丢失,而我們在構造函數中把num設定為1。因為在調用OnNavigatedTo之前會執行方法相機的傳回方法camera_Completed。如果num被設定為1024就不應該恢複資料。是以我們可以在OnNavigatedTo中通過num的值來判斷是否恢複,如果是1就需要恢複,如果不是就說明有新的值。

這裡需要注意,如果在 camera_Completed中得到的資料就是1,那麼還是會被恢複,是以一定根據情況來。這裡隻是舉例子。另外我們要注意構造函數中num初始化和camera.Completed事件綁定之間的順序,一定要先初始化資料,最後在注冊完成事件,否則執行完camera_Completed方法後,num又被初始化了。

下面看看執行結果:

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
Run Camera
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xe83037a) has exited with code 0 (0x0).
The thread '<No Name>' (0xeb50372) has exited with code 0 (0x0).
The thread '<No Name>' (0xf53033a) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
Set carmera num
MainPage OnNavigatedTo
Add Method,TextBox is 1025

           

和之前的差別就在于這裡沒有恢複資料,最後使用的是相機得到的資料。對于沒有調用相機,而是點選Win導緻的程式恢複,這時恢複的資料,結果正确

Application_Launching
MainPage Constructor
MainPage OnNavigatedTo
Add Method,TextBox is 2
Add Method,TextBox is 3
MainPage OnNavigatedFrom
Save data
Application_Deactivated
The thread '<No Name>' (0xebe0382) has exited with code 0 (0x0).
The thread '<No Name>' (0xeb203be) has exited with code 0 (0x0).
The thread '<No Name>' (0xec103d6) has exited with code 0 (0x0).
'UI Task' (Managed): Loaded 'C:\Program Files\Microsoft.NET\SDK\CompactFramework\v2.0\WindowsCE\mscorlib.dll'
'UI Task' (Managed): Loaded 'System.Windows.RuntimeHost.dll'
'UI Task' (Managed): Loaded 'System.dll'
'UI Task' (Managed): Loaded 'System.Windows.dll'
'UI Task' (Managed): Loaded 'System.Net.dll'
'UI Task' (Managed): Loaded 'System.Core.dll'
'UI Task' (Managed): Loaded 'System.Xml.dll'
'UI Task' (Managed): Loaded '\Applications\Install\F5BE083B-4EC0-4192-84B0-EE83323B8240\Install\Demo5.dll', Symbols loaded.
'UI Task' (Managed): Loaded 'Microsoft.Phone.dll'
'UI Task' (Managed): Loaded 'Microsoft.Phone.Interop.dll'
'UI Task' (Managed): Loaded 'System.Runtime.Serialization.dll'
Application_Activated
MainPage Constructor
MainPage OnNavigatedTo
Recover data
Add Method,TextBox is 4
           

四 禁用墓碑

如果你擁有一台真機,在更新到到芒果之前,或許總是要忍受恢複程式時的"Resuming"的提示。當然也不是沒有辦法,網上就流傳了修改系統資料庫來禁用墓碑狀态的方法。首先你需要有一台已經越獄的機器,然後下載下傳一個RegistryEditor的XPA軟體包。運作這個軟體,把\HKLM\Software\Microsoft\TaskHost\DehydrateOnPause的鍵值從3修改為0。再試試,程式切換的很快了,"Resuming"基本也不出現了。我買手機的時候Mango的Beta版已經出了,而且系統資料庫也已經被賣家修改好了。是以在手機上還沒見到"Resuming"就更新到Mango了。在我更新到7712之後,偶爾卻會出現"Resuming",是以決定研究下修改這個系統資料庫的作用。

搜尋了好久,終于找到了一篇相關的文章:Tombstoning, Dehydration, and Windows Phone  。從文章中我們可以了解到,這個鍵值有4個選項:

  1. Don’t dehydrate
  2. Forcefully dehydrate
  3. Gracefully dehydrate
  4. Automatically decide

表示程式在暫停時是否執行Dehydrate,Dehydrate願意是脫水。在進入墓碑狀态後,一些系統級别的操作會停止,運作環境開始回收資源,其中就包括.NET運作時,回收完以後整個程式的運作環境也被回收了。這樣一個過程就叫做Dehydrate。

系統預設是3,表示指令程式的運作環境在合适的時候優雅的執行Dehydrate。但是目前并不清楚是如何決定是否合适。但是對運作環境來說預設是執行Dehydrate,但是也可以選擇不進行Dehydrate。

HRESULT SHSetAutoDehydratingHostEligibility(BOOL fEligible);
           

通過這個方法可以設定程式運作環境是否進行Dehydrate。當我們選擇2時,系統總是會執行Dehydrate,而選擇1時,系統會給程式發送一個WM_CLOSE消息來強制,理論上來說我們可以捕獲這個消息,來自己決定是否執行Dehydrate。而當設定為0是,Dehydrate被禁用了,也就是我們程式的運作環境不會被回收,.NET運作時,程式的執行個體都不會被銷毀。而在我們恢複程式是,主要是建立新的執行個體和.NET運作時,導緻了出現"Resuming"。

是以我們修改系統資料庫為0後,系統看起來就系象7.1中的休眠狀态一樣了。而且程式恢複也不會建立新的執行個體,是以并不清楚和WP7.1的休眠有什麼差別。也有可能7.1的休眠也是采用這種方式,預設不執行Dehydrate,而在資源不足時執行Dehydrate進入墓碑。這樣在恢複時會有Resuming字樣,而在WP7.0中不執行Dehydrate,當資源不夠時程式會被結束,是以不會出現Resuming字樣。這是我個人的猜測。在Mango中用不了系統資料庫工具,沒法檢視。也不知道此鍵值是否還效或者是否有修改。

另外系統規定隻能有5個程式進入墓碑狀态,我在真機上看了下,最多也隻能有5個第三方的程式運作,而不管是休眠還是墓碑狀态。當你開第六個程式時,最早使用的程式就會被關閉,大家可以長按Back按鈕來觀察。

五 總結

通過上面介紹我們發現對于我們程式而言,墓碑機制中對資料的儲存和恢複是我們需要關注的地方。我們通過三種情況,介紹了對頁面已經程式資料的儲存和恢複方法,以及決定是否恢複資料的一般方法。了解了程式的執行模型。其中Page中的OnNavigatedTo和OnNavigatedFrom是最重要的方法。另外所有儲存的資料必須可以被序列化。

下面是微軟關于執行模型的最佳時間方法:http://msdn.microsoft.com/zh-cn/library/ff817009.aspx

其中介紹了一些文章中沒有涉及的部分,比如當程式從墓碑模式被終止的時候需要儲存資料到隔離區。是以我們應該在Deactivated事件中就進行這樣的操作,因為我們無法預料程式可能進入的狀态。另外有一點要指出,當我們程式被切換至背景,進入到休眠或墓碑狀态時,此時我們點選程式圖示,重新啟動一個執行個體時,之前的執行個體的資料都無法恢複。想解決這個問題隻能把資料存放到隔離區,然後恢複。但是會出現歡迎界面,對于有登陸界面的的還會出現登陸界面,我們需要進行更多的處理,才能讓使用者感覺不到是新開的程式。另外其中還建議Application中的事件以及頁面中的兩個方法操作的時間不要超過10s,否則程式會被終止。但是我嘗試了下使用Thread.Sleep(15000),程式并沒有被終止。

Demo5代碼下載下傳:http://download.csdn.net/source/3500401

繼續閱讀