Android App开发基础篇—数据存储(SP和文件)

Android App开发基础篇—数据存储(SP和文件)

    前言:Android中提供了多种数据存储技术来永久性保存应用数据,以便开发者能够根据需求选择合适的数据存储方案,例如:数据是应用的私有数据,还是可供其他应用(和用户)访问的数据,数据所占的存储空间等。Android中的数据存储方案主要有:共享首选项(SharedPreferences)内部存储(Internal Storage)外部存储(External Storage)SQLite数据库网络存储等。下面来逐一了解。

   注意:本文中所有提到的目录路径(如/data/data/包名/files之类的目录)都是使用模拟器进行测试时显示的路径,不同的手机显示出的路径可能不同。例如,使用getFilesDir()方法获取内部存储目录时,在模拟器上显示的是 /data/data/包名/files,而在博主手机(米3W)上显示的是 /data/user/0/包名/files。

一、SharedPreferences 共享首选项 

    1.定义

    SharedPreferences类提供了一个通用框架,使开发者能够以键值对的方式,永久性的保存一些原始数据类型的数据,包括:布尔值,浮点值,整型值,长整型和字符串,被保存的数据可以跨多个用户会话永久保留(即使应用已经终止)。

    2.使用

    要使用SharedPreferences,主要步骤有:

    (1)通过getSharedPreferences()或者getPreferences()方法获取SharedPreferences对象。其中,两个方法的区别在于:如果使用getSharedPreferences()方法,则保存的数据是全局性的,即在整个app应用过程中都是可获取的。当使用此方法时,系统会在/data/data/包名/shared_prefs目录下生成一个xxx.xml文件,文件名根据getSharedPreferences()传入的第一个参数而定;而使用getPreferences()方法,所保存的数据仅对保存数据的那个Activity有效,而在其他Activity中将无法获取。系统会在/data/data/包名/shared_prefs目录下生成一个Activity名.xml的文件。

    (2)通过SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象;

    (3)使用(2)中获取的对象调用putBoolean()(或者putString()putInt()等,由要保存的数据的数据类型而定)等方法添加值;

    (4)使用commit()提交,必须执行这一步。

    (5)在需要获取数据的地方,使用SharedPreferences对象的getBoolean()(或者getString()getInt()等,同样由所保存的数据的数据类型而定)等方法读取值。

    下面上一个简单示例,示例中在MainActivity中添加两个Button,一个存值,一个取值。代码如下:

package com.test.app2;

import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Button btn1 = (Button) findViewById(R.id.btn_main);
        Button btn2 = (Button) findViewById(R.id.btn2_main);
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
              //获取SharedPreferences对象,方法中两个参数的意思为:第一个name
              //表示文件名,系统将会在/dada/dada/包名/shared_prefs目录下生成
                //一个以该参数命名的.xml文件。第二个mode表示创建的模式,通过查看
                //方法注释得知,建议以0或者MODE_PRIVATE为默认值。
                SharedPreferences app2 = getSharedPreferences("app2", 0);
                //获取Editor对象
                SharedPreferences.Editor edit = app2.edit();
                //根据要保存的数据的类型,调用对应的put方法,
                //以键值对的形式添加新值。
                edit.putString("data", "testdata");
                //提交新值。必须执行,否则前面的操作都无效。
                edit.commit();
            }
        });
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //根据保存时所用的name属性,获取SharedPreferences对象
                SharedPreferences pf = getSharedPreferences("app2", 0);
                //根据数据类型,调用对应的get方法,通过键取得对应的值。
                String string = pf.getString("data", "null");
                Log.d("MainActivity", string);
            }
        });
    }
}

二、存储文件类型的数据

    当需要存储文件类型的数据时,可以将数据保存在设备的Internal Storage(内部存储),或者External Storage(外部存储)中。

    1.Internal Storage 内部存储 (文件目录:/data/data/)

    1.1 定义

    默认情况下,如果要保存的文件是应用的私有文件,应当将文件保存在设备的Internal Storage(内部存储)中。这里所说的Internal Storage(内部存储),是指系统在自身内部存储器上所分配出来的用作Internal Storage(内部存储)的分区。当用户卸载应用时,保存在Internal Storage(内部存储)中的文件也会被移除。

   1. 2.在内部存储中创建和读取私有文件(文件目录:/data/data/包名/files)

    1.2.1 创建私有文件并存入内部存储

    要创建一个私有文件并写入内部存储,可以通过以下几个步骤:

    (1)使用文件名和操作模式为参数,调用openFileOutput()方法。该方法会在/data/data/包名/files目录下新建一个文件。此方法需要两个参数:第一个参数表示要创建的文件的文件名;第二个参数表示创建文件的模式,该参数有几个可选项:Context.MODE_PRIVATEContext.MODE_APPENDContext.MODE_WORLD_READABLEContext.MODE_WORLD_WRITEABLE。各个选项的意义为:

    Context.MODE_PRIVATE:默认的文件创建模式。使用该模式创建文件,如果文件目录中已存在同名文件,则新建的文件会覆盖旧文件。并且,文件只能由创建文件的应用(或者与该应用共享同一user ID的应用)所访问。

    Context.MODE_APPEND:使用该模式创建文件,如果文件目录中已存在同名文件,则新的内容将直接被添加到旧文件的尾部,而不会新建一个文件来覆盖旧文件。

    Context.MODE_WORLD_READABLE:使其他应用对文件具有读的权限。

    Context.MODE_WORLD_WIRTEABLE:使其他应用对文件具有写的权限。

    注意:从API Level 17以后,MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE已经被弃用。从Android N(即7.x)开始,使用这两个常量会导致SecurityException。这意味着面向Android N和更高版本的应用无法按名称共享私有文件,尝试共享“file://"类型的URI将会导致FileUriExposedException。

    (2)使用(1)中返回的FileOutputStream实例,调用write()方法将数据写入文件。

    (3)调用close()方法关闭流式传输。

    例如,下面一个简单的例子:

创建一个应用私有文件,并写入数据
//文件名
String file_name = "test_file";
//写入文件的内容
String string = "hello android!!";
try {
    //调用openFileOutput,返回一个FileOutputStream
    FileOutputStream fos = openFileOutput(file_name, Context.MODE_PRIVATE);
    //调用write()方法写入数据
    fos.write(string.getBytes());
    //调用close()方法关闭流式传输
    fos.close();

} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

    1.2.2 从内部存储中读取文件  (文件目录:/data/data/包名/files)

    要从内部存储中读取文件,步骤如下:

    (1)使用要读取的文件的文件名作为参数,调用openFileInput()方法,返回一个FileInputStream实例。

    (2)调用read()方法读取文件字节。

    (3)调用close()方法关闭流式传输。

     例如,下面例子演示了从上面例子创建的test_file文件中读取数据:

从内部存储中读取文件
//用于存储数据流的byte数组
byte[] b = new byte[1024];
try {
    //调用openFileInput,返回一个FileInputStream
    FileInputStream fis = openFileInput(file_name);
    //调用read()方法,将数据读入byte数组
    int read = fis.read(b);
    //调用close()方法关闭流式传输
    fis.close();

} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

    注意:文件的读写操作是耗时操作,因此最好不要在主线程(UI线程)中执行,避免程序出现ANR(程序未响应)。并且,在每次操作结束后,要记得调用close()方法关闭对应的输入流或者输出流,以释放系统资源。

    1.3.内部存储缓存文件    (文件目录:/data/data/包名/cache)

    当有一些文件只需临时使用,不必永久保存的时候,应该把这些文件保存在内部存储的缓存目录中。保存在内部存储的缓存目录中的文件,当设备内部存储空间不足时,系统可能会首先删除这些缓存文件以释放内存空间。但是,开发者不应该依赖系统来清理这些文件,而应该始终自行维护缓存文件,使其占用的空间保持在合理的限制范围内(如1MB)。当应用被卸载时,缓存文件也会被移除。

    1.3.1 将文件保存为内部存储缓存文件

    要在内部存储缓存目录中保存缓存文件,具体步骤如下:

在内部存储缓存目录中保存缓存文件
 
  
//文件名
String filename = "test_cache_file";
//写入文件的内容
String string = "hello cache";
//使用getCacheDir()方法获得内部存储缓存目录,
///data/data/包名/cache目录
File cacheDir = getCacheDir();
//构造文件路径,即/data/data/包名/cache/文件名,
//通过这种方式指明文件要保存在内部存储缓存目录中。
String cache_fle = cacheDir + "/" + filename;
try {
    //通过FileOutputStream的构造方法传入构造好
    //的文件路径,返回一个FileOutputStream实例。
    //注意:这里没有再使用openFileOutput,是因
    //openFileOutput参数不允许包含路径分隔符“/”,
    //如果继续使用openFileOutput方法,将会出现
    //IllegalArgumentException异常
    FileOutputStream fos = new FileOutputStream(cache_fle);
    //调用write()方法写入数据
    fos.write(string.getBytes());
    //调用close()方法关闭流式传输
    fos.close();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

    1.3.2 读取内部存储缓存目录下的缓存文件

    要读取缓存文件,具体步骤如下:

读取内部存储缓存目录中的缓存文件
//文件名
String filename = "test_cache_file";
//一个byte数组用于保存读取到的数据
byte[] b = new byte[1024];
//使用getCacheDir()方法获得内部存储缓存目录,
///data/data/包名/cache目录
File cacheDir = getCacheDir();
//获取要读取的文件的路径
String cache_file = cacheDir + "/" + filename;
try {
    //通过FileInputStream传入文件路径,返回一个FileInputStream
    //注意:openFileInput参数同样不能包含路径分隔符
    FileInputStream fis = new FileInputStream(cache_file);
    //调用read()方法读取文件内容,并保存在byte数组中
    int read = fis.read(b);
    //byte数组转为String字符串
    String s = new String(b);
    //调用close()方法关闭流式传输
    fis.close();

} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

     1.4.一些对Internal Storage(内部存储)中文件和目录的操作实用的方法

    (1)getFilesDir():获取存储内部文件的文件系统目录的绝对路径。即 /data/data/包名/files

    (2)getDir(String name,int mode):在内部存储空间中新建(或打开现有的)目录。即在 data/data/包名 目录下新建(或打开现有的)目录。

    (3)deleteFile(String name):删除 /data/data/包名/files目录下name属性指定的文件。此方法参数不能包含路径分隔符“/”,直接传入要删除的文件的文件名即可。

    (4)fileList():列出 /data/data/包名/files目录下所有文件的文件名。

    2.External Storage 外部存储  (文件目录:/mnt/sdcard/)

    2.1 定义

    所有兼容Android的设备都支持一个共享的,能够让用户保存文件的“External Storage(外部存储)”。这个外部存储可能是一个可移除的存储介质(如SD卡),或者是系统在自身内部存储器上所分配出来的用作External Storage(外部存储)的分区,包括设备上的一些公共目录,比如手机相册(Pictures)、音乐(Music)等目录。存储在外部存储中的文件是全局可读取文件,并且当用户启用了USB大容量存储介质在PC上传输文件时,用户是可以对这些文件进行编辑的。

    注意:当用户将外部存储加载到PC上,或者将存储介质从设备上移除时,外部存储可能会变得不可用。此外,对于存储在外部存储里的文件是没有权限限制性可言的。所有的应用都可以对这些文件进行读写,用户也可以随时对它们进行编辑或删除。

    2.2 访问External Storage(外部存储)

    2.2.1.添加访问权限

    为了使应用能够访问外部存储中的文件,首先需要给应用添加 READ_EXTERNAL_STORAGWRITE_EXTERNAL_STORAGE权限。READ_EXTERNAL_STORAG允许应用访问外部存储中的文件,但无法对文件进行编辑。如果想要访问外部存储中的文件并对其进行编辑,则只需配置 WRITE_EXTERNAL_STORAGE权限即可,该权限会使应用同时包含对文件的读和写权限。配置权限的方法如下,在AndroidManifest.xml文件中添加外部存储访问权限配置

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.app2">

    android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
    ....

    2.2.2.检查External Storage(外部存储)的可用状态

    要访问外部存储,首先需要先检查外部存储是否可用。通过使用Environment.getExternalStorageState()方法检查外部存储是否可用,该方法返回当前设备外部存储的可用状态,我们可以通过该状态值,判断外部存储的可用性。如下面方法用于判断外部存储是否可用(既可读又可写):

/**
 * 判断设备外部存储是否可用(既可读又可写)
 *
 * @return true(可用) or false(不可用)
 */
public boolean isExternalStorageAvailable() {
    //获取外部存储的可用状态
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

    2.2.3 在外部存储保存文件

    (1)将文件保存在设备的公共目录(手机相册、音乐、铃声目录等)

    通常来说,如果用户通过应用所创建的文件,允许其他应用访问,并且允许用户在设备上方便的对其进行操作,比如复制,那么这些文件应该保存在设备的公共存储目录中,例如设备的相册、音乐、铃声等文件目录。可以通过Environment.getExternalStoragePublicDirectory(String type)方法,该方法必须传入一个type参数,以指定所保存的文件的类型,例如Environment.DIRECTORY_MUSICEnvironment.DIRECTORY_PICTURESEnvironment.DIRECTORY_RINGTONES等,而后系统的媒体扫描器将会对文件进行归类,保存到对应类型的公共目录中(例如:一个铃声类型的文件将会被保存在系统的铃声目录中,而不会出现在系统的音乐文件夹中)。例如,下面代码将在设备的相册(Pictures)目录下新建一个"MyPictures"文件夹:

//获取外存状态
String state = Environment.getExternalStorageState();
//如果外存可用
if (Environment.MEDIA_MOUNTED.equals(state)) {
    //获取设备的相册目录的路径
    File picDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
    //获取一个File
    File newFolder = new File(picDirectory, "MyPictures");
    //如果File指定的目录不存在,新建目录
    if (!newFolder.exists()){
        newFolder.mkdir();
    }
}

    (2)将文件保存在应用的私有目录中 (文件目录:/mnt/sdcard/Android/data/包名/files)

    如果应用所创建的文件并不想对其他应用可见,那么应该将文件保存在外部存储的应用私有目录中。系统的媒体扫描程序(MediaStore内容提供者)不会读取该目录下的文件。但是,其他具有权限的应用仍然可以访问。存储在该目录下的文件,在应用卸载时会一起被移除,因此,如果文件最终是用户需要长久持有的,应该把文件保存在设备的公共目录中。

    要使文件保存在外部存储,并处于应用私有目录,需要使用getExternalFilesDir(String type)方法,该方法会返回应用在外部存储中的私有目录的路径。该方法同样需要一个type参数来指定文件的类型,以便对所保存的文件进行归类。如果不需要指定类型,则可以使用null,文件将被默认保存到/mnt/sdcard/Android/data/包名/files目录中。例如,下面例子将在外部存储的私有目录(/mnt/sdcard/Android/data/包名/files)下创建一个Music文件夹并保存一个名为music的文件。

//获取外部存储中的应用私有目录,同时指定要存储的文件是音乐类型的文件
File externalFilesDir = getExternalFilesDir(Environment.DIRECTORY_MUSIC);
//获取一个File
File file = new File(externalFilesDir, "music");
//如果文件不存在,创建一个新的文件
if (!file.exists()) {
    try {
        file.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

    (3)保存缓存文件  (文件目录:/mnt/sdcard/Android/data/包名/cache)

    如果要保存的文件是缓存文件,可以通过getExternalCacheDir()方法获取外部存储中的cache目录路径。为了节省文件空间和应用性能,对于缓存文件应该要做适当的管理,比如限制缓存文件所占空间在合理的范围内(如1MB),及时移除没用的缓存文件等。


    后记:文中所提到的Internal Storage(内部存储)和External Storage(外部存储),所指的实际上都是位于设备内部存储器上的两个存储分区,无论是Internal Storage还是External Storage,所存储的文件其实都是存储在设备的内部存储介质上,就如同PC上的硬盘,一般我们在操作系统中会看到被分成几个分区一般。希望大家不要被名字所混淆。这里的“内”和“外”,所表示的实际意义是:应用所创建的文件对于其他应用(或者用户)所开放的访问权限。对于Internal Storage里面的文件而言,只有文件所属者的应用能够访问;而对于External Storage里面的文件,只要是具有READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE权限的应用,都可以访问。

      未完,待续!

你可能感兴趣的:(Android)