文章目录
- 目的
- 基础说明
- 应用专属存储空间
- 共享存储
- 其它
- 总结
目的
APP开发与使用过程中免不了和文件打交道,对于Windows或Linux而言文件的存储与访问操作都很方便,直接通过文件系统路径和文件接口操作就行。在Android中因为安全原因,这方面变得有些复杂,这篇文章将对相关内容做个梳理记录。
这篇文章的内容主要参考下面官方文档:
https://developer.android.google.cn/guide/topics/data
文件存储与访问相关操作受Android SDK API版本影响,本文编写时 target api = 31。
基础说明
Android中对于APP开发而言文件存储主要可以分为两大部分 应用专属存储空间 和 共享存储 。
应用专属存储空间
应用专属存储空间又可以分为 内部存储 和 外部存储 ,这每一块又可以分别用来存储 持久性文件 和 缓存文件 。通常而言内部存储位于 /data/user/
或 /data/data/
下,外部存储位于 /Android/data/
下。持久性文件目录为 files
,缓存文件目录为 cache
。
需要注意的是上面的 外部 只是一种逻辑上的划分,和实际硬件的存储器组织方式无关,特别是对于现在大多数不支持扩展卡的设备而言这些区域都是位于设备内部存储器上的。
内部存储空间 - 持久性文件 使用 getFilesDir()
来获取其根目录路径:
知道路径就可以用传统的方式来操作文件了:
File file = new File(context.getFilesDir(), filename);
不过Android也可以直接操作文件,可以理解为这块空间就是默认路径:
String filename = "naisu.txt";
// 写文件
String fileContents = "Hello Naisu!";
try (FileOutputStream fos = openFileOutput(filename, MODE_PRIVATE)) {
fos.write(fileContents.getBytes());
}
// 读文件
FileInputStream fis = openFileInput(filename);
InputStreamReader inputStreamReader = new InputStreamReader(fis, StandardCharsets.UTF_8);
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
// Read File
} catch (IOException e) {
// Error occurred when opening raw file for reading.
}
// 文件列表
String[] files = fileList();
内部存储空间 - 缓存文件 使用 getCacheDir()
来获取其根目录路径:
下面是一些缓存文件常用操作:
// 创建缓存文件
File.createTempFile(filename, null, getCacheDir());
// 访问缓存文件
File cacheFile = new File(getCacheDir(), filename);
// 删除缓存文件
cacheFile.delete(); 或 deleteFile(filename);
外部存储空间 - 持久性文件 使用 getExternalFilesDir()
来获取其根目录路径:
这里主要操作就是一般的文件操作:
File appSpecificExternalDir = new File(getExternalFilesDir(null), filename);
这个目录下新建的子目录名称也可以使用预设的,比如下面代码中的 DIRECTORY_PICTURES
属于SDK定义的目录
(https://developer.android.google.cn/reference/android/os/Environment#fields):
@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
// Get the pictures directory that's inside the app-specific directory on external storage.
File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), albumName);
if (file == null || !file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
在 Android 11(API 级别 30)及更高版本中这个目录有更多限制了。
外部存储空间 - 缓存文件 使用 getExternalCacheDir()
来获取其根目录路径:
这里主要操作就是一般的文件操作:
File externalCacheFile = new File(getExternalCacheDir(), filename);
externalCacheFile.delete();
共享存储
现在的Android设备很多都会自带文件管理器,打开文件管理器默认可以看到 Android Download Music 等目录,对用户而言这些目录是比较常用的目录,这些目录通常位于 /storage/emulated/0/
目录下,这个路径可以通过下面方法获取:
Environment.getExternalStorageDirectory();
// Environment.getExternalStoragePublicDirectory("");
通常来说应用与应用或应用与用户都可以访问的共享存储空间主要就是上面目录了。该目录默认设计主要用于存放媒体内容(Media),通常根据内容会分为下面几类进行存储:
- Images 主要位于
DCIM/
Pictures/
- Video 主要位于
DCIM/
Movies/
Pictures/
- Audio 主要位于
Alarms/
Audiobooks/
Music/
Notifications/
Podcasts/
Ringtones/
- Downloads 主要位于
Download/
- Files
上面内容都是官方默认的分类,这些区域都属于MediaStore,可以有专门的方式去访问,或者也可以以文件方式访问。
在Android 10和11之前很多APP根据自己喜好来,在 /storage/emulated/0/
下建个自己的目录,所有东西都丢里面。现在谷歌不推荐怎么来了,当然也没把路堵死,可以通过启用 MANAGE_EXTERNAL_STORAGE
权限来解锁。
共享存储空间操作通常需要声明相关权限:
<!-- Required only if your app needs to access images or photos that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Required only if your app needs to access videos that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Required only if your app needs to access audio files that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- If your app doesn't need to access media files that other apps created, set the "maxSdkVersion" attribute to "28" instead. -->
<!-- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" /> -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android11开始,提供对应用专属目录和 MediaStore 之外文件的写入权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Android10特供,停用分区存储 -->
<!-- <application android:requestLegacyExternalStorage="true"> -->
<!-- 如果应用是在Android11以下版本时安装,系统升级到Android11后应用将保留其之前安装时的存储结构 -->
<!-- <application android:requestLegacyExternalStorage="true"> -->
有的权限开启可能需要用户主动确认:
public void checkPermission() {
// 需要用到的权限
String[] permissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
};
if (android.os.Build.VERSION.SDK_INT >= 23) {
ArrayList<String> list = new ArrayList<>();
for (String item : permissions) {
// 检查权限是否开启
if (this.checkSelfPermission(item) != PackageManager.PERMISSION_GRANTED) {
list.add(item);
}
}
if (list.size() != 0) {
String[] arr = new String[list.size()];
list.toArray(arr);
this.requestPermissions(arr, 233); // 开启弹窗让用户确认
}
}
}
下面是个关于上面内容的简单的文件操作演示:
上面演示中在应用启用时会去检查相关权限,如果权限未开启则让用户确认开启;通过定时器定时查询权限情况,如果权限开启了则建立目录与文件。注意AndroidManifest.xml中也需要添加权限
另外提一句像 /storage/emulated/0/
这种目录下很多公共资源都可以在设备上浏览器中使用 file:///storage/emulated/0/...
进行浏览。
其它
res/raw/
在应用开发时很多资源都是放在项目工程的 res/
目录下的,这些都会被编译成二进制文件打包到apk中,也可以在该目录下建立raw目录,即 res/raw/
这下面也可以放置文件,且不会被编译成二进制文件。在程序中使用的话就和其它 res/
目录下的资源一样,通过 R.id.filename
进行访问。
assets/
可以在 项目目录/app/src/main/
下建立 assets
目录,在其中放置的资源文件也会被打包到apk中。这里的内容需要使用 AssetManager
类进行访问。还有一种使用方式是在webView中通过 myWebView.loadUrl("file:///android_asset/...")
形式进行加载。
需要注意的是这里的两个目录下的文件可能不支持大于1M的文件。
总结
本文主要讨论了Android中文件存储与访问的一些基本内容,其中稍微和其它平台不同的就是Android中对于APP可见的文件系统结构,总体来说使用起来也并不复杂。本文中没有提到存储空间检查、访问权限、存储卡以及其它很多情况,这些内容可以文章开头给出的文档链接中获取相关信息。
除了上面内容外常用的像是轻量数据和数据库、应用间数据和文件交互等内容均没有涉及,这些内容虽然最终也是以文件形式来保存数据的,但是APP开发者不用关系其具体的存储位置。这些内容可以在文章开头给出的文档链接中获取相关信息。