65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Windows Phone 应用中本地存储数据的 3 种简便方法

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2014年4月12日

CPOL

9分钟阅读

viewsIcon

11895

无论我们的应用程序是游戏、生产力工具还是商业软件,它们都离不开数据。

无论我们的应用程序是游戏、生产力工具还是商业软件,它们都离不开数据。因此,在开发任何类型的程序时,我们大部分时间都花在解决与数据相关的问题上,例如在哪里获取数据、如何处理数据、如何向用户展示数据以及存储在哪里。在这里,我想讨论如何处理最后一个任务,只关注如何本地持久化应用程序的信息。在 Windows Phone 中,这实际上非常容易做到,尽管仍然有几种选项可供选择,而选择取决于你的数据结构的复杂程度以及你计划如何以及何时使用它们。



设置


在 Windows Phone 应用程序中存储数据的第一个方法是使用 IsolatedStorageSettings。虽然这强制实施了对开发人员的一些限制,但它同时也是可能是最广泛使用的方法。原因几乎任何应用程序都有一些设置——既有对用户可见的,也有应用程序内部使用的。IsolatedStorageSettings 实际上为你提供了一个键值存储,用于保存一些命名的值,并使它们易于访问。这种方法最适合存储简单的数据,如 boolstring,尽管借助数据契约,也可以在设置字典中保存复杂对象。下面的代码片段展示了如何将一些值放入 IsolatedStorageSettings 中:

//using System.IO.IsolatedStorage;

IsolatedStorageSettings.ApplicationSettings["DontAskForRating"] = false;
IsolatedStorageSettings.ApplicationSettings["UserName"] = "Just Someone";
IsolatedStorageSettings.ApplicationSettings["WinsCount"] = 9000;

读取设置并不比存储它们更困难。 这里需要注意的一点是,在你尝试检索值之前必须先存储它们。幸运的是,与任何字典一样,IsolatedStorageSettings 提供的字典允许检查某个键是否存在于其中:

if (IsolatedStorageSettings.ApplicationSettings.Contains("DontAskForRating"))
{
var booleanSetting = (bool)IsolatedStorageSettings.ApplicationSettings["DontAskForRating"];
}

if (IsolatedStorageSettings.ApplicationSettings.Contains("UserName"))
{
var stringSetting = (string)IsolatedStorageSettings.ApplicationSettings["UserName"];
}

if (IsolatedStorageSettings.ApplicationSettings.Contains("WinsCount"))
{
var intSetting = (int)IsolatedStorageSettings.ApplicationSettings["WinsCount"];
}

关闭应用程序时,应用程序将自动持久化更新后的值,尽管如果你想立即保存它们,也可以随时调用 Save() 方法。 为了使事情更简单、更容易维护和更具可读性,我倾向于将所有与应用程序设置的交互封装在我自己的辅助类中,该类通常看起来像这样:

class MySettings
{
private const string _DontAskForRatingKey = "dontaskforrating";
private const string _UserNameKey = "username";

private IsolatedStorageSettings AppSettings
{
get
{
return IsolatedStorageSettings.ApplicationSettings;
}
}

public bool DontAskForRating
{
get
{
if (AppSettings.Contains(_DontAskForRatingKey) == false)
{
DontAskForRating = false;
}
return (bool)AppSettings[_DontAskForRatingKey];
}

set
{
AppSettings[_DontAskForRatingKey] = value;
AppSettings.Save();
}
}

public string UserName
{
get
{
if (AppSettings.Contains(_UserNameKey) == false)
{
UserName = "Just Someone";
}
return (string)AppSettings[_UserNameKey];
}

set
{
AppSettings[_UserNameKey] = value;
AppSettings.Save();
}
}
}

在 getter 中,我检查相应的键,这样如果某个键不存在,我会先设置默认值。此外,最好将设置的键保存在一些常量中,以便它们定义在一个地方,并且可以轻松一致地更改。最后,引入像 AppSettings 这样的快捷方式,可以减少代码中的混乱,使其更易于阅读,这对我来说似乎总是一个好主意。

尽管 IsolatedStorageSettings 字典的值类型是 object,并且你可以将任何 DataContract-可序列化的内容放入其中,但将大型和复杂的实体存储在设置中并不是一个好主意。如果你决定这样做,你应该记住,应用程序的设置是在应用程序启动过程中检索的,以便在应用程序加载后即可使用。这意味着加载对象的工作越困难,应用程序的加载时间就会越长,而且你将无法控制它(如果反序列化抛出异常怎么办?)。话虽如此,最好将复杂实体移出设置——在这种情况下,你可以自己决定何时恢复或保存它们。

总的来说,根据 MSDN 的建议,IsolatedStorageSettings 最适合存储应用程序启动或关闭时所需的小块数据——在这些时刻,键值存储对你是完全可用的。如果你想了解更多关于这种方法的信息,请查看诺基亚开发者门户上的这篇文章


简单类型序列化


现在我们转向基于文件的存储。Windows Phone 通过 IsolatedStorageFile 类向开发人员提供了应用程序的独立文件系统的访问权限。这个类允许你以非常类似于编写桌面程序的方式来创建、读取、写入和删除文件。这基本上意味着你可以自由地将数据以任何可以写入传统文件的形式存储——例如,你可以自由地将文本或字节推送到通过打开文件获得的流中。同时,这种低级方法很少方便和易于维护,这就引出了序列化。在 Windows Phone 应用程序中序列化数据的实际标准是基于 DataContract-的序列化,我将在下一节介绍。现在我将坚持使用 XmlSerializer 来演示几乎任何序列化器都适合这里。 我想提供的是存储一系列内置类型——例如,一个字符串列表。这个选项, 虽然应用范围不是很广,但在某些情况下非常方便且易于实现,因为它根本不需要任何数据契约:

public void SaveStrings()
{
var quiteImportantStrings = new List<string>
{
"Audi", "BMW", "Mercedes Benz", "Porshe", "Volkswagen", "Opel"
};

using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (storage.FileExists("awesomefile.xml"))
{
storage.DeleteFile("awesomefile.xml");
}

using (var file = storage.CreateFile("awesomefile.xml"))
{
var serializer = new XmlSerializer(typeof(List<string>));
serializer.Serialize(file, quiteImportantStrings);
}
}
}

很简单,对吧?这种方法最佳用例是当你的应用程序处理一些可以用内置类型轻松 unambiguous 表示的对象时。如果因为后者以某种方式被应用程序使用,你还需要在对象和表示之间进行转换,那么它就更好了。在这种情况下,使用 DataContract 序列化将意味着不必要的开销,因此倾向于这种方法是一个很好的选择,可以避免编写额外的代码并用属性层覆盖你的对象。另一方面,与你可以用来存储对象相同表示的 IsolatedStorageSettings 相比,它具有可控执行的优势。当你的集合可能会随着时间而增长时,这一点尤其重要。所以如果你只有一些可以用字符串表示的普通列表,可以考虑这个选项,即使它显示出一定程度的 Primitive Obsession。使用 XmlSerializer 从文件加载内容并不比保存它们更难:

public IEnumerable<string> LoadStrings()
{
using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (storage.FileExists("awesomefile.xml"))
{
using (var file = storage.OpenFile("awesomefile.xml", System.IO.FileMode.Open))
{
try
{
var serializer = new XmlSerializer(typeof(List<string>));
return (List<string>)serializer.Deserialize(file);
}
catch (Exception)
{
//You'd better do something more clever here
return new List<string>();
}
}
}
else
{
return new List<string>();
}
}
}

如你所见,使用 IsolatedStorageSettingsXmlSerializer 的组合来存储东西非常容易。这种方法的用例是有限的,因为它介于使用 IsolatedStorageSettingsDataContract 序列化之间,这两种方法都非常强大,并且可以满足开发人员的大部分需求。尽管如此,这种序列化 仍然有一个利基市场——当需要存储潜在的长且不总是需要的原始对象集合时,它工作得相当好,可以避免设置 DataContract 和浪费 IsolatedStorageSettings 中的空间。


数据契约序列化


现在我们可以转向最有可能在大多数时间使用的-即 DataContract 序列化与 IsolatedStorageFile 的使用相结合。文件处理方面与我们在上一节中看到的完全相似。你只需使用 IsolatedStorageFile.FileExists(..) 方法检查特定名称的文件是否存在,使用 IsolatedStorageFile.OpenFile(..) 打开它,使用 IsolatedStorageFile.CreateFile(..) 创建它,或使用 IsolatedStorageFile.DeleteFile(..) 删除它。不同的是,要使用 DataContractSerializer 处理你自己的实体,你必须先定义 据契约。这是通过将适当的属性应用于你要保存和加载的类来完成的。类本身用 DataContractAttribute 标记,而你需要序列化的属性和字段应该带有 DataMemberAttribute

[DataContract]
public class ImportantData
{
[DataMember]
public int GamesTotal { get; set; }

[DataMember]
public double WinsPct { get; set; }

[DataMember]
public string PlayerName { get; set; }

[DataMember]
public bool IsPremiumUser { get; set; }
}

一旦你设置好了 DataContract,你就可以像处理普通集合一样轻松地序列化相应类的实例了:

//using System.Runtime.Serialization;
//using System.IO;
//using System.IO.IsolatedStorage;

public class StoreWithDataContract
{
public void SaveData(ImportantData data)
{
try
{
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (storage.FileExists("important.xml"))
{
storage.DeleteFile("important.xml");
}

using (var file = storage.CreateFile("important.xml"))
{
var serializer = new DataContractSerializer(typeof(ImportantData));
serializer.WriteObject(file, data);
}
}
}
catch (Exception)
{
//your clever error handling goes here
}
}

public ImportantData LoadData()
{
try
{
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isf.FileExists("important.xml"))
{
var serializer = new DataContractSerializer(typeof(T));
using (var file = isf.OpenFile("important.xml", FileMode.Open))
{
return (ImportantData)serializer.ReadObject(file);
}
}
}
}
catch (Exception)
{
return new ImportantData();
}

}
}

基于 DataContract 的方法的优点很明显:它允许存储相当复杂的对象——甚至是它们的整个图谱,以及在任何命令式代码和相关重复代码的情况下,在一个地方彻底控制它们的表示。 DataContractDataMember 属性确实非常方便,并且不会让类的代码变得难以阅读。此外,有几种类型的 DataContractSerializer 类,允许你选择最合适的数据格式——例如,你可以使用传统的 DataContractSerializer 来以 XML 格式存储你的信息,或者切换到 JsonDataContractSerializer,并将所有数据保存为 JSON 格式。正如你所期望的,改变序列化器不会让你以任何方式修改 DataContract(嗯,有日期,但是……)

DataContract 序列化而言,如果你有 Windows Communication Foundation (WCF) 经验,有一件事需要牢记。在 WCF 中,这种方法允许你序列化不仅是公共数据成员,还可以是受保护和私有的数据成员,这使得它比不能深入检查对象内部的 XmlSerializer 更有用。然而,Windows Phone 的情况有所不同,在这里 DataContractSerializer 无法轻松访问受保护和私有数据。尽管我觉得这有时很受限制,但它使得人们能够更好地分离接口和实现,从而提高我们软件的质量。

那么...


这些是我想要介绍的 3 种选项。同时,至少还有一种技术在某些情况下效果更好:那就是使用嵌入在你应用程序中的数据库。虽然有大量的引擎可供选择,但我相信 SQLite 是 最受欢迎的。这里有一篇文章,介绍了在你的应用程序中设置它的过程。最后,虽然你很可能需要某种本地存储并使用上述方法之一来实现它,但你仍然可以决定将大部分数据移到外部存储——基于云的存储或运行在你个人服务器上的数据库。工具和方法几乎有无限的选择,所以你一定能找到最适合你需求的工具,但这超出了本文的范围。

为了帮助你进一步了解,有来自 #wpdevfusion 活动的 非常棒的幻灯片,由 Matteo Pagani 制作,它们比我更简洁地比较了存储 Windows Phone 应用数据的方法。Matteo 还在他的 博客 中拥有大量的 Windows Phone 开发材料——如果你为这个伟大的平台开发应用程序,我强烈推荐它。

请分享你对保留 Windows Phone 应用程序数据的最佳方式的看法。我将非常感谢你对我的描述的这些方法的建议或评论——欢迎随时告诉我我可能犯的任何错误。
© . All rights reserved.