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程序来说,他有启动—运行—休眠—墓碑—退出等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 单页面的情况

 

 

我们看个列子,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不会被清空。让我们在模拟器上来模仿一下墓碑状态的情况,需要进行如下设置就可以了。

然后我们再次运行程序,结果如下:

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

0

如果本文对您有帮助,可以扫描下方二维码打赏!您的支持是我的动力!
微信打赏 支付宝打赏

3 评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注