阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680

一、前言

文件系统一直是Android开发过程中经常接触的东西。而关于内部存储、外部存储、外置存储、私有存储、公共存储,以及访问哪些文件需要申请运行时权限等问题,一直是许多开发者头疼的问题。本文就将详细地讲解这些重要而模糊的知识点。

二、内部存储

内部存储主要用于保存应用的私有文件,其他应用无法访问这些数据。当应用卸载的时候,这些数据也会被删除。使用内部存储不需要任何额外权限。

2.1、写入数据

String filename="innerFile";
String outData="CodingEnding";//需要写入内部存储的数据
FileOutputStream fos=null;
try {
fos=openFileOutput(filename, Context.MODE_PRIVATE);
fos.write(outData.getBytes());//写入数据
Log.i(TAG,"写入成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {//关闭文件流
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

核心代码如下:

fos=openFileOutput(filename, Context.MODE_PRIVATE);
fos.write(outData.getBytes());//写入数据
fos.close();

首先使用Context的openFileOutput方法获取FileOutputStream,然后按照Java的文件写入方式操作就行了。

2.2读取数据

String filename="innerFile";
FileInputStream fis=null;
try {
fis=openFileInput(filename);
BufferedReader bf=new BufferedReader(new InputStreamReader(fis));
StringBuilder builder=new StringBuilder();
String line=null;
while((line=bf.readLine())!=null){
builder.append(line);
}
Log.i(TAG,"已读取数据:"+builder.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {//关闭资源
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

关键代码如下:

fis=openFileInput(filename);
BufferedReader bf=new BufferedReader(new InputStreamReader(fis));
StringBuilder builder=new StringBuilder();
String line=null;
while((line=bf.readLine())!=null){
builder.append(line);
}

通过Context的openFileInput方法获取FileInputStream,然后按照Java的文件读取方式操作就行了。

2.3读取静态文件

对于res/raw文件夹下的数据,可以使用Resources的openRawResource方法读取,示例代码如下:

InputStream fis=null;
try {
fis=getResources().openRawResource(R.raw.rawfile);
BufferedReader bf=new BufferedReader(new InputStreamReader(fis));
StringBuilder builder=new StringBuilder();
String line=null;
while((line=bf.readLine())!=null){
builder.append(line);
}
Log.i(TAG,"已读取Raw数据:"+builder.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {//关闭资源
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

关键代码如下:

fis=getResources().openRawResource(R.raw.rawfile);

可以看到,这里只是改用openRawResource方法获取InputStream,后续操作和读取内部存储数据一致。

注意,在raw文件夹中,文件名只能包含小写字母、数字和下划线。

2.4 缓存数据

对于应用的私有缓存数据,可以保存在内部存储的缓存目录中,关键方法是Context的getCacheDir方法。

public abstract File getCacheDir();

这个方法会返回一个File类型的对象,这个File对象对应的就是内部存储中用于保存缓存数据的根目录。通过这个File对象,我们就可以利用Java文件流的方式去读取和写入缓存数据了。

注意,应用的私有缓存文件不应该过大。如果内部存储空间不足,系统可能会删除这些缓存文件。为了保证良好的用户体验,应用应该定期主动清除自己的缓存数据。

3 外部存储

除了内部存储,Android系统还为开发者提供了外部存储。需要注意的是,外部存储并不仅仅指SD卡,它可能是可移除的存储介质(典型如SD卡),也可能是不可移除的存储介质(如现在很多一体机内置的存储器)。外部存储是相对于内部存储的概念,用于保存全局范围可读取的文件。这也就意味着,保存在外部存储中的数据可以被设备中的任何应用访问,甚至也可以被用户查看、修改。

4 获取权限

和访问内部存储不同,要读取外部存储中的文件首先需要获取权限,即READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。如果应用同时有读、写的需求,只需要申请WRITE_EXTERNAL_STORAGE权限即可。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

注意,Android 6.0(API 23)引入了运行时权限的概念,以上提到的两种权限都需要动态地获取。如果不了解运行时权限的概念,可以参考这篇博客:

5 详解Android权限机制

另外,在Android 4.4(API 19)及以上,如果只是在外部存储中读、写应用的私有文件,就不需要申请这些权限。因此,我们可以使用maxSdkVersion属性实现只在较低版本申请权限,如下所示:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="18"/>

6 可用性检查

由于外部存储存在被移除的情况,我们在使用外部存储前首先应该进行可用性检查。使用Environment的getExternalStorageState方法可以获得外部存储的状态,通过判断返回的状态就实现了对外部存储的可用性检查。下面提供两个简单的示例:

1.判断外部存储是否可写和可读

String state=Environment.getExternalStorageState();
if(Environment.MEDIA_MOUNTED.equals(state)){
//外部存储可写、可读
}

2.判断外部存储是否至少可读

String state=Environment.getExternalStorageState();
if(Environment.MEDIA_MOUNTED.equals(state)||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)){
//外部存储至少可读
}

实际上,getExternalStorageState方法有10种返回值,如下:

MEDIA_UNKNOWN:未知状态
MEDIA_REMOVED:移除状态(外部存储不存在)
MEDIA_UNMOUNTED:未装载状态(外部存储存在但是没有装载)
MEDIA_CHECKING:磁盘检测状态
MEDIA_NOFS:外部存储存在,但是磁盘为空或使用了不支持的文件系统
MEDIA_MOUNTED:就绪状态(可读、可写)
MEDIA_MOUNTED_READ_ONLY:只读状态
MEDIA_SHARED:共享状态(外部存储存在且正通过USB共享数据)
MEDIA_BAD_REMOVAL:异常移除状态(外部存储还没有正确卸载就被移除了)
MEDIA_UNMOUNTABLE:不可装载状态(外部存储存在但是无法被装载,一般是磁盘的文件系统损坏造成的)

7 公共文件(共享文件)

对于在应用中产生的多媒体类型的文件,如音乐、图片、铃声等,一般应该保存在外置存储中对应的公共目录下,如/Music、/Pictures、/Ringtones,这样方便和其他的应用共享这些文件。同时,系统的媒体扫描器也能正确地对这些文件进行归类。

要将这些公共文件(共享文件)保存到指定位置,关键是Environment的getExternalStoragePublicDirectory方法,其原型如下:

public static File getExternalStoragePublicDirectory(String type);

这个方法需要提供一个String类型的type参数,以便返回保存相应类型公共文件的根目录,即一个File对象。type的值不可为null,可选值如下(都是Environment中定义的常量):

DIRECTORY_MUSIC:音乐类型
DIRECTORY_PICTURES:图片类型
DIRECTORY_MOVIES:电影类型
DIRECTORY_DCIM:照片类型
DIRECTORY_DOWNLOADS:下载文件类型
DIRECTORY_DOCUMENTS:文档类型
DIRECTORY_RINGTONES:铃声类型
DIRECTORY_ALARMS:闹钟提示音类型
DIRECTORY_NOTIFICATIONS:通知提示音类型
DIRECTORY_PODCASTS:播客音频类型
注意,返回的文件目录可能还不存在,因此在执行文件操作前应该确保相应的文件目录已经存在,否则使用File的mkdirs方法创建文件目录。

下面演示如何在下载文件类型根目录下创建自己的文件目录(具体保存文件的代码请参考demo):

String directoryName="PublicFileTest";
File publicDownloadDirectory=Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS);
File myDownloadDirectory=new File(publicDownloadDirectory,directoryName); if(!myDownloadDirectory.exists()){//确保指定目录已经创建
boolean createResult=myDownloadDirectory.mkdirs();
if(createResult){
Log.i(TAG,"创建成功");
}else{
Log.i(TAG,"创建失败");
}
}

小技巧:如果不希望系统的媒体扫描器访问我们的媒体文件,可以在媒体文件所在的目录下新建一个名为.nomedia的空文件,这会阻止媒体扫描器归类我们的文件并提供给其他应用。

8 私有文件

对于应用私有的文件,则应该使用Context的getExternalFilesDir方法访问外部存储中的私有存储目录,媒体扫描器不会扫描这些目录。可以为这个方法传入一个String类型的type参数,用于获取私有存储目录中相应的媒体文件子目录。当然,也可以传入null直接获取私有存储的根目录。这个方法的返回值也是一个File对象。

public abstract File getExternalFilesDir(String type);

注意,某些移动设备可能既提供了内置存储器作为外部存储空间,同时又提供了SD卡作为外部存储空间。也就是说,在这些设备中外部存储实际上包含了两块磁盘。在Android 4.3(API 18)及以下,Context的getExternalFilesDir方法仅仅会返回内置存储器对应的外部存储控件,而无法访问SD卡对应的存储空间。从Android 4.4(API 19)开始,Context新增了getExternalFilesDirs方法。这个方法的返回值是一个File数组,包含两个对象(可能为null),这样就可以实现对内置存储器和SD卡的访问。数组的第一个对象默认是外部主存储,官方的开发建议是除非这个位置已满或不可用,否则应该使用这个位置。

public abstract File[] getExternalFilesDirs(String type);

另外,出于兼容性的考虑,可以使用ContextCompat的getExternalFilesDirs方法。这是一个静态方法,返回值也是一个File数组。在Android 4.4及以上,效果和Context的getExternalFilesDirs方法一致;而在Android 4.3及以下,返回的File数组始终只包含一个对象。

public static File[] getExternalFilesDirs(Context context, String type)

对于以上方法的type参数,有以下几种可选值(都是Environment中定义的常量):

DIRECTORY_MUSIC:音乐类型
DIRECTORY_PICTURES:图片类型
DIRECTORY_MOVIES:电影类型
DIRECTORY_RINGTONES:铃声类型
DIRECTORY_ALARMS:闹钟提示音类型
DIRECTORY_NOTIFICATIONS:通知提示音类型
DIRECTORY_PODCASTS:播客音频类型
注意,当应用卸载时,这些私有存储目录中的文件也会被删除。此外,虽然系统的媒体扫描器不会访问外部存储中的私有存储目录,但是其他具有READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE权限的应用依旧可以读/写这些私有存储目录中的文件。因此对于真正重要的文件,还是应该保存在应用的内部存储中。

补充:私有文件根目录的参考路径:Android/data/包名/files/

(具体保存文件的代码请参考demo)

9 缓存文件

在外部存储中也有专门保存缓存文件的空间,可以通过Context的getExternalCacheDir方法访问缓存文件目录,返回值是一个File对象。上文曾说过,外部存储可能同时包含内置存储器和SD卡两个存储空间,因此在Android 4.4(API 19)及以上还可以通过Context的getExternalCacheDirs方法访问这两个存储空间。这个方法会返回一个File数组,包含两个对象,第一个对象默认是外部主存储对应的缓存文件目录。

public abstract File getExternalCacheDir();
public abstract File[] getExternalCacheDirs();

同样,为了兼容性,也可以使用ContextCompat的getExternalCacheDirs方法。这是一个静态方法,返回值也是一个File数组。在Android 4.4及以上,效果和Context的getExternalCacheDirs方法一致;而在Android 4.3及以下,返回的File数组始终只包含一个对象。

public static File[] getExternalCacheDirs(Context context);

注意,当应用卸载时,缓存目录下的文件也会被系统删除。当然,官方建议开发者应该主动移除不再需要的缓存文件,这有助于节省存储空间并保持应用性能。

补充:缓存文件根目录的参考路径:Android/data/包名/cache/

10 其他常用API

除了以上提过的方法,Android文件系统还提供了其他可用的API,下面简单进行讲解:

Context

public abstract File[] getExternalMediaDirs();

该方法将以File数组的形式返回外部存储中所有可以保存媒体文件的目录。这些目录中的文件将会被系统媒体扫描器访问,并可以通过MediaStore提供给其他应用。

public abstract File getDataDir();

这个方法以绝对路径的方式访问应用的私有文件目录(内部存储)。官方并不建议直接使用这个方法返回的路径,因为如果应用迁移到其他位置(如迁移到SD卡),文件路径将发生改变。

public abstract File getDir(String name, @FileMode int mode);

以File形式返回一个文件目录,应用可以在这个目录中保存自己的数据文件。如果这个目录还不存在,系统将会自动创建它。

public abstract File getFilesDir();

以File形式返回openFileOutput方法所使用的文件目录(即内部存储根目录)。注意,这个方法是通过绝对路径进行文件访问的,因此应用发生迁移将导致返回的路径发生变化。

public abstract File getFileStreamPath(String name);

以File形式返回通过openFileOutput方法存储的文件。注意,这个方法是通过绝对路径进行文件访问的,因此应用发生迁移将导致返回的路径发生变化。

public abstract File getObbDir();

以File形式(绝对路径)返回应用Obb文件的存储目录。如果当前应用并不存在Obb文件,则这个目录也不存在,返回null。

public abstract File[] getObbDirs();

和上一个方法类型,只不过返回的对象是File数组,因此得以访问SD卡中的Obb文件目录。这个方法在Android 4.4(API 19)及以上可用。

ContextCompat

public static File getDataDir(Context context);

这是Context#getDataDir方法的兼容性版本。

public static File[] getObbDirs(Context context);

这是Context#getObbDirs方法的兼容性版本。

Environment

public static File getDataDirectory();

以File形式返回用户数据目录,即/data目录。

public static File getDownloadCacheDirectory();

以File形式返回下载缓存数据目录,即/cache目录。

public static File getExternalStorageDirectory();

以File形式返回外部存储根目录。

public static File getRootDirectory();

以File形式返回存放系统OS的文件根目录,即system目录。注意,这个分区始终处于只读状态。
阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680
原文链接https://blog.csdn.net/codingending/article/details/79183750

数据持久化之Android文件系统(一)的更多相关文章

  1. Docker 容器数据 持久化(系统学习Docker05)

    写在前面 本来是可以将数据存储在 容器内部 的.但是存在容器内部,一旦容器被删除掉或者容器毁坏(我亲身经历的痛,当时我们的大数据平台就是运行在docker容器内,有次停电后,不管怎样容器都起不来.以前 ...

  2. Android开发学习之路--数据持久化之初体验

    上班第一天,虽然工作上处于酱油模式,但是学习上依旧不能拉下,接着学习android开发吧,这里学习数据持久化的 知识. 其实数据持久化就是数据可以保存起来,一般我们保存数据都是以文件,或者数据库的形式 ...

  3. Android中的数据持久化机制

    Android中几种最简单但是却最通用的数据持久化技术:SharedPreference.实例状态Bundle和本地文件. Android的非确定性Activity和应用程序生存期使在会话间保留UI状 ...

  4. $《第一行代码:Android》读书笔记——第6章 数据持久化

    主要讲述了Android数据持久化的三种方式:文件存储.SharedPreference存储.SQLite数据库存储. (一)文件存储 其实Android中文件存储方式和Java的文件操作类似,就是用 ...

  5. Android学习_数据持久化

    数据持久化:将内存中的瞬时数据存储到设备中 1. 文件存储 存储一些简单的文本数据或二进制数据. 核心:Context类提供的openFileOutput()和openFileInput()方法,然后 ...

  6. iOS数据持久化-OC

    沙盒详解 1.IOS沙盒机制 IOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文 ...

  7. Docker数据持久化与容器迁移

    上节讲到当容器运行期间产生的数据是不会在写镜像里面的,重新用此镜像启动新的容器就会初始化镜像,会加一个全新的读写入层来保存数据.如果想做到数据持久化,Docker提供数据卷(Data volume)或 ...

  8. iOS中的数据持久化方式

    iOS中的数据持久化方式,基本上有以下四种:属性列表.对象归档.SQLite3和Core Data. 1.属性列表 涉及到的主要类:NSUserDefaults,一般 [NSUserDefaults ...

  9. iOS开发中的4种数据持久化方式【一、属性列表与归档解档】

    iOS中的永久存储,也就是在关机重新启动设备,或者关闭应用时,不会丢失数据.在实际开发应用时,往往需要持久存储数据的,这样用户才能在对应用进行操作后,再次启动能看到自己更改的结果与痕迹.ios开发中, ...

随机推荐

  1. java多线程学习笔记(八)

    本节开始线程间通信: 使用wait/notify实现线程间通信 生产者/消费者模式的实现 方法join的使用 ThreadLocal类的使用 可以通过使用 sleep() 结合 while(true) ...

  2. Codeforces Paths and Trees

    Paths and Trees time limit per test3 seconds memory limit per test256 megabytes Little girl Susie ac ...

  3. docker概述与安装及运行容器

    传统虚拟化 传统虚拟化步骤 1.安装虚拟化软件以及虚拟化的管理软件 2.创建虚拟机 3.给虚拟机安装os 4.在虚拟机内部不是应用(http.db之类的应用) 传统虚拟化的特点 1.VM与VM之间是完 ...

  4. elasticsearch Mapping 定义索引

    Mapping is the process of defining how a document should be mapped to the Search Engine, including i ...

  5. tomcat manager 禁止外网访问 只容许内网访问

    参考:http://tomcat.apache.org/tomcat-7.0-doc/manager-howto.html A default Tomcat installation includes ...

  6. 怀旧浪潮来袭,小霸王游戏、windows95......曾经的经典哪些能戳中你的心怀?

    随着前两天上架的 Rewound 在 iPhone 上复刻了 iPod Classic为大家掀起一场怀旧浪潮,那么除了 Rewound还有什么经典?今天我们就来怀旧一下那些曾经的经典.80经典小霸王游 ...

  7. Navicat连接MySQL8+时出现2059报错

    当我们连接时,会报2059错误 在用navicat连接MySQL8+时会出现2059错误,这是由于新版本的MySQL使用的是caching_sha2_password验证方式,但此时的navicat还 ...

  8. cocos2D-X 显示中文

    { 将所在的cpp文件改为utf-8 无签名格式再编译 //但,治标不治本 }

  9. Delphi 字符串函数StrUtils单元AnsiLeftStr、AnsiRightStr、AnsiMidStr、AnsiContainsStr、AnsiContainsText、AnsiStartsStr、AnsiStartsText、AnsiEndsStr、AnsiEndsText、AnsiReplaceStr、AnsiReplaceText、AnsiResemblesText...

    引用单元 StrUtils 非 SysUtilsAnsiLeftStr.AnsiRightStr.AnsiMidStr.AnsiContainsStr.AnsiContainsText.AnsiSta ...

  10. MaxCompute问答整理之10月

    本文是基于本人对MaxCompute产品的学习进度,再结合开发者社区里面的一些问题,进而整理成文.希望对大家有所帮助. 问题一.DataStudio中是否可以通过shell节点调取MaxCompute ...