前言

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

内部存储

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

写入数据

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的文件写入方式操作就行了。

读取数据

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的文件读取方式操作就行了。

读取静态文件

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

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文件夹中,文件名只能包含小写字母、数字和下划线。

缓存数据

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

public abstract File getCacheDir();

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

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

外部存储

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

获取权限

和访问内部存储不同,要读取外部存储中的文件首先需要获取权限,即READ_EXTERNAL_STORAGEWRITE_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)引入了运行时权限的概念,以上提到的两种权限都需要动态地获取。如果不了解运行时权限的概念,可以参考这篇博客:

详解Android权限机制

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

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

可用性检查

由于外部存储存在被移除的情况,我们在使用外部存储前首先应该进行可用性检查。使用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种返回值,如下:

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

公共文件(共享文件)

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

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

public static File getExternalStoragePublicDirectory(String type);

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

  1. DIRECTORY_MUSIC:音乐类型
  2. DIRECTORY_PICTURES:图片类型
  3. DIRECTORY_MOVIES:电影类型
  4. DIRECTORY_DCIM:照片类型
  5. DIRECTORY_DOWNLOADS:下载文件类型
  6. DIRECTORY_DOCUMENTS:文档类型
  7. DIRECTORY_RINGTONES:铃声类型
  8. DIRECTORY_ALARMS:闹钟提示音类型
  9. DIRECTORY_NOTIFICATIONS:通知提示音类型
  10. 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的空文件,这会阻止媒体扫描器归类我们的文件并提供给其他应用。

私有文件

对于应用私有的文件,则应该使用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中定义的常量):

  1. DIRECTORY_MUSIC:音乐类型
  2. DIRECTORY_PICTURES:图片类型
  3. DIRECTORY_MOVIES:电影类型
  4. DIRECTORY_RINGTONES:铃声类型
  5. DIRECTORY_ALARMS:闹钟提示音类型
  6. DIRECTORY_NOTIFICATIONS:通知提示音类型
  7. DIRECTORY_PODCASTS:播客音频类型

注意,当应用卸载时,这些私有存储目录中的文件也会被删除。此外,虽然系统的媒体扫描器不会访问外部存储中的私有存储目录,但是其他具有READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限的应用依旧可以读/写这些私有存储目录中的文件。因此对于真正重要的文件,还是应该保存在应用的内部存储中。

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

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

缓存文件

在外部存储中也有专门保存缓存文件的空间,可以通过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/

其他常用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目录。注意,这个分区始终处于只读状态。

参考资料

官方文档:https://developer.android.google.cn/guide/topics/data/data-storage.html#db

demo

下载地址:FileSystemDemo

Android文件系统详解相关推荐

  1. Android.mk详解

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/52875825 文章出自:薛瑄的博客 你也可以查看我的其他同类文章,也会让你有一定的 ...

  2. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  3. Android菜单详解——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  4. Android LayoutInflater详解

    Android LayoutInflater详解 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类 似于findViewById().不同点是LayoutInflater是用来 ...

  5. android Fragments详解

    android Fragments详解一:概述 android Fragments详解二:创建Fragment 转载于:https://my.oschina.net/liangzhenghui/blo ...

  6. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  7. android WebView详解,常见漏洞详解和安全源码(上)

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...

  8. android子视图无菜单,Android 菜单详解

    Android中菜单分为三种,选项菜单(OptionMenu),上下文菜单(ContextMenu),子菜单(SubMenu) 选项菜单 可以通过两种办法增加选项菜单,一是在menu.xml中添加,该 ...

  9. Android StateFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121913352 本文出自[赵彦军的博客] 文章目录 系列文章 一.冷流还是热流 S ...

最新文章

  1. 张祥雨团队最新工作:用于物体检测的实例条件知识蒸馏 | NeurIPS 2021
  2. 大话中文文本分类之TextRNN
  3. ELSE 技术周刊(2017.11.20期)
  4. 第三次上课 PPT 课后测试
  5. LuaBridge 中C++类和继承示例
  6. 使用Android DataBinding BindingAdapter和Dagger 2
  7. PostgreSQL——不仅仅是监控
  8. 【Computer Organization笔记23】非易失性存储:磁表面存储设备,磁盘的访问过程,RAID技术
  9. expect的安装与使用
  10. listview添加item动画
  11. 关于【缓存穿透、缓存击穿、缓存雪崩、热点数据失效】解决方案
  12. Android默认头像那些事儿
  13. Windows下动过批处理指令在浏览器中打开指定文件中的URL地址
  14. 高中数学相关的专业术语
  15. 验证码识别PaddleOCR 快速开始
  16. java集合(附源码分析)
  17. Easy Excel使用说明
  18. Kubernetes弃用Docker?关于Kubernetes、Docker和containerd的那些事
  19. final、finally与finalize三者的区别
  20. 高斯与最小二乘法的故事

热门文章

  1. PXE kickstart 安装时候出现/dev/root does not exits
  2. 3D可视化孪生系统之燃气展示方案
  3. chatgpt一键生成 PPT
  4. OneAPM使用笔记 BI应用性能管理
  5. JavaWeb(5)——XML Tomcat
  6. topic是短语还是句子_英语topic
  7. 【AI面试】目标检测中one-stage、two-stage算法的内容和优缺点对比汇总
  8. 第二周 生活随笔——记录平凡生活中的唯一瞬间
  9. 2023年计算机考研难度大吗?
  10. 给文件夹添加everyone用户