《第一行Android代码》六十 第 6 章 数据存储全方案——详解持久化技术

 

 第 6 章 数据存储全方案——详解持久化技术

任何一个应用程序,其实说白了就是在不停地和数据打交道,我们聊QQ、看新闻、刷微博,所关心的都是里面的数据,没有数据的应用程序就变成了一个空壳子,对用户来说没有任何实际用途。那么这些数据都是从哪来的呢?现在多数的数据基本都是由用户产生的,比如你发微博、评论新闻,其实都是在产生数据。

 

而我们前面章节所编写的众多例子中也有用到各种各样的数据,
例如第3章最佳实践部分在聊天界面编写的聊天内容,
第5章最佳实践部分在登录界面输入的账号和密码。
这些数据都有一个共同点,即它们都属于瞬时数据。
那么什么是瞬时数据呢?就是指那些存储在内存当中,有可能会因为程序关闭或其他原因导致内存被回收而丢失的数据。
这对于一些关键性的数据信息来说是绝对不能容忍的,谁都不希望自己刚发出去的一条微博,刷新一下就没了吧。
那么怎样才能保证一些关键性的数据不会丢失呢?
这就需要用到数据持久化技术了。
6.1 持久化技术简介

数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失。
保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的,
持久化技术则提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换。

 

持久化技术被广泛应用于各种程序设计的领域当中,而本书中要探讨的自然是Android中的数据持久化技术。
Android系统中主要提供了3种方式用于简单地实现数据持久化功能,
即文件存储、SharedPreferences存储以及数据库存储。
当然,除了这3种方式之外,你还可以将数据保存在手机的SD卡中,
不过使用文件、SharedPreferences或数据库来保存数据会相对更简单一些,而且比起将数据保存在SD卡中会更加地安全。

 

那么下面我就将对这3种数据持久化的方式一一进行详细的讲解。

6.2 文件存储

文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合用于存储一些简单的文本数据或二进制数据。
如果你想使用文件存储的方式来保存一些较为复杂的文本数据,就需要定义一套自己的格式规范,这样可以方便之后将数据从文件中重新解析出来。

 

那么首先我们就来看一看,Android中是如何通过文件来保存数据的。

 

6.2.1 将数据存储到文件中

Context 类中提供了一个openFileOutput() 方法,可以用于将数据存储到指定的文件中。
这个方法接收两个参数,第一个参数是文件名,在文件创建的时候使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/<package name>/files/目录下的。
第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。
其实文件的操作模式本来还有另外两种:MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,这两种模式表示允许其他的应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危险,很容易引起应用的安全性漏洞,已在Android 4.2版本中被废弃。

 

openFileOutput () 方法返回的是一个FileOutputStream 对象,得到了这个对象之后就可以使用Java流的方式将数据写入到文件中了。以下是一段简单的代码示例,展示了如何将一段文本内容保存到文件中:

 

public void save() {

    String data = "Data to save";

    FileOutputStream out = null;

    BufferedWriter writer = null;

    try {

        out = openFileOutput("data", Context.MODE_PRIVATE);

        writer = new BufferedWriter(new OutputStreamWriter(out));

        writer.write(data);

    } catch (IOException e) {

        e.printStackTrace();

    } finally {

        try {

            if (writer != null) {

                writer.close();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }


如果你已经比较熟悉Java流了,理解上面的代码一定轻而易举吧。这里通过openFileOutput() 方法能够得到一个FileOutputStream 对象,然后再借助它构建出一个OutputStreamWriter 对象,接着再使用OutputStreamWriter 构建出一个BufferedWriter 对象,这样你就可以通过BufferedWriter 来将文本内容写入到文件中了。

 

下面我们就编写一个完整的例子,借此学习一下如何在Android项目中使用文件存储的技术。
首先创建一个FilePersistenceTest项目,并修改activity_main.xml中的代码,如下所示
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

 

    <EditText

        android:id="@+id/edit"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:hint="Type something here"

        />

 

</LinearLayout>
这里只是在布局中加入了一个EditText,用于输入文本内容。其实现在你就可以运行一下程序了,界面上肯定会有一个文本输入框。然后在文本输入框中随意输入点什么内容,再按下Back键,这时输入的内容肯定就已经丢失了,因为它只是瞬时数据,在活动被销毁后就会被回收。而这里我们要做的,就是
在数据被回收之前,将它存储到文件当中。修改MainActivity中的代码,如下所示: 

public class MainActivity extends AppCompatActivity {

    private EditText edit;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        edit = (EditText) findViewById(R.id.edit);

    }

    @Override

    protected void onDestroy() {

        super.onDestroy();

        String inputText = edit.getText().toString();

        save(inputText);

    }

 

    public void save(String inputText) {

        FileOutputStream out = null;

        BufferedWriter writer = null;

        try {

            out = openFileOutput("data", Context.MODE_PRIVATE);

            writer = new BufferedWriter(new OutputStreamWriter(out));

            writer.write(inputText);

        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            try {

                if (writer != null) {

                    writer.close();

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

 

}

可以看到,首先我们在onCreate() 方法中获取了EditText的实例,然后重写了onDestroy() 方法,
这样就可以保证在活动销毁之前一定会调用这个方法。
在onDestroy() 方法中我们获取了EditText中输入的内容,并调用save() 方法把输入的内容存储到文件中,文件命名为data。
save() 方法中的代码和之前的示例基本相同,这里就不再做解释了。
现在重新运行一下程序,并在EditText中输入一些内容,
如图6.1所示。

图 6.1 在EditText中随意输入点内容

 

然后按下Back键关闭程序,这时我们输入的内容就已经保存到文件中了。
那么如何才能证实数据确实已经保存成功了呢?
我们可以借助Android Device Monitor工具来查看一下。
点击Android Studio导航栏中的Tools→Android,
(新版本的as中,大部分的设别监视器组件已经弃用,需要开启的话,要进入android-sdk/tools目录下输入monitor即可)
会看到如图6.2所示的工具列表。

图 6.2 Android工具列表

 

点击Android Device Monitor就可以打开Android Device Monitor工具了,然后进入File Explorer标签页,在这里找到/data/data/com.example.filepersistencetest/files/目录,可以看到生成了一个data文件
,如图6.3所示。(注:Android 7.0系统的模拟器可能无法正常查看File Explorer中的内容,这或许是新版模拟器的一个bug,可能会在未来的版本中修复。如果你遇到了这种情况,创建一个Android 6.0系统的模拟器即可解决。)

图 6.3 生成的data文件

然后点击图6.4中左边的按钮可以将这个文件导出到电脑上。

 

图 6.4 导入导出按钮

使用记事本打开这个文件,里面的内容如图6.5所示。

图 6.5 data文件中的内容

这样就证实了,在EditText中输入的内容确实已经成功保存到文件中了。

 

不过只是成功将数据保存下来还不够,我们还需要想办法在
下次启动程序的时候让这些数据能够还原到EditText中,因此接下来我们就要学习一下如何从文件中读取数据。

6.2.2 从文件中读取数据

类似于将数据存储到文件中,Context 类中还提供了一个openFileInput() 方法,用于从文件中读取数据。
这个方法要比openFileOutput() 简单一些,它只接收一个参数,即要读取的文件名,
然后系统会自动到/data/data/<package name>/files/目录下去加载这个文件,
并返回一个FileInputStream 对象,得到了这个对象之后再通过Java流的方式就可以将数据读取出来了。

 

以下是一段简单的代码示例,展示了如何从文件中读取文本数据:

public String load() {

    FileInputStream in = null;

    BufferedReader reader = null;

    StringBuilder content = new StringBuilder();

    try {

        in = openFileInput("data");

        reader = new BufferedReader(new InputStreamReader(in));

        String line = "";

        while ((line = reader.readLine()) != null) {

            content.append(line);

        }

    } catch (IOException e) {

        e.printStackTrace();

    } finally {

        if (reader != null) {

            try {

                reader.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

    return content.toString();

}

 

在这段代码中,
首先通过openFileInput() 方法获取到了一个FileInputStream 对象,
然后借助它又构建出了一个InputStreamReader 对象,
接着再使用InputStreamReader 构建出一个BufferedReader 对象,
这样我们就可以通过BufferedReader 进行一行行地读取,把
文件中所有的文本内容全部读取出来,
并存放在一个StringBuilder 对象中,
最后将读取到的内容返回就可以了。

 

了解了从文件中读取数据的方法,那么我们就来继续完善上一小节中的例子,
使得重新启动程序时EditText中能够保留我们上次输入的内容。
修改MainActivity中的代码,如下所示:

 

package com.example.filepersistencetest;

import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.Toast;

import com.example.filepersistencetest.R;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

private EditText edit;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit);
String inputText = load();
if (!TextUtils.isEmpty(inputText)) {
edit.setText(inputText);
edit.setSelection(inputText.length());
Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show();
}
}

public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
}

 

可以看到,在onCreate() 方法中调用load() 方法来读取文件中存储的文本内容,如果读到的内容不为null ,
就调用EditText的setText() 方法将内容填充到EditText里,
并调用setSelection() 方法将输入光标移动到文本的末尾位置以便于继续输入,
然后弹出一句还原成功的提示。
load() 方法中的细节我们在前面已经讲过,这里就不再赘述了。

 

注意,上述代码在对字符串进行非空判断的时候使用了TextUtils.isEmpty() 方法,这是一个非常好用的方法,它可以一次性进行两种空值的判断。当传入的字符串等于null 或者等于空字符串的时候,这个方法都会返回true ,从而使得我们不需要先单独判断这两种空值再使用逻辑运算符连接起来了。

 

现在重新运行一下程序,刚才保存的Content字符串肯定会被填充到EditText中,然后编写一点其他的内容,
比如在EditText中输入Hello,接着按下Back键退出程序,再重新启动程序,这时刚才输入的内容并不会丢失,
而是还原到了EditText中,如图6.6所示。

这样我们就已经把文件存储方面的知识学习完了,
其实所用到的核心技术就是Context 类中提供的openFileInput() 和openFileOutput() 方法,
之后就是利用Java的各种流来进行读写操作。

 

不过正如我前面所说,文件存储的方式并不适合用于保存一些较为复杂的文本数据,
因此,下面我们就来学习一下Android中另一种数据持久化的方式,
它比文件存储更加简单易用,而且可以很方便地对某一指定的数据进行读写操作。

 

6.3 SharedPreferences存储

不同于文件的存储方式,SharedPreferences是使用键值对的方式来存储数据的。
也就是说,当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。
而且SharedPreferences还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的;
如果存储的数据是一个字符串,那么读取出来的数据仍然是字符串。

 

这样你应该就能明显地感觉到,使用SharedPreferences来进行数据持久化要比使用文件方便很多,
下面我们就来看一下它的具体用法吧。

 

6.3.1 将数据存储到SharedPreferences中

要想使用SharedPreferences来存储数据,首先需要获取到SharedPreferences 对象。
Android中主要提供了3种方法用于得到SharedPreferences 对象。

 

01.Context 类中的getSharedPreferences() 方法

 

此方法接收两个参数,
第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存放在/data/data/<package name>/shared_prefs/目录下的。
第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,它是默认的操作模式,和直接传入0效果是相同的,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。
其他几种操作模式均已被废弃,MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE这两种模式是在Android 4.2版本中被废弃的,MODE_MULTI_PROCESS模式是在Android 6.0版本中被废弃的。

 

02.Activity 类中的getPreferences() 方法

这个方法和Context中的getSharedPreferences() 方法很相似,
不过它只接收一个操作模式参数,因为使用这个方法时会
自动将当前活动的类名作为SharedPreferences的文件名。

 

03.PreferenceManager 类中的getDefaultSharedPreferences() 方法


这是一个静态方法,它接收一个Context 参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。得到了SharedPreferences 对象之后,就可以开始向SharedPreferences文件中存储数据了,主要可以分为3步实现。

 

(1) 调用SharedPreferences 对象的edit() 方法来获取一个SharedPreferences.Editor 对象。

 

(2) 向SharedPreferences.Editor 对象中添加数据,比如添加一个布尔型数据就使用putBoolean() 方法,添加一个字符串则使用putString() 方法,以此类推。

 

(3) 调用apply() 方法将添加的数据提交,从而完成数据存储操作。

 

不知不觉中已经将理论知识介绍得挺多了,那我们就赶快
通过一个例子来体验一下SharedPreferences存储的用法吧。
新建一个SharedPreferencesTest项目,然后修改activity_main.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical" >

 

    <Button

        android:id="@+id/save_data"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="Save data"

        /> 
</LinearLayout>
这里我们不做任何复杂的功能,只是简单地放置了一个按钮,用于将一些数据存储到SharedPreferences文件当中。
然后修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Button saveData = (Button) findViewById(R.id.save_data);

        saveData.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                SharedPreferences.Editor editor = getSharedPreferences("data",

                    MODE_PRIVATE).edit();

                editor.putString("name", "Tom");

                editor.putInt("age", 28);

                editor.putBoolean("married", false);

                editor.apply();

            }

        });

    }

 

}

 

 

 

 

 

 

 

 

可以看到,这里首先给按钮注册了一个点击事件,然后在点击事件中通过getSharedPreferences() 方法指定SharedPreferences的文件名为data,并得到了SharedPreferences.Editor 对象。接着向这个对象中添加了3条不同类型的数据,最后调用apply() 方法进行提交,从而完成了数据存储的操作。

 

很简单吧?现在就可以运行一下程序了,进入程序的主界面后,点击一下Save data按钮。这时的数据应该已经保存成功了,不过为了证实一下,我们还是要借助File Explorer来进行查看。打开Android Device Monitor,并点击File Explorer标签页,然后进入到/data/data/com.example.sharedpreferencestest/shared_prefs/目录下,可以看到生成了一个data.xml文件,如图6.7所示。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值