将文件保存在设备存储上

File对象适用于从头到尾读取或写入大量数据而不会跳过。 例如,它适用于图像文件或通过网络交换的任何内容。
保存文件的位置的确切位置可能因设备而异,因此您应该使用此页面中描述的方法来访问内部和外部存储路径,而不是使用绝对文件路径。
要查看设备上的文件,您可以记录File.getAbsolutePath()等方法提供的文件位置,然后使用Android Studio的Device File Explorer浏览设备文件。

一、选择内部或外部存储

所有Android设备都有两个文件存储区:“内部”和“外部”存储。 这些名称来自Android的早期,当时大多数设备提供内置的非易失性存储器(内部存储),以及可移动存储介质,如micro SD卡(外部存储)。 现在,许多设备将永久存储空间划分为单独的“内部”和“外部”分区。 因此,即使没有可移动存储介质,这两个存储空间也始终存在,无论外部存储是否可移动,API行为都是相同的。
由于外部存储可能是可移除的,因此这两个选项之间存在一些差异,如下所示。

内部存储器:
  • 它始终可用。
  • 此处保存的文件只能由您的应用访问。
  • 当用户卸载您的应用程序时,系统会从内部存储中删除所有应用程序的文件。

如果您想确保用户和其他应用程序都无法访问您的文件,则最好使用内部存储。

外部存储:
  • 它并不总是可用,因为用户可以将外部存储装载为USB存储器,并在某些情况下将其从设备中移除。
  • 它是可读的,因此保存在此处的文件可能会在您的控件之外读取。
  • 当用户卸载您的应用程序时,仅当您将应用程序的文件保存在getExternalFilesDir()目录中时,系统才会从此处删除该应用程序的文件。

对于不需要访问限制的文件以及要与其他应用程序共享或允许用户使用计算机访问的文件,外部存储是最佳位置。

提示:虽然默认情况下应用程序安装在内部存储上,但您可以通过在清单中指定android:installLocation属性来允许将应用程序安装在外部存储上。 当APK大小非常大并且外部存储空间大于内部存储空间时,用户会欣赏此选项。

二、将文件保存在内部存储上

您的应用程序的内部存储目录由您的应用程序包名称指定在Android文件系统的特定位置,可以使用以下API访问。

注意:与外部存储目录不同,您的应用程序不需要任何系统权限来读取和写入这些方法返回的内部目录。

写一个文件

将文件保存到内部存储时,可以通过调用以下两种方法之一获取相应的目录作为文件:

getFilesDir()

返回表示应用程序内部目录的文件。

getCacheDir()

返回表示应用程序临时缓存文件的内部目录的文件。 确保在不再需要时删除每个文件,并对在任何给定时间使用的内存量(例如1MB)实施合理的大小限制。

警告:如果系统存储空间不足,则可能会在没有警告的情况下删除缓存文件。

要在其中一个目录中创建新文件,可以使用File()构造函数,传递上述方法之一提供的文件,该方法指定您的内部存储目录。 例如:

File file = new File(context.getFilesDir(), filename);

或者,您可以调用openFileOutput()来获取写入内部目录中文件的FileOutputStream。 例如,以下是如何将一些文本写入文件:

String filename = "myfile";
String fileContents = "Hello world!";
FileOutputStream outputStream;

try {
    outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
    outputStream.write(fileContents.getBytes());
    outputStream.close();
} catch (Exception e) {
    e.printStackTrace();
}

请注意,openFileOutput()方法需要文件模式参数。 传递MODE_PRIVATE会使其对您的应用程序保密。 自API级别17以来,其他模式选项MODE_WORLD_READABLEMODE_WORLD_WRITEABLE已被弃用。从Android 7.0(API级别24)开始,如果您使用Android,则会抛出SecurityException。 如果您的应用需要与其他应用共享私有文件,则应使用带有FLAG_GRANT_READ_URI_PERMISSIONFileProvider
在Android 6.0(API级别23)及更低级别上,如果您将文件模式设置为全局可读,则其他应用程序可以读取您的内部文件。 但是,其他应用必须知道您的应用包名称和文件名。 除非您明确将文件设置为可读或可写,否则其他应用程序无法浏览您的内部目录并且没有读取或写入权限。 因此,只要您将MODE_PRIVATE用于内部存储上的文件,其他应用就永远无法访问它们。

写一个缓存文件

如果您需要缓存某些文件,则应使用createTempFile()。 例如,以下方法从URL中提取文件名,并在应用程序的内部缓存目录中创建具有该名称的文件:

private File getTempFile(Context context, String url) {
    File file;
    try {
        String fileName = Uri.parse(url).getLastPathSegment();
        file = File.createTempFile(fileName, null, context.getCacheDir());
    } catch (IOException e) {
        // Error while creating file
    }
    return file;
}

使用createTempFile()创建的文件放在应用程序专用的缓存目录中。 您应该定期删除不再需要的文件。

警告:如果系统存储空间不足,可能会在没有警告的情况下删除缓存文件,因此请确保在读取之前检查缓存文件是否存在。

打开现有文件

要读取现有文件,请调用openFileInput(name),传递文件名。
您可以通过调用fileList()获取所有应用程序文件名的数组。

提示:如果您需要在应用程序中打包一个可在安装时访问的文件,请将该文件保存在项目的res / raw /目录中。 您可以使用openRawResource()打开这些文件,并传递R.raw.filename资源ID。 此方法返回可用于读取文件的InputStream。 您无法写入原始文件。

打开目录

您可以使用以下方法在内部文件系统上打开目录:

getFilesDir()

返回表示文件系统上与您的应用唯一关联的目录的文件。

getDir(name, mode)

在应用程序的唯一文件系统目录中创建新目录(或打开现有目录)。 这个新目录出现在getFilesDir()提供的目录中。

getCacheDir()

返回一个文件,表示文件系统上与您的应用唯一关联的缓存目录。 此目录适用于临时文件,应定期清理。 如果磁盘空间不足,系统可能会删除那里的文件,因此请确保在读取之前检查缓存文件是否存在。

要在其中一个目录中创建新文件,可以使用File()构造函数,传递上述方法之一提供的File对象,该方法指定内部存储目录。 例如:

File directory = context.getFilesDir();
File file = new File(directory, filename);

三、将文件保存在外部存储上

使用外部存储非常适合您要与其他应用共享或允许用户使用计算机访问的文件。
在请求存储权限并验证存储可用后,您可以保存两种不同类型的文件:

  • 公共文件:应该可供其他应用程序和用户免费使用的文件。 当用户卸载您的应用时,这些文件应该仍然可供用户使用。 例如,应用程序或其他下载文件捕获的照片应保存为公共文件。
  • 私人文件:合法属于您的应用的文件,将在用户卸载您的应用时删除。 虽然这些文件在技术上可由用户和其他应用程序访问,因为它们位于外部存储上,但它们不会为应用程序外的用户提供价值。

警告:如果用户卸下SD卡或将设备连接到计算机,外部存储可能会变得不可用。 并且文件仍然对用户和具有READ_EXTERNAL_STORAGE权限的其他应用程序可见。 因此,如果您的应用程序的功能取决于这些文件,或者您需要完全限制访问,则应将文件写入内部存储。

请求外部存储权限

要写入公共外部存储,您必须在清单文件中请求WRITE_EXTERNAL_STORAGE权限:

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

注意:如果您的应用使用WRITE_EXTERNAL_STORAGE权限,则它也隐式拥有读取外部存储的权限。

如果您的应用只需要读取外部存储(但不能写入),那么您需要声明READ_EXTERNAL_STORAGE权限:

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

从Android 4.4(API级别19)开始,在应用程序的私有外部存储目录中读取或写入文件(使用getExternalFilesDir()访问)不需要READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限。 因此,如果您的应用支持Android 4.3(API级别18)及更低版本,并且您只想访问专用外部存储目录,则应通过添加maxSdkVersion属性声明仅在较低版本的Android上请求权限:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
    ...
</manifest>
验证外部存储是否可用

由于外部存储可能不可用 - 例如当用户将存储装置安装到PC或已移除提供外部存储的SD卡时 - 您应始终在访问之前验证该卷是否可用。 您可以通过调用getExternalStorageState()来查询外部存储状态。 如果返回的状态为MEDIA_MOUNTED,则可以读取和写入文件。 如果是MEDIA_MOUNTED_READ_ONLY,则只能读取文件。
例如,以下方法可用于确定存储可用性:

/* 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;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}
保存到公共目录

如果要将公共文件保存在外部存储上,请使用getExternalStoragePublicDirectory()方法获取表示外部存储上相应目录的File。 该方法接受一个参数,指定要保存的文件类型,以便可以使用其他公共文件(如DIRECTORY_MUSICDIRECTORY_PICTURES)对其进行逻辑组织。 例如:

public File getPublicAlbumStorageDir(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;
}

如果要从Media Scanner中隐藏文件,请在外部文件目录中包含名为.nomedia的空文件(请注意文件名中的点前缀)。 这可以防止媒体扫描程序读取您的媒体文件,并通过MediaStore内容提供商将其提供给其他应用程序。

保存到私人目录

如果要将文件保存在应用程序专用的外部存储上且MediaStore内容提供程序无法访问,您可以通过调用getExternalFilesDir()并向其传递一个指示目录类型的名称来获取仅由您的应用程序使用的目录。 你想。 以这种方式创建的每个目录都会添加到父目录中,该目录封装了应用程序的所有外部存储文件,系统会在用户卸载应用程序时将其删除。

警告:外部存储上的文件并非始终可访问,因为用户可以将外部存储装载到计算机以用作存储设备。 因此,如果您需要存储对应用程序功能至关重要的文件,则应将其存储在内部存储中。

例如,这是一种可用于为单个相册创建目录的方法:

public File getPrivateAlbumStorageDir(Context context, String albumName) {
    // Get the directory for the app's private pictures directory.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

如果没有预定义的子目录名称适合您的文件,则可以调用getExternalFilesDir()并传递null。 这将返回外部存储上应用程序私有目录的根目录。

请记住,getExternalFilesDir()会创建一个在用户卸载应用程序时删除的目录。 如果您保存的文件在用户卸载应用程序后仍然可用 - 例如当您的应用程序捕获照片并且用户应保留这些照片时 - 您应该将文件保存到公共目录。

无论您对共享文件使用getExternalStoragePublicDirectory()还是对应用程序专用的文件使用getExternalFilesDir(),都必须使用API常量(如DIRECTORY_PICTURES)提供的目录名称。 这些目录名称可确保系统正确处理文件。 例如,保存在DIRECTORY_RINGTONES中的文件被系统媒体扫描仪分类为铃声而不是音乐。

在多个存储位置之间选择

有时,分配内部存储器分区以用作外部存储器的设备也提供SD卡插槽。 这意味着该设备有两个不同的外部存储目录,因此您需要选择在将“私有”文件写入外部存储时使用哪个目录。

从Android 4.4(API级别19)开始,您可以通过调用getExternalFilesDirs()来访问这两个位置,该方法返回一个文件数组,其中包含每个存储位置的条目。 阵列中的第一个条目被视为主要外部存储,您应该使用该位置,除非它已满或不可用。

如果您的应用支持Android 4.3及更低版本,则应使用支持库的静态方法ContextCompat.getExternalFilesDirs()。 这总是返回一个文件阵列,但如果设备运行的是Android 4.3及更低版本,那么它只包含一个主外部存储条目(如果有第二个存储位置,则无法在Android 4.3及更低版本上访问它)。

四、查询可用空间

如果您提前知道要保存多少数据,则可以通过调用getFreeSpace()getTotalSpace()来确定是否有足够的空间可用而不会导致IOException。 这些方法分别提供当前可用空间和存储卷中的总空间。 此信息对于避免将存储卷填充到某个阈值以上也很有用。
但是,系统不保证您可以写入getFreeSpace()指示的字节数。 如果返回的数字比您要保存的数据大小多几MB,或者文件系统小于90%已满,则可以继续。 否则,您可能不应该写入存储。

注意:在保存文件之前,无需检查可用空间量。 您可以尝试立即编写文件,然后在发生IOException时捕获它。 如果您不确切知道需要多少空间,则可能需要执行此操作。 例如,如果在通过将PNG图像转换为JPEG来保存之前更改文件的编码,则事先不会知道文件的大小。

五、删除文件

您应该始终删除应用不再需要的文件。 删除文件最直接的方法是在File对象上调用delete()

myFile.delete();

如果文件保存在内部存储器上,您还可以通过调用deleteFile()来请求Context查找并删除文件:

myContext.deleteFile(fileName);

注意:当用户卸载您的应用时,Android系统会删除以下内容:

  • 您在内部存储上保存的所有文件。
  • 使用getExternalFilesDir()保存外部存储的所有文件。

但是,您应该定期手动删除使用getCacheDir()创建的所有缓存文件,并定期删除不再需要的其他文件。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,894评论 2 89
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,463评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,691评论 2 59
  • 为人处世,身口意避免七伤拳的方式 做事以义工的心态去做。 看人都是菩萨的心态去看。
    西瓜_b4f5阅读 413评论 0 0