第四章 数据存储

  • 5种数据存储方式的特点
  • 使用文件存储数据
  • 使用SharedPreferences存储数据
  • XML文件的序列化与解析

大部分应用程序都会涉及到数据存储,Android程序也不例外。Android中的数据存储方式有5种,分别为文件存储、SharedPreferences、SQLite数据库、ContentProvider以及网络存储,每种方式都有其不同的特点。

  • 文件存储:以I/O流形式把数据存入手机内存或者SD卡,可以存储大量数据,如音乐、图片或者视频等。
  • SharedPreferences:它本质上是一个XML文件,以Map形式存入手机内存中。常用于存储较简单的参数设置,如登录帐号密码的存储、窗口功能状态的存储等,使用起来简单、方便。
  • SQLite数据库:SQLite是一个轻量级、跨平台的数据库。数据库中所有信息都存储在单一文件中,占用内存小,并且支持基本SQL语法,是项目中经常被采用的一种数据存储方式,通常用于存储用户信息等。
  • ContentProvider:又称为内容提供者,是Android四大组件之一,以数据库形式存入手机内存,可以共享自己的数据给其他应用使用。相对于其他对外共享数据的方式而言,ContentProvicer统一了数据访问方式,使用起来更规范。
  • 网络存储:把数据存储在服务器上,不存储在本地,使用的时候直接从网络获取,避免了手机端信息丢失以及其他的安全隐患。

需要注意的是,Android中应用程序存储的数据都属于应用私有,如果要将程序的私有数据分享给其他应用程序,可以使用文件存储、SharedPreferences以及ContentProvider,推荐使用ContentProvider。

一、文件存储

1. 文件存储简介

文件存储是Android中最基本的一种数据存储方式,它与Java中的文件存储类似,都是通过I/O流的形式把数据原封不动地存储到文件中。不同的是,Android中的文件存储分为内部存储和外部存储。

(1) 内部存储

内部存储是指将应用程序中的数据以文件方式存储到设备的内部存储空间中(该文件位于data/data/packagename/files/目录下)。

内存存储方式存储的文件为其所创建的应用程序所私有,如果其他应用程序要操作本应用程序中的文件,需要设置权限。

当创建的应用程序被卸载时,其内部存储文件也随之被删除。

内部存储可以使用Context提供的openFileOutput(String name, int mode)方法和openFileInput(String name)方法分别获取FileOutputStream对象和FileInputStream对象。

  • openFileOutput() 用于打开应用程序中对应的输出流,将数据存储到指定的文件中;
  • openFileInput() 用于打开应用程序中对应的输入流,用于从文件中读取数据。

其中参数name表示文件名,mode表示文件的操作模式,也就是读写文件的方式,它的取值有4种:

  • MODE_PRIVATE:该文件只能被当前程序读写,默认的操作方式;
  • MODE_APPEND:该文件的内容可以追加,常用的一种方式;
  • MODE_WORLD_READABLE:该文件的内容可以被其他程序读取,安全性低,通常不使用;
  • MODE_WORLD_WRITEABLE: 该文件的内容可以被其他文件程序写入,安全性低,通常不使用。

使用FileOutputStream对象将数据存储到文件中的示例代码

String saveinfo = et_info.getText().toString().trim();
FileOutputStream fos;
try {
    fos = openFileOutput("data.txt", Context.MODE_APPEND);
    fos.write(saveinfo.getBytes());
    fos.close();
} catch (Exception e) {
    e.printStackTrace();
}

使用FileInputStream对象从文件中读取数据的示例代码

String content = "";
try {
    FileInputStream fis = openFileInput("data.txt");
    byte[] buffer = new byte[fis.available()];
    fis.read(buffer);
    content = new String(buffer);
} catch (Exception e) {
    e.printStackTrace();
}

案例:Demo0401,存储用户信息,用文件存储信息以及从文件中读取信息

(2) 外部存储

外部存储是指将文件存储到一些外围设备上(该文件通常位于mnt/sdcard目录下,不同厂商生产的手机这个路径可能会不同),例如SD卡或者设备内嵌的存储卡,属于永久性的存储方式。

外部存储的文件可以被其他应用程序所共享,当将外围存储设备连接到计算机时,这些文件可以被浏览、修改和删除,因此这种方式不安全。

由于外围存储设备可能被移除、丢失或者处于其他状态,因此在使用外围设备之前必须使用Environment.getExternalStorageState()方法来确认外围设备是否可用,当外围设备可用并且具有读写权限时,就可以通过FileInputStream、FileOutputStream或者FileReader、FileWriter对象来读写外围设备中的文件。

向外围设备(SD卡)中存储数据的示例代码

String state=Environment.getExternalStorageState();
if(state.equals(Environment.MEDIA_MOUNTED)){
    File SDPath=Environment.getExternalStorageDirectory();
    File file=new File(SDPath, "data.txt");
    String data="Hello World";
    FileOutputStream fos;
    try{
        fos=new FileOutputStream(file);
        fos.write(data.getBytes());
        fos.close();
    }catch(Exception e){
        e.printStackTrace();
    }
}

上述代码中,使用了Environment.getExternalStorageDirectory()方法获取SD卡根目录的路径。由于不同手机厂商SD卡的根目录路径可能不一致,用这种方法可以避免把路径写死而找不到SD卡

从外围设备(SD卡)中读取数据的示例代码

String state=Environment.getExternalStorageState();
if(state.equals(Environment.MEDIA_MOUNTED)){
    File SDPath=Environment.getExternalStorageDirectory();
    File file=new File(SDPath, "data.txt");
    FileInputStream fis;
    try{
        fis=new FileInputStream(file);
        BufferedReader br=new BufferedReader(new InputStreamReader(fis));
        String data=br.readLine();
    }catch(Exception e){
        e.printStackTrace();
    }
}

需要注意的是,操作SD卡中数据时,需要在清单文件的节点中配置权限信息,指定应用程序具有读或写SD卡中数据的权限。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

2. XML序列化与解析

文件存储有很多方式,XML就是其中一种。XML存储的数据结构比较清晰,应用比较广泛。

(1) XML序列化

序列化是将对象状态转换为可保持或可传输状态的过程。在序列化对象时,需要使用XmlSerialize序列化器(XmlSerializer类),它可以将I/O流中传输的对象变得像基本类型数据一样,实现传输的功能,序列化后的对象以XML形式保存。

对象序列化的示例代码

要将数据序列化,首先要创建与XML文件相对应的序列化器(XmlSerializer),然后将对象转化为XML。

File file = new File(Environment.getExternalStorageDirectory(), "person.xml");
FileOutputStream os = new FileOutputStream(file);

XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(os, "UTF-8"); // 设置XML文件编码方式
serializer.startDocument("UTF-8", true); // 写入XML文件标志,即<?xml version="1.0" encoding="utf-8" standalone="yes"?>
serializer.startTag(null, "persons");
int count = 0;
for (Person person : userData) {
    serializer.startTag(null, "person");
    serializer.attribute(null, "id", count + "");

    //将Person对象的name属性写入XML文件
    serializer.startTag(null, "name");
    serializer.text(person.getName());
    serializer.endTag(null, "name");

    //将Person对象的age属性写入XML文件
    serializer.startTag(null, "age");
    serializer.text(String.valueOf(person.getAge()));
    serializer.endTag(null, "age");

    //将Person对象的score属性写入XML文件
    serializer.startTag(null, "score");
    serializer.text(String.valueOf(person.getScore()));
    serializer.endTag(null, "score");

    serializer.endTag(null, "person");
    count++;
}
serializer.endTag(null, "persons");
serializer.endDocument();
serializer.flush();
os.close();

序列化后的XML文件如下所示:

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<persons>
    <person id="0">
        <name>王0</name>
        <age>100</age>
        <score>80</score>
    </person>
    <person id="1">
        <name>王1</name>
        <age>99</age>
        <score>79</score>
    </person>
    <person id="2">
        <name>王2</name>
        <age>98</age>
        <score>78</score>
    </person>
</persons>
  • 在XML文件中,带有<>的是开始标签,serializer.startTag()写入开始标签;带有</>的是结束标签,用serializer.endTag()写入结束标签。
  • 使用serializer.attribute()向标签写入属性数据;使用serializer.text()向节点写入节点数据。
  • XML文档的开始和结束分别用serializer.startDocument()和serializer.endDocument()来表示

案例:Demo0402,XML序列化

(2) XML解析

若要操作XML文档,首先需要将XML文档解析出来。通常情况下,解析XML文档有三种方式,分别是SAX解析、DOM解析和PULL解析。

SAX解析

SAX解析会逐行扫描XML文档,当遇到标签时触发解析处理器,采用事件处理的方式解析XML。

它在读取文档的同时即对XML进行处理,不必等到文档加载结束,相对快捷。而且也不需要将整个文档加载进内存,因此不存在占用内存的问题,可以解析超大XML。

但是,SAX解析只能用来读取XML中的数据,无法进行增删改。

DOM解析

DOM(Document Object Mode)解析式一种基于对象的API,它会将XML文件的所有内容以文档树的形式存放在内存中,然后允许使用DOM API遍历XML树、检索所需的数据,这样便能根据树的结构以节点形式对文件进行操作。

使用DOM操作XML的代码看起来是比较直观的,而且在编码方面比SAX解析更加简单。

但是,由于DOM需要将整个XML文件以文档树的形式存放在内存中,消耗内存比较大。因此,较小的XML文件可以采用这种方式解析,但较大的文件不建议采用这种方式解析。

PULL解析

PULL解析器是一个开源的Java项目,既可以用于Android项目,也可以用于JavaEE程序。

Android已经集成了PULL解析器,因此,在Android中最常用的解析方式就是PULL解析。使用PULL解析XML文档,首先要创建XmlPullParser解析器,该解析器提供了很多属性,通过这些属性可以解析出XML文件中的各个节点内容。

XmlPullParser的常用属性:

  • XmlPullParser.START_DOCUMENT:XML文档的开始,如<?xml version=”1.0” encoding=”utf-8”?>
  • XmlPullParser.END_DOCUMENT:XML文档的结束。
  • XmlPullParser.START_TAG:开始节点,在XML文件中,除了文本之外,带有尖括号<>的都是开始节点,如< weather>。
  • XmlPullParser.END_TAG:结束节点,带有</>都是结束节点,如< /weather>。

使用PULL解析器的步骤:

  • 1) 通过调用Xml.newPullParser() 得到一个XmlPullParser对象。
  • 2) 通过parser.getEventType() 获取当前的事件类型。
  • 3) 通过while循环判断当前操作事件类型是否为文档结束,是则跳出while循环。
  • 4) while循环中通过switch语句判断当前事件类型是否为开始标签,是则获取该标签中的内容。

案例:Demo0403,天气预报,演示XML解析

二、SharedPreferences

SharedPreferences是Android平台上一个轻量级的存储类,主要用于存储一些应用程序的配置参数,例如用户名、密码、自定义参数的设置等。

SharedPreferences中存储的数据是以key/value对的形式保存在XML文件中,该文件位于data/data/packagename/shared_prefs文件夹中。

需要注意的是,SharedPreferences中的value值只能是float、int、long、boolean、String、StringSet类型数据。

使用SharedPreferences类存储数据时,首先需要通过context.getSharedPreferences(String name, int mode)获取SharedPreferences的实例对象(在Activity中可以直接使用this代表上下文,如果不是在Activity中则需要传入一个Context对象获取上下文):

SharedPreferences sp=context.getSharedPreferences(String name, int mode);

在该代码中,name表示文件名,mode表示文件的操作模式,该模式有多个值可供选择:

  • MODE_PRIVATE:指定该SharedPreferences中的数据只能被本应用程序读写。
  • MODE_APPEND:该文件的内容可以追加。
  • MODE_WORLD_READABLE:指定该SharedPreferences中的数据可以被其他应用程序读。
  • MODE_WORLD_WRITEABLE:指定该SharedPreferences中的数据可以被其他应用程序写。

如图4-1所示,SharedPreferences提供了一系列方法用于获取应用程序中的数据,但需要注意的是,SharedPreferences对象本身只能获取数据,并不支持数据的存储和修改。


图 4-1

数据的存储和修改需要通过SharedPreferences.Editor对象实现。要想获取Editor实例对象,需要调用SharedPreferences.Editor.edit()方法。


图 4-2

向SharedPreferences对象中存储数据的示例代码

使用SharedPreferences存储数据时,需要先获取SharedPreferences对象,通过该对象获取到Editor对象,然后通过Editor对象的相关方法(如图4-2所示)存储数据。

SharedPreferences sp=getSharedPreferences("config", MODE_PRIVATE);
Editor editor=sp.edit();             //获取编辑器
editor.putString("name", "value1");  //存入String类型数据
editor.putInt("age", 8);             //存入int类型数据
editor.commit();                     //提交修改

从SharedPreferences对象中获取数据的示例代码

创建SharedPreferences对象,使用该对象获取相应key的值。

SharedPreferences sp=context.getSharedPreferences();
String data=sp.getString("name", "");

从SharedPreferences对象中删除数据的示例代码

与存储数据类似,先要获取到Editor对象,然后通过该对象删除数据,最后提交。

SharedPreferences sp=getSharedPreferences("config", MODE_PRIVATE);
Editor editor=sp.edit();             //获取编辑器
editor.remove("name");               //删除一条数据
editor.clear();                      //删除所有数据
editor.commit();                     //提交修改

注意:SharedPreferences使用很简单,但一定要注意以下几点:

  • 存入数据和删除数据时,一定要在最后使用editor.commit()方法提交修改
  • 获取数据的key值与存入数据的key值的数据类型要一致,否则查找不到数据。
  • 保存SharedPreferences的key值时,可以用静态变量保存,以免存储、删除时写错了,如 private static final String key=”key1”;

案例:Demo0404,QQ登陆,演示使用SharedPreferences存储数据

分享到 评论