第006天:APP的数据存储技术

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

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

6.1持久化技术简介

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

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

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

6.2文件存储

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

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

6.2.1将数据存储到文件中

        Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。 这个方法接收两个参数,第一个参数是文件名,在文件创建的时候使用的就是这个名称,注意这 里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/<package name>/files/目录下的。第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE 和M0DE_APPENDo其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候, 所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在,就往文 件里面追加内容,不存在就创建新文件。其实文件的操作模式本来还有另外两种: MODE_WORLD_READABLEMODE_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
         tif (writer != null)
            writer.close();
    } catch (IOException e) 
        }
    }
}

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

        下面我们就编写一个完整的例子,借此学习一下如何在Android项目中使用文件存储的技术。 首先创建一个FilePersistenceTest项目,并修改activity main.xml中的代油,如下所示:

<LinearLayoutxmlns;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"l
</LinearLayout>

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

public class MainActivity extends AppCompatActivity (
    private EditText edit;
    @Override
    protected void onCreate(Bundle savedlnstanceState) {
        super.onCreate(savedlnstanceState);
        setContentView(R.layout.activitymain); 
        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.MODEPRIVATE); 
        writer = new BufferedWriter(new OutputStreamWriter(out));             
        writer.write(inputText);
} 
    catch (lOException e) (
        e.printStackTrace();
}   finally {
        try {
            if (writer != null) { writer.closet);
}
}         catch (lOException e) {
            e.printStackTrace();
}

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

        然后按下Back键关闭程序,这时我们输入的内容就已经保存到文件中了。那么如何才能证 实数据确实已经保存成功了呢?我们可以借助Android Device Monitor工具来查看一下。点击 Android Studio导航栏中的Tools—>Android,会看到如图6.2所示的工具列表。

点击 Android Device Monitor 就可以打开 Android Device Monitor 工具了,然后进入 File Explorer•标签页,在这里找到/data/data/com.example.filepersistencetest/files/目录,可以看到生成了 一个data文件,如图6.3所示。

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

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

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

不过只是成功将数据保存下来还不够,我们还需要想办法在下次启动程序的时候让这些数据

能够还原到EditText中,因此接下来我们就要学习一下如何从文件中读取数据。

6.2.2从文件中读取数据

类似于将数据存储到文件中,Context类中还提供了一个0penFilelnput ()方法,用于从 文件中读取数据。这个方法要比openFileOutputO简单一些,它只接收一个参数,即要读取的 文件名,然后系统会自动到/data/data/vpackage 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 (lOException e) {
        e.printStackTrace();
} finally { if (reader != null) { try ( reader.closet); } catch (lOException e) {             
         e.printStackTrace();
}
}
}
        return content.toString();
}

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

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

public class MainActivity extends AppCompatActivity {
private EditText edit;
^Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activitymain);
edit = (EditText) findViewByld(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 (lOException e) { e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (lOException e) ( e.printStackTrace();
}
}
}
return content.toString();
}
}

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

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

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

FilePersistenceTest

He

6.6成功还原保存的内容

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

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

  1. SharedPreferences 存储

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

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

  1. 将数据存储到 SharedPreferences

中主要提供了 3种方法用于得到SharedPreferences对象。

  1. Context 类中的 getSharedPreferences()方法

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

  1. Activity 类中的 getPreferences()方法

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

  1. PreferenceManager 类中的 getDefaultSharedPreferences()方法

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

  1. 调用 SharedPreferences 对象的 edit ()方法来获取一个 SharedPreferences. Editor 对象。
  2. SharedPreferences.Editor对象中添加数据,比如添加一个布尔型数据就使用 putBooleanO方法,添加一个字符串则使用putStringO方法,以此类推。
  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_parent11 android:orientation="vertical" >

<Button

android: id=,,@+id/save_datan

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Save data"

/>

</LinearLayout>

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

public class MainActivity extends AppCompatActivity {

(QOverride

protected void onCreate(Bundle savedlnstanceState) {

super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain); Button saveData = (Button) findViewById(R.id.savedata); saveData.setOnClickListener(new View.OnClickListener() {

(aOverride

public void onClick(View v) { SharedPreferences.Editor editor = getSharedPreferences("data", MODEPRIVATE).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/sharedprefs/ 目录下,可以看到生成 了一个 data.xml 文件,如图 6.7 所示

接下来,同样是点击导出按钮将这个文件导出到电脑上,并用记事本进行查看,里面的内容 如图6.8所示。

可以看到,我们刚刚在按钮的点击事件中添加的所有数据都已经成功保存下来了,并且 SharedPreferences文件是使用XML格式来对数据进行管理的。

那么接下来我们自然要看一看,如何从SharedPreferences文件中去读取这些数据了。

  1. SharedPreferences 中读取数据

你应该已经感觉到了,使用SharedPreferences来存储数据是非常简单的,不过下面还有更好 的消息,其实从SharedPreferences文件中读取数据会更加地简单。SharedPreferences对象中 提供了一系列的get方法,用于对存储的数据进行读取,每种get方法都对应了 SharedPreferences .Editor中的一种put方法,比如读取一个布尔型数据就使用getBoolean ()方法, 读取一个字符串就使用getStringO方法。这些get方法都接收两个参数,第一个参数是键, 传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不 到对应的值时会以什么样的默认值进行返回。

我们还是通过例子来实际体验一下吧,仍然是在SharedPreferencesTest项目的基础上继续开 发,修改activity_main.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layoutwidth="matchparent" android:layout_height="match_parent" android:orientation="vertical" >

<Button

android:id="@+id/save_data"

android:layout_width="matchparent"

android:layout_height="wrap_content"

android:text="Save data"

/>

<Button

android:id="@+id/restore_data"

android:layout_width="match_parent"

android:layout_height="wrap_content" android:text="Restore data"

/>

</LinearLayout>

这里增加了一个还原数据的按钮,我们希望通过点击这个按钮来从SharedPreferences文件中 读取数据。修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

^Override

protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain);

Button restoreData = (Button) findViewById(R.id.restore_data); restoreData.setOnClickListener(new View.OnClickListener() { ©Override public void onClick(View v) {

SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE); String name = pref.getString("name","")

int age = pref.getlnt("age", 0); boolean married = pref.getBoolean("married", false); Log.d("MainActivity", "name is " + name);

Log. d ("MainActivity11, "age is " + age);

Log.d("MainActivity", "married is " + married);

}

})

}

可以看到,我们在还原数据按钮的点击事件中首先通过getSharedPreferences()方法得到 了 SharedPreferences 对象,然后分另U调用它的 getString(). getlnt ()getBoolean() 方法,去获取前面所存储的姓名、年龄和是否已婚,如果没有找到相应的值,就会使用方法中传 入的默认值来代替,最后通过Log将这些值打印出来。

现在重新运行一下程序,并点击界面上的Restore data按钮,然后查看logcat中的打印信息, 如图6.9所示。

com. example, sharedpreferencestest D/MainActivity name is Tom

com. example, sharedpreferencestest D/MainActivity age is 28

com. example, sharedpreferencestest D/MainActivity: married is false

6.9打印data.xml中存储的内容

所有之前存储的数据都成功读取出来了!通过这个例子,我们就把SharedPreferences存储 的知识也学习完了。相比之下,SharedPreferences存储确实要比

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值