参考文章
概述
Android 提供了多种选项来保存永久性应用数据,这一过程也有叫做是数据的持久化存储。我们可以根据我们特定的业务需求来选择合适的存储方式。
数据存储选项
选项 | 存储方式 |
---|---|
SharedPreferences 存储 | 用键值对存储私有原始数据 |
内部存储 | 在设备内存中存储私有数据 |
外部存储 | 在共享的外部存储中存储公共数据 |
SQLite 数据库存储 | 在私有的数据库中存储结构化数据 |
网络存储 | 通过自己的网络服务器存储数据 |
说明:
SharedPreferences
这个类可以对应用的一些参数配置、设置等数据进行存储。
Android 为我们提供了一个组件 ContentProvider
,我们可以使用它将自己应用的数据(甚至是私有数据),添加自己希望的任何限制来公开其它应用对自己应用数据的读/写访问权限,这里不做过多说明。
SharedPreferences
SharedPreferences
这个类提供了一个通用的框架,我们可以很方便的使用它来保存任何类型的数据:布尔型、浮点型、整型、长整型和字符型。它保存在 data\data\<应用程序包名>\shared_prefs\
下,可以使用虚拟机查看。
示例代码如下
public static final String PREFS_NAME = "MyPrefsFile";
// We need an Editor object to make preference changes.
// All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("silentMode", mSilentMode);
// Commit the edits!
editor.commit();
...
// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
boolean silent = settings.getBoolean("silentMode", false);
内部存储
在设备的内部存储中保存文件。默认情况下,保存到内部存储的文件是应用的私有文件,其他应用(和用户)不能访问这些文件。 当用户卸载您的应用时,这些文件也会被移除。文件会保存在 /data/data/应用程序包名/files
目录下,如上图所示。
文件写入内部存储的步骤
- 调用
openFileOutput()
方法,返回一个FileOutputStream
对象; - 通过 FileOutputStream 对象,调用
write()
方法写入到文件; - 通过 FileOutputStream 对象,调用
close()
关闭流;
示例代码如下
String FILENAME = "hello_file";
String string = "hello world!";
FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();
MODE_PRIVATE
将会创建文件(或替换具有相同名称的文件),并将其设为应用的私有文件。其他可用模式包括:MODE_APPEND
如果文件存在,则在后面追加数据、MODE_WORLD_READABLE
和 MODE_WORLD_WRITEABLE
。
注意:自 API 级别 17 以来,常量 MODE_WORLD_READABLE
和 MODE_WORLD_WRITEABLE
已被弃用。从 Android N 开始,使用这些常量将会导致引发 SecurityException
。这意味着,面向 Android N 和更高版本的应用将无法按名称共享私有文件,尝试共享 “file://”URI
将会导致引发 FileUriExposedException
。 如果自己的应用需要与其他应用共享私有文件,则可以通过 FileProvider
与 FLAG_GRANT_READ_URI_PERMISSION
配合使用。这里不做过多说明。
从内部存储中读取文件步骤
- 调用
openFileInput()
方法,返回一个FileInputStream
对象; - 通过 FileInputStream 对象,调用
read()
方法读取文件字节; - 通过 FileInputStream 对象,调用
close()
关闭流;
提示:对于静态文件(就是那些不会改变的文件,比如 CSS
文件,JS
文件,提示音等),我们可以将它们放在项目的 res/raw/
目录中或 assets/
目录中。然后通过资源 id
调用 openRawResource()
或其它方法返回一个 InputStream
对象,通过流读取文件。
使用缓存文件
如果您想要缓存一些数据,而不是永久存储这些数据,应该使用 getCacheDir()
来打开一个 File,它表示您的应用应该将临时缓存文件保存到的内部存储目录。
当设备的内部存储空间不足时,Android 可能会删除这些缓存文件以回收空间。 但我们不应该依赖系统来为您清理这些文件, 而应该始终自行维护缓存文件,使其占用的空间保持在合理的限制范围内(例如 1 MB)。 当用户卸载应用时,这些文件也会被移除。
内部存储的缓存文件读取步骤和上面的普通文件读取类似,只是将上面的 FILENAME
换成了 getCacheDir() + File.separator + FILE_NAME
。
示例代码如下
FileOutputStream fos = openFileOutput(getCacheDir() + File.separator + FILE_NAME, Context.MODE_PRIVATE);
FileOutputStream fis = FileInputStream(getCacheDir() + File.separator + FILE_NAME, Context.MODE_PRIVATE);
其它实用方法
方法 | 含义 |
---|---|
getFilesDir( ) | 获取应用中存储内部文件的文件系统目录的绝对路径 |
getDir( ) | 在应用的内部存储空间内创建(或打开现有的)目录 |
deleteFile( ) | 删除保存在应用内部存储的文件 |
fileList( ) | 返回应用当前保存的一系列文件 |
外部存储
外部存储可能是可移除的存储介质(例如 SD 卡)或内部(不可移除)存储。 保存到外部存储的文件是全局可读取文件,而且可由用户修改这些文件。有两种类型的文件,一类是 可与其它应用共享的文件,还有就是 应用私有文件。
标准手机的 SD 卡根目录是: /mnt/sdcard/
, 其他国产手机 SD 卡的根目录各不相同,如 /storage/emulated/0/
等。这里以 /mnt/sdcard/
为例。
使用 Scoped Directory(作用域目录) 访问
在 Android 7.0 或更高版本中,如果应用需要访问外部存储上的特定目录,官方推荐使用作用域目录访问。 作用域目录访问可简化应用访问标准外部存储目录(例如 Pictures
目录)的方式,并提供简单的权限 UI,清楚地详细介绍应用正在请求访问的目录。这里不做过多说明。
获取外部存储的访问权限
要读取或写入外部存储上的文件,应用必须获取 READ_EXTERNAL_STORAGE
或 WRITE_EXTERNAL_STORAGE
系统权限。
如果应用同时需要读取和写入文件,则只需请求 WRITE_EXTERNAL_STORAGE
权限,因为此权限也隐含了读取权限要求。
注意:从 Android 4.4 开始,如果应用仅仅读取或写入应用外部存储的私有文件,则不需要这些权限。
检查外部存储的可用性
在使用外部存储执行任何工作之前,应始终调用 getExternalStorageState()
以检查外部存储是否可用。存储介质可能已装载到计算机,处于缺失、只读或其他某种状态。
示例代码如下
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
getExternalStorageState()
方法可能返回当前应用的其它状态(例如存储介质是否处于共享 [连接到计算]、完全缺失、错误移除等状态)。当应用返回这些状态时,我们可以使用这些状态通知给用户。
保存可与其它应用共享的文件
文件是可以被自由访问,且文件的数据对其它应用或者用户来说都是有意义的,当应用被卸载之后,其卸载前创建的文件仍然保留。
下表是一些外部存储共享文件的根目录说明
根目录 | 含义 |
---|---|
/mnt/sdcard/Music/ | 媒体扫描器把在这个目录中找到所有媒体文件作为用户音乐 |
/mnt/sdcard/Podcasts/ | 媒体扫描器把在这个目录中找到的所有媒体文件作为音/视频的剪辑片段 |
/mnt/sdcard/Ringtones/ | 媒体扫描器把在这个目录中找到的所有媒体文件作为铃声 |
/mnt/sdcard/Alarms/ | 媒体扫描器把在这个目录中找到的所有媒体文件作为闹钟的声音 |
/mnt/sdcard/Pictures/ | 所有的图片(不包括那些用照相机拍摄的照片) |
/mnt/sdcard/Movies | 所有的电影(不包括那些用摄像机拍摄的视频) |
/mnt/sdcard/Download/ | 其他下载的内容 |
… | … |
一般而言,应该将用户可通过自己的应用获取的新文件保存到设备上的“公共”位置,以便其它应用能够在其中访问这些文件,并且用户也能轻松地从该设备复制这些文件。 执行此操作时,应使用共享的公共目录之一,例如 Music/
、Pictures/
和 Ringtones/
等。
要获取表示相应的公共目录的 File
,需调用 getExternalStoragePublicDirectory()
方法,向其传递需要的目录类型,例如 DIRECTORY_MUSIC
、DIRECTORY_PICTURES
、 DIRECTORY_RINGTONES
或其他类型。然后将文件保存到相应的媒体类型目录,系统的媒体扫描程序可以在系统中正确地归类存储的文件(例如铃声在系统设置中显示为铃声而不是音乐)。
示例代码如下
public File getAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
// 公共图片目录中创建了一个用于新相册的目录
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
保存应用私有文件
如果应用正在处理的文件不适合其它应用使用(例如仅供自己应用使用的图形纹理或音效),则应该通过调用 getExternalFilesDir()
来使用外部存储上的私有存储目录。此方法还会采用 type
参数指定子目录的类型(例如 DIRECTORY_MOVIES
)。 如果我们不需要特定的媒体目录,可传递 null
以接收应用私有目录的根目录。
所有应用程序的 外部存储的私有文件 都放在 SD 卡根目录的 Android/data/
下,目录形式如:/mnt/sdcard/Android/data/< 应用程序包名 >/files
。
从 Android 4.4 开始,读取或写入应用私有目录中的文件不再需要 READ_EXTERNAL_STORAGE
或 WRITE_EXTERNAL_STORAGE
权限。 因此,我们可以通过添加 maxSdkVersion
属性来声明,只能在较低版本的 Android 中请求该权限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
...
</manifest>
有时,已分配某个内部存储器分区用作外部存储的设备可能还提供了 SD 卡槽。在使用运行 Android 4.3 和更低版本的这类设备时,getExternalFilesDir()
方法将仅提供内部分区的访问权限,而我们应用无法读取或写入 SD 卡。不过,从 Android 4.4 开始,可通过调用 getExternalFilesDirs()
来同时访问两个位置,该方法将会返回包含各个位置条目的 File 数组。 数组中的第一个条目被视为外部主存储;除非该位置已满或不可用,否则应该使用该位置。 如果我们希望在支持 Android 4.3 和更低版本的同时访问两个可能的位置,可使用支持库中的静态方法 ContextCompat.getExternalFilesDirs()
。 在 Android 4.3 和更低版本中,此方法也会返回一个 File 数组,但其中始终仅包含一个条目
。
注意:尽管 MediaStore
内容提供程序不能访问 getExternalFilesDir()
和 getExternalFilesDirs()
所提供的目录,但其他具有 READ_EXTERNAL_STORAGE
权限的应用仍可访问外部存储上的所有文件,包括上述文件。 如果我们需要完全限制自己应用文件的访问权限,则应该转而将文件写入到内部存储。
保存缓存文件
要打开缓存文件保存到外部存储目录的 File
,需调用 getExternalCacheDir()
。 如果用户卸载了应用,这些文件也会被自动删除。
与前述 ContextCompat.getExternalFilesDirs()
方法相似,我们可以通过调用 ContextCompat.getExternalCacheDirs()
方法来访问辅助外部存储(如果可用)上的缓存目录。
缓存文件放在目录 /mnt/sdcard/Android/data/< package_name>/cache
下。
提示 :为节省文件空间并保持应用性能,我们应该在应用的整个生命周期内仔细管理缓存文件并移除其中不再需要的文件,这一点非常重要。
使用数据库
Android 提供了对 SQLite
数据库的完全支持。
创建新 SQLite 数据库的推荐方法是创建 SQLiteOpenHelper
的子类并覆盖 onCreate()
方法,在此方法中,我们可以执行 SQLite 命令以创建数据库中的表。
示例代码如下
public class DictionaryOpenHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 2;
private static final String DICTIONARY_TABLE_NAME = "dictionary";
private static final String DICTIONARY_TABLE_CREATE =
"CREATE TABLE " + DICTIONARY_TABLE_NAME + " (" +
KEY_WORD + " TEXT, " +
KEY_DEFINITION + " TEXT);";
DictionaryOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DICTIONARY_TABLE_CREATE);
}
}
然后使用已定义的构造函数获取 SQLiteOpenHelper
实现的实例。 要从数据库执行写入和读取操作时,分别调用 getWritableDatabase()
和 getReadableDatabase()
。二者都会返回一个表示数据库的 SQLiteDatabase
对象,并提供用于 SQLite 操作的方法,使用 SQLiteDatabase query()
方法来执行 SQLite 查询。
每个 SQLite 查询都会返回一个指向该查询找到的所有行的 Cursor
。 我们可以使用 Cursor 机制来浏览数据库查询结果,以及读取行和列。
Android SDK 包含一项 sqlite3
数据库工具,利用此工具可以浏览表内容,运行 SQL 命令,以及在 SQLite 数据库上执行其他实用功能。
使用网络存储
通过网络来存储和检索有关自己的网络服务的数据。
参考文章