第 17 章 存储与加载本地文件

请参考教材,全面理解和完成本章节内容... ...

复制工程ch16,将工程目录改名为ch17.

在手机上完全退出你的“陋习手记”App(不是把应用隐藏起来),再重新执行“陋习手记”App,哇!我的之前的手记哪里去了?

几乎所有应用都需要有个地方存储数据。本章,我们将升级CriminalIntent应用,实现保存并加载存储在设备上的JSON文件数据。

Android设备上的所有应用都拥有一个独立的文件系统,应用程序只能在自己的文件系统区域内存储和读取文件,不可以去其它地方访问,此区域被称为沙盒。所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等。将文件保存在沙盒中可阻止其他应用的访问、甚至是其他用户的私自窥探(当然,要是设备被root了的话,则用户可以随意获取任何数据)。

提示

沙盒也叫沙箱sandbox,且多用于计算机安全技术。其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。沙盒中的程序和文件所做的任何改动对操作系统不会造成任何损失。这种技术被计算机技术人员广泛使用.

每个应用的沙盒目录都是设备/data/data目录的子目录,且默认以应用包命名。例如,CriminalIntent应用的沙盒目录全路径为:/data/data/com.jet.criminalintent。

好消息是,应用开发时,不必在内存中存放应用的沙盒目录路径。需要知道路径时,我们可直接调用Android API中的便利方法来获取它。

除沙盒目录外,应用也可将文件保存在外部存储介质上,如常用的SD存储卡等。一般来说设备并不内置SD卡,因此需用户自行配置。虽然文件甚至整个应用都可以存储到SD卡上。但出于安全考虑,通常不推荐这么做。这其中最重要的一个因素就是,外部存储上的数据存取并不仅仅局限于应用本身,也就是说,任何人都可以读取、写入以及删除这些数据。

17.1 CriminalIntent 应用的数据存取

为应用添加数据持久存储功能主要涉及两大处理过程:将数据保存至文件系统、以及应用启动时重新加载保存的数据。每个处理过程又分为两个步骤。保存数据时,首先将数据转换为可保存格式(如JSON数据格式),然后将数据写入文件;读取数据时,则刚好相反,首先从文件中读取格式化的数据,然后将其解析为应用所需的内容。

提示:

JSONJavaScript Object NotationXML是一种比较流行的数据交换格式

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成。

采用JSON的格式的数据,采用一对"名称/值“的形式,简单如:

{"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"}

如果用它保存数组,优势更明显,如:

{

"people":[

{"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},

{"firstName":"Jason","lastName":"Hunter","email":"bbbb"},

{"firstName":"Elliotte","lastName":"Harold","email":"cccc"}

]

}

JSON适用于webservices接口服务的开发。Android SDK内置了标准的org.json类包,我们可以利用其中的类和方法来创建和解析JSON文件。要了解更多有关org.json包的信息,可查阅Android开发者文档。也可访问网址http://json.org,了解更多有关JSON数据交换格式的内容;而XML是另一种数据交换格式,可用来格式化数据以便写入文件。同样,Android提供了创建和解析XML文件的类和方法。

CriminalIntent应用中,保存的数据格式是JSON。我们将使用Context类的I/O方法写入或读取文件。图17-1总体描绘了应如何实现CriminalIntent应用数据的保存与读取。

image

17-1 CriminalIntent应用的数据存取

应用读取文件的最便捷方式是使用Context类的I/O方法。这些方法可以返回标准的Java类实例,如java.io.File和java.io.FileInputStream。(Context类几乎是所有关键应用组件的超类,常见的几个应用组件有:ApplicationActivityService。)

17.1.1 保存crime数据到JSON文件

在CriminalIntent应用中,CrimeLab类将负责触发数据的保存与加载,而创建和解析JSON数据的工作则交由新的CriminalIntentJSONSerializer类以及当前的Crime类处理。

创建CriminalIntentJSONSerializer

新的CriminalIntentJSONSerializer类负责读取Crime数组列表中的数据,然后进行数据格式转换,最后写入JSON文件。

创建CriminalIntentJSONSerializer类,参照代码清单17-1输入实现代码(注: 代码中toJSON()方法会产生错误,因为稍后我们才会在Crime类中实现该方法。暂时忽略它)。

代码清单17-1 编码实施CriminalIntentJSONSerializer

image

有个问题,需要讨论:

Q:为什么不将对象序列化放到CrimeLab类中完成,而是单独放到CriminalIntentJSONSerializer类中实现?

A:虽然对象序列化也可以直接在CrimeLab类中完成,但将其封装到独立的单元会有诸多优点:

  • 首先,对应用中其他代码部分的依赖度较低,独立封装类更容易进行单元测试。
  • 其次,CriminalIntentJSONSerializer类的构造方法可接受Context实例参数。这意味着该类不做任何修改就可以在多处复用,因为使用者只需实现任意一个Context类作为参数传入即可。

saveCrimes(ArrayList<Crime>)方法中,应首先创建一个JSONArray数组对象。然后针对数组列表中的所有crime记录调用toJSON()方法,并将结果保存到JSONArray数组中。

要打开文件并写入数据,需使用Context.openFileOutput()方法。该方法接受文件名以及文件操作模式参数,会自动将传入的文件名附加到应用沙盒文件目录路径之后,形成一个新路径,然后在新路径下创建并打开文件,等待数据写入。如选择手动获取私有文件目录并在其下创建和打开文件,记得总是使用Context.getFilesDir()替代方法。不过,如需创建不同使用权限的文件,还是少不了要使用openFileOutput()方法。

新建文件打开后,即可使用标准的Java接口类来写入数据。这里,我们使用了java.io类包中的三个类:WriterOutputStreamOutputStreamWriter。整个过程大致描述如下:

  • 首先调用openFileOutput()方法获得OutputStream对象,
  • 然后用其创建一个新的OutputStreamWriter对象,
  • 最后调用OutputStreamWriter的写入方法写入数据。

至于Java的Strings与最终写入文件的原始字节流之间的转换,则不必担心,OutputStreamWriter会搞定一切。

实现Crime类的JSON序列化功能

为了以JSON文件格式保存mCrimes数组,首先必须能以JSON文件格式保存单个Crime实例对象。在Crime.java中,添加下列常量,然后实现toJSON()方法,以JSON格式保存Crime对象,并返回可放入JSONArrayJSONObject类实例,如代码清单17-2所示。

代码清单17-2 实现toJSON()方法(Crime.java)

image

以上代码中,使用JSONObject类中的方法,我们将Crime对象数据转换为可写入JSON文件的JSONObject对象数据。

CrimeLab类中保存crime记录

有了CriminalIntentJSONSerializer类以及支持JSON序列化的Crime类,现在可将crime列表转换为JSON格式,并保存到文件中。

什么时“点”保存数据合适呢?适用于移动应用的一个普遍规则是:尽可能频繁地保存数据,尤其是用户数据修改行为发生时。既然修改crime记录后的数据更新都需CrimeLab类处理,那么最靠谱的就是在该类中将数据保存到文件中。

如果数据保存过于频繁,会拖慢应用的运行,影响到用户的使用体验。我们的代码中,数据只要有更新,都是重新将全部crime数据写入文件中。考虑到CriminalIntent应用的规模,这样做不会太耗时。然而,对于超频繁数据保存的应用来说,应考虑采用某种方式只保存修改过的数据,而不是每次都保存全部数据,比如说使用SQLite数据库等。后几章我们将学习如何在应用中使用SQLite数据库。

在CrimeLab.java中,在类构造方法里创建一个CriminalIntentJSONSerializer实例。然后再添加一个序列化crime对象的saveCrimes()方法。同时,为确认文件保存操作是否成功,再添加一些相应的日志记录代码。如代码清单17-3所示。

代码清单17-3 在CrimeLab类中进行数据持久保存(CrimeLab.java)

image

简单起见,CriminalIntent应用只记录错误信息并输出至控制台。实际开发时,如文件保存失败,最好考虑采用某种方式直接提醒用户,例如,使用Toast或对话框。

onPause()方法中保存应用数据

应该在哪里调用saveCrimes()方法呢?onPause()生命周期方法是最安全的选择,如代码清单17-4所示。为什么不选择onStop()或者onDestroy()方法?前面我们讲过,操作系统需要回收内存时,会销毁暂停的activity,因此不应考虑它们,否则将会失去保存数据的机会。

代码清单17-4 在onPause()方法中保存数据(CrimeFragment.java)

image

运行CriminalIntent应用。添加一两条crime记录,然后点击Home键暂停activity,保存crime记录列表到文件中。最后查看LogCat确认成功与否。

17.1.2 从文件中读取crime数据

现在我们来进行逆向操作,实现应用启动后,从文件中读取crime数据。首先,在Crime.java中,添加一个接受JSONObject对象的构造方法,如代码清单17-5所示。

代码清单17-5 实现Crime(JSONObject)方法(Crime.java)

image

然后,在CriminalIntentJSONSerializer.java中,添加一个从文件中加载crime记录的loadCrimes()方法,如代码清单17-6所示。

代码清单17-6 实现loadCrimes()方法(CriminalIntentJSONSerializer.java)

image

以上代码可以看到,联合使用Java、JSON类,以及ContextopenFileInput(...)方法,我们从文件中读取数据并转换为JSONObjects类型的string,然后再解析为JSONArray,接着再解析为ArrayList,最后返回获得的ArrayList

注意,在finally代码块中,应调用reader.close()方法。这样,即使发生错误,也可以保证完成底层文件句柄的释放。

最后,在CrimeLab的构造方法中,在应用首次访问单例对象时,代替总是创建空的crime数组列表,将crime数据加载到ArrayList数组列表中。在CrimeLab.java中,完成相应的代码修改,如代码清单17-7所示。

代码清单17-7 加载crime记录(CrimeLab.java)

image

以上代码中,我们首先尝试加载crime数据。如加载失败,则新建一个空数组列表。

现在,CriminalIntent应用可以保存应用启停间的数据了。我们可模拟一些不同场景进行测试。运行应用,添加几条crime记录,或修改现有记录,然后切换到其他应用,如网络浏览器。此时,CriminalIntent应用很可能会被操作系统关闭。重新启动它,检查更新的数据是否已保存。也可测试强制关闭应用,然后从Eclipse中重新启动应用的场景。

现在可以放心地记录各种令人讨厌的办公室陋习了。既然应用已可靠地实现了数据持久化,后续CriminalIntent应用的功能升级过程中,可直接使用已保存的crime记录。从此,我们再也不需要在每次应用启动后,反反复复地添加crime记录了。

原文地址:https://www.cnblogs.com/jlxuqiang/p/4758656.html