保存瞬态数据
假设你在SimplestKeypad程序中输入了一个重要的数字,并且你的系统崩溃了 - 可能是打了电话。 稍后,您关闭手机,有效终止该程序。
下次运行SimplestKeypad时会发生什么? 你以前输入的长串数字是否应该被丢弃? 还是应该看起来好像该程序从您最后离开的状态恢复? 当然,对于像SimplestKeypad这样的简单演示程序来说并不重要,但在一般情况下,用户希望移动应用程序能够准确记住他们上次与程序进行交互时所做的事情。
出于这个原因,Application类支持两个帮助程序保存和重新存储数据的工具:
Application的Properties属性是一个包含字符串键和对象项的字典。 该字典的内容在应用程序中断之前自动保存,下次应用程序运行时保存的内容可用。
Application类定义了三个名为OnStart,OnSleep和OnResume的受保护虚拟方法,并且由Xamarin.Forms模板生成的App类覆盖了这些方法。 这些方法可帮助应用程序处理所谓的应用程序生命周期事件。
要使用这些工具,您需要确定应用程序需要保存的信息,以便在终止和重新启动后恢复其状态。 通常,这是应用程序设置的组合,例如颜色和字体大小,用户可能有机会设置瞬态数据,例如半键输入字段。 应用程序设置通常适用于整个应用程序,而瞬态数据对应用程序中的每个页面都是唯一的。 如果此数据的每个项目都是属性字典中的条目,则每个项目都需要一个字典密钥。 但是,如果程序需要保存大型文件(如文字处理文档),则不应使用属性字典,而应直接访问平台的文件系统。 (这是第20章“异步和文件I / O”的工作。)
另外,您应该将与Properties一起使用的数据类型限制为.NET和C#支持的基本数据类型,例如string,int和double。
SimplestKeypad程序只需要保存一个瞬时数据项,而字典键“displayLabelText”似乎是合理的。
有时程序可以使用属性字典来保存和检索数据,而不涉及应用程序生命周期事件。 例如,SimplestKeypad程序确切知道displayLabel的Text属性何时更改。 它只发生在两个Clicked事件处理程序中的数字键和删除键。 这两个事件处理程序可以将新值存储在属性字典中。
但是请等待:属性是Application类的一个属性。 我们是否需要保存App类的实例,以便SimplestKeypadPage中的代码可以访问字典? 不,这是没有必要的。 应用程序定义一个名为Current的静态属性,返回Application类的当前应用程序的实例。
要将标签的Text属性存储在字典中,只需在SimplestKeypad中两个Clicked事件处理程序的底层添加以下行:
Application.Current.Properties["displayLabelText"] = displayLabel.Text;
如果字典中尚不存在displayLabelText键,请不要担心:属性字典实现了通用的IDictionary接口,该接口明确定义了索引器,以便在键已经存在的情况下替换上一个项,或者将新项添加到 字典如果密钥不存在。 这种行为正是你想要的。
SimplestKeypadPage构造函数可以通过以下代码初始化Label的Text属性来完成,该代码从字典中检索项目:
IDictionary<string, object> properties = Application.Current.Properties;
if (properties.ContainsKey("displayLabelText"))
{
displayLabel.Text = properties["displayLabelText"] as string;
backspaceButton.IsEnabled = displayLabel.Text.Length > 0;
}
这是您的所有应用程序需要执行的操作:只需在“属性”字典中保存信息并进行修改即可。 Xamarin.Forms本身负责保存和加载平台特定应用程序存储中的词典内容。
但是,通常情况下,应用程序以更具结构化的方式与属性字典进行交互会更好,以下是应用程序生命周期事件的起始地点。 这些是由Xamarin.Forms模板生成的App类中出现的三种方法:
public class App : Application
{
public App()
{
…
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
最重要的是OnSleep呼叫。 通常,当应用程序不再命令屏幕并且变为不活动时(除了可能已经启动的某些后台作业外),应用程序进入睡眠模式。 在这种睡眠模式下,应用程序可以恢复(通过OnResume调用发出信号)或终止。 但是这很重要:在OnSleep调用之后,没有进一步的通知表明应用正在被终止。 OnSleep呼叫尽可能接近终止通知,并始终在终止之前。 例如,如果您的应用程序正在运行并且用户关闭手机,则应用程序会在手机关机时收到OnSleep呼叫。
实际上,对OnSleep的调用总是在程序定义之前有一些例外:崩溃的程序不会首先获得OnSleep调用,但您可能期望这样。 但是您可能无法预料到这种情况:当您调试Xamarin.Forms应用程序并使用Visual Studio或Xamarin Studio停止调试时,程序将在没有预先调用OnSleep的情况下终止。 这意味着当你在调试使用这些应用程序生命周期事件的代码时,你应该习惯使用手机本身来让程序进入休眠状态,恢复程序并终止它。
当您的Xamarin.Forms应用程序正在运行时,在手机或模拟器上触发OnSleep呼叫的最简单方法是按手机的主页按钮。 然后,您可以将程序带回前台,并通过从主菜单(在iOS设备或Android设备上)选择应用程序,或通过按下后退按钮(在Android和Windows Phone设备上)来触发OnResume呼叫。
如果您的Xamarin.Forms程序正在运行,并且您通过按下iOS设备上的主页按钮两次,按下Android设备上的多任务按钮(或通过按住较旧的Android设备上的主页按钮) 按住Windows Phone上的后退按钮 - 应用程序将获得OnSleep呼叫。 如果您选择该程序,应用程序会在恢复执行时收到OnResume调用。 如果您通过在iOS设备上向上滑动应用程序的图像或通过点击Android和Windows Phone设备上的应用程序图像右上角的X来终止应用程序 - 则程序会停止执行,而不会再发出通知。
因此,这里有一条基本规则:当应用程序调用OnSleep时,应确保Properties字典包含有关要保存的应用程序的所有信息。
如果您仅将生命周期事件用于保存和恢复程序数据,则不需要执行OnResume方法。 当您的程序进行OnResume调用时,操作系统已准备好自动恢复程序内容和状态。 如果您愿意,可以使用OnResume作为清除属性字典的机会,因为在程序终止之前可以确保获得另一个OnSleep调用。 但是,如果程序已与Web服务建立连接(或正在建立此类连接),则可能需要使用On?Resume来恢复该连接。 也许在该程序处于非活动状态的时间间隔内连接已经超时。 或者可能有一些新的数据可用。
在程序开始运行时,将属性字典中的数据恢复到您的应用程序时,您可以拥有一定的灵活性。 当Xamarin.Forms程序启动时,您必须在可移植类库中执行一些代码的第一个机会是App类的构造函数。 那时,属性字典已经填充了来自特定于平台的存储的保存数据。 下一个执行的代码通常是应用程序构造函数中实例化的应用程序中第一个页面的构造函数。 应用程序(和应用程序)中的OnStart调用紧接着,然后在页面类中调用名为OnAppearing的可覆盖方法。 您可以在启动过程中随时检索数据。
应用程序需要保存的数据通常位于页面类中,但OnSleep覆盖位于App类中。 所以不知何故页面类和App类必须通信。 一种方法是在页面类中定义一个OnSleep方法,该方法将数据保存到属性字典中,然后从App中的OnSleep方法调用页面的OnSleep方法。 这种方法适用于单页面应用程序,实际上,Application类有一个名为MainPage的静态属性
在App构造函数中设置,OnSleep方法可以使用该方法访问该页面 - 但对于多页应用程序来说,它几乎不起作用
这里有一个不同的方法:首先在App类中定义所有需要保存为公共属性的数据,例如:
public class App : Application
{
public App()
{
…
}
public string DisplayLabelText { set; get; }
…
}
然后页面类(或类)可以方便地设置和检索这些属性。 在实例化页面之前,App类可以从其构造函数中的属性字典中恢复任何此类属性,并且可以将属性存储在OnSleep覆盖中的属性字典中。
这是PersistentKeypad项目采取的方法。 该程序与最简单的键盘相同,只是它包含保存和恢复键盘内容的代码。 这里是App类,它维护一个公共的DisplayLabelText属性,保存在OnSleep覆盖中并加载到App构造函数中:
namespace PersistentKeypad
{
public class App : Application
{
const string displayLabelText = "displayLabelText";
public App()
{
if (Properties.ContainsKey(displayLabelText))
{
DisplayLabelText = (string)Properties[displayLabelText];
}
MainPage = new PersistentKeypadPage();
}
public string DisplayLabelText { set; get; }
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
Properties[displayLabelText] = DisplayLabelText;
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
为避免拼写错误,App类将字符串字典键定义为常量。 它与属性名称相同,只是它以小写字母开头。 请注意,DisplayLabelText属性在实例化PersistentKeypadPage之前设置,以便它在PersistentKeypadPage构造函数中可用。
具有更多项目的应用程序可能希望将它们合并到名为AppSet关联的类中(例如),将该类序列化为XML或JSON字符串,然后将该字符串保存在字典中。
PersistentKeypadPage类在其构造函数中访问该DisplayLabelText属性,并在其两个事件处理函数中设置属性:
public class PersistentKeypadPage : ContentPage
{
Label displayLabel;
Button backspaceButton;
public PersistentKeypadPage()
{
…
// New code for loading previous keypad text.
App app = Application.Current as App;
displayLabel.Text = app.DisplayLabelText;
backspaceButton.IsEnabled = displayLabel.Text != null &&
displayLabel.Text.Length > 0;
}
void OnDigitButtonClicked(object sender, EventArgs args)
{
Button button = (Button)sender;
displayLabel.Text += (string)button.StyleId;
backspaceButton.IsEnabled = true;
// Save keypad text.
App app = Application.Current as App;
app.DisplayLabelText = displayLabel.Text;
}
void OnBackspaceButtonClicked(object sender, EventArgs args)
{
string text = displayLabel.Text;
displayLabel.Text = text.Substring(0, text.Length - 1);
backspaceButton.IsEnabled = displayLabel.Text.Length > 0;
// Save keypad text.
App app = Application.Current as App;
app.DisplayLabelText = displayLabel.Text;
}
}
测试使用属性字典和应用程序生命周期事件的程序时,您需要偶尔从手机或模拟器中卸载该程序。 从设备上卸载程序也会删除所有存储的数据,因此下次从Visual Studio或Xamarin Studio部署程序时,程序会遇到空字典,就像它第一次运行一样。