方便以后查找才复制文章
前言
这篇文章解析 Android 设备上数据持久化存储方式,数据存储方式包括共享首选项、内部存储、外部存储、SQLite 数据库、网络连接五种存储方式,方式的选取取决于使用者的需求,如果对持久化存储方式不了解,那么一定会有这么几个疑问:
清理缓存,清理的是哪里?
清理数据,清理的是哪里?
应用私有文件放在哪里?
公共文件存放在哪里?
如果你对以上存在疑问,那么看完本章后相信可以解开你的疑惑,还会加深对数据持久化存储的理解。
准备知识
理解 Android 系统中的内部存储和外部存储空间的区别,通过 Android Studio3.0 Device File Explorer 查看 Android 手机顶级系统目录
关注 data 和 storage 目录,内部存储空间指的是 data目录,外部存储空间指的是 storage目录,下面来细说这两个目录:
data 目录内主要目录是由存放所有应用数据的 data目录和存放所有应用程序的 app 目录组成。安装应用的时候是把 APK 数据提取到 app 目录内,而 data目录是 app 目录内应用产生数据的存放处,各个应用数据以包名分割开,该 data 目录是内部存储私有目录,内容会随着应用卸载而清除。
应用内部存储空间内会有以下几个常见目录:
/data/data/包名/shared_prefs
/data/data/包名/databses
/data/data/包名/files
/data/data/包名/cache
shared_prefs 是 SharedPreference 创建的文件;databases是数据库创建的文件;files 存放应用创建文件;cache 存储临时缓存文件,这些目录都是应用私有目录。
那么如何获取私有目录,因为和应用相关,所以私有目录通过 context 对象获取,以下目录都属于内部存储私有目录:
getCacheDir()
获取缓存目录。该目录用于缓存临时数据,不能永久存储数据,因为当设备的内部存储空间不足时,Android 系统会删除这些缓存文件以回收空间。为了 APP 高效管理存储空间,应该自行维护文件,不应该依赖系统来清理。系统应用管理点击清除缓存的时候,私有目录内只有缓存目录的数据会被清除,如果点击的是清除数据,那么私有目录内即包名下的所有文件都会被清除。该目录对其他或者用户不可见,敏感数据应该存放在此目录下。
getDir(String name, int mode)
在内部私有根目录下(包名下)创建 name目录,目录名称任意字符串,函数返回 File 对象,如果name目录不存在会创建目录。
getFilesDir()
获取存放文件的目录,该目录可以存放持久文件,只要应用不卸载。
openFileOutput(String name, int mode)
在 getFilesDir() 下打开或创建文件,如果存在则打开,不存在则创建新文件,可以通过write() 方法写入文件,文件存储在 getFilesDir() 目录内。
openFileInput(String name)
打开指定文件,如果文件存在则返回 FileInputStream 对象,操作对象可以读取文件内容,文件不存在抛出 FileNotFoundException 异常。
storage 目录是外部存储目录。外部存储可以是可移除存储介质(SD卡)或内部存储(不可移除),通过文件管理器看到的都是外部存储目录,外部存储目录又分为公有目录和私有目录。外部存储公有目录下的文件所有应用都有访问、修改、删除权限,应用的卸载不会影响公有目录文件,应用访问公有目录需要申请权限;私有目录默认仅供应用本身访问,不需要申请权限,可以读取/写入内容;如果访问非私有目录,需要申请文件权限。应用卸载后,私有目录和内容将全部被清除,下面介绍这两个目录如何获取:
公有目录获取
公有目录与 Context 无关,使用 Environment 获取
Environment.getExternalStoragePublicDirectory(Stringtype)
如果要获取公有根目录使用无参数函数获取,如下:
Environment.getExternalStoragePublicDirectory()
以下type是公有文件常见的存储位置
Environment.DIRECTORY_PICTURES 图片目录
Environment.DIRECTORY_DCIM 相册目录
Environment.DIRECTORY_DOCUMENTS 文档目录
Environment.DIRECTORY_DOWNLOADS 下载目录
Environment.DIRECTORY_MOVIES 视频
私有目录获取
私有目录和 Context 相关,所以使用 context.getExternalXXX 获取目录,以下目录范围都属于外部存储私有目录内:
getExternalFilesDirs() 获取文件根目录,应用不卸载,可以长期存放文件。
getExternalFilesDirs(int type) 获取文件内的具体目录。通过 type 参数指定,type 类型和公有目录类型一致
getExternalCacheDir() 获取缓存目录。该目录用于存放临时私有文件,如果系统应用管理内点击清除缓存,那么该目录下所有内容被清除,如果点击的是清除数据,那么整个私有目录(包名)和内容都会被清除。
getExternalMediaDirs() 获取应用存放媒体目录。目录内的内容可以被其他应用访问,通过 MediaStore 可以查询和获取。 再普及一个小知识,在 Android6.0 及以上版本中来看两个绝对路径:
/data/data/com.example.interviewroad/shared_prefs
/data/user/0/com.example.interviewroad/shared_prefs
这两条绝对路径指向同一个文件。这是因为 Android 6.0 支持多用户,文件的实际路径是 /data/user/0/XXX(0代表的用户ID),而 /data/data/XXX 是为了方便查看数据而创建的引用目录,实际指向 /data/user/0/XXX,下文有时会出现 /data/data/XXX 和 /data/user/0/XXX表述的是一样的意思,如果是 Android 5.0 及以下版本,返回的是 /data/data/XXX, 这下完全明白了。
把准备知识看懂了,前言中提到的问题可以轻松解决。下面开始解析Android数据持久化的方式。
共享首选项
共享首选项这个名字听起来有点陌生,其实是 SharedPreference 类,Android 中文网官网这么翻译,我觉得以后要习惯这叫法,具体用法相信大家已经非常熟就不造轮子了,来看一段代码
privatevoidstoreSharedPreferences(){SharedPreferencessharedPreferences=getSharedPreferences("My_Pref_File",Context.MODE_PRIVATE);SharedPreferences.Editoredit=sharedPreferences.edit();edit.putBoolean("silentMode",true);edit.commit();}
这段代码会在 Android 设备上创建 My_Pref_File.xml 文件,文件内存放了一对布尔类型的键值对,键为 silentMode,值为true。My_Pref_File.xml 文件存放在内部存储空间内,来看一张截图,这张图是通过 Android Studio3.0 新增特性 Device File Explorer 查看
My_Pref_File.xml 文件位于 /data/user/0/com.example.interviewroad/shared_prefs 目录下,该目录属于内部存储空间,所以SharedPreference类创建的文件所在目录位于/data/user/0/包名/shared_prefs/目录下,位于内部存储私有目录下。
使用内部存储
1、内部存储保存文件:
使用 openFileOutput(String name, int mode) 函数,返回 FileOutputStream 对象。
调用 FileOutputStream 对象的 write() 函数。
关闭流对象,调用 close() 方法。
读取使用 openFileInput(String name),使用完后也要关闭流对象。
如果需要读取 /res/raw/ 下的文件,使用 getResources().openRawResource(X),X 为资源 ID 即 R.raw.,返回InputStream对象。
代码如下:
StringFILENAME="hello_file";
Stringstring="hello world!";
FileOutputStreamfos=openFileOutput(FILENAME,Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();
以上代码会创建 hello_file 文件,文件存放在 /data/data/包名/files 目录下,使用 context.getFilesDir() 获取。
2、内部存储内放缓存文件:
主要用于缓存数据,而不是持久化数据,通过 getCacheDir() 获取缓存目录,该目录仅供于存放临时文件,应用清除缓存时,会清理该目录,详细说明也在准备知识中讲到。
使用外部存储
1、权限申请
访问外部存储上的文件,必须申请 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 权限。
如果需要读/写权限,只需要申请 WRITE_EXTERNAL_STORAGE,内包含读取权限。
2、检查存储介质可用性
由于外部存储介质可能处于卸载、可读或其他状态,所以在使用之前一定要调用 getExternalStorageState() 检查存储介质是否可用,代码如下:
publicbooleanisExternalStorageWritable(){
Stringstate=Environment.getExternalStorageState();
if(Environment.MEDIA_MOUNTED.equals(state)){
returntrue;
}
returnfalse;
}
3、保存公有文件
如果应用保存的文件需要供给其他应用或者用户访问,那么文件应该存放在公有目录,如果私有目录文件或数据库需要共享,那么要使用自定义 ContentProvider。公有目录在准备知识中已经介绍了,根据数据的类型保存到对应的目录,比如图片保存在 Pictures/、 音乐保存在 Music/、下载文件保存在 Download/ 等,按类别保存到对应的目录,系统媒体扫描程序会将文件归类,而这些目录获取通过以下目录
Environment.getExternalStoragePublicDirectory(Stringtype)
来看一段代码,获取图片公有目录,并在内部创建 albumName 文件夹
publicFilegetAlbumStorageDir(StringalbumName{
Filefile=newFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),albumName);if(!file.mkdirs()){Log.e(LOG_TAG,"创建失败");
}
returnfile;
}
4、保存私有文件
调用 getExternalFilesDir(String type) 将数据保存在外部存储的私有存储目录下。需要将文件保存至合适目录,可以通过 type 指定目录类型,具体类型参考准备知识部分。
从 Android 4.4 开始如果仅仅读取或写入私有文件,则不需要文件读写权限,如果需要兼容低版本,可以通过添加 maxSdkVersion 属性来声明,只在较低版本上使用权限,来看代码:
当卸载应用时,此目录下的所有文件将被清除。
5、外部存储空间上保存缓存文件
要将文件缓存在外部存储上,需要用 getExternalCacheDir() 访问,该目录是私有目录。同样该目录会随着应用卸载而清除,且清理缓存文件时也会清理该目录。应用提高 app 性能和稳定性,应当自行维护缓存目录的大小,而不能依赖系统。这里建议使用 ContextCompat.getExternalCacheDir() 访问,增强向下兼容性。
使用数据库
Android 系统提供对 SQLite 数据库的完全支持,在应用开发中,根据自己需求,使用数据库,比如需要存储复杂关联关系数据,存放安全数据等。
使用网络
将数据保存在云端。
总结:
如果敏感数据或者不被其他应用访问的数据存放在内部存储私有目录 ,非隐私数据可存放在外部存储私有目录或者公有目录下。
公有文件存放在外部存储 getExternalStoragePublicDirectory 目录下,不同的文件类型通过 type 指定存放到对应的目录,好让系统的媒体扫描程序可以在系统中正确地归类您的文件。
公有目录文件的读写操作需要申请文件读写权限;自身应用访问外部存储私有目录 Android 4.4 以及更高版本不需要申请文件读写权限,低于 4.4 需要申请权限;自身应用访问内部存储私有目录不需要申请文件读写权限;访问其他应用外部存储私有目录需要申请文件读写权限。
系统清理缓存的时候,内部存储私有目录 cache下的所有文件和外部存储私有目录 cache 下所有文件将被清除;系统清理数据的时候,内部存储私有目录和外部程序私有目录下所有内容将被清理。
使用外部存储时要检查外部存储介质是否存在。