在Android上写入外部SD卡的通用方法


76

在我的应用程序中,我需要在设备存储中存储大量图像。这样的文件往往可以满足设备的存储需求,我希望用户可以选择外部SD卡作为目标文件夹。

我到处都读到Android不允许用户写入外部SD卡,所谓SD卡,是指外部可安装的SD卡,而不是外部存储,但是文件管理器应用程序设法在所有Android版本上写入外部SD。

在不同的API级别(Pre-KitKat,KitKat,Lollipop +)上授予对外部SD卡的读/写访问权限的更好方法是什么?

更新1

我从Doomknight的答案中尝试了方法1,但无济于事:如您所见,在尝试在SD上写入之前,我正在运行时检查权限:

HashSet<String> extDirs = getStorageDirectories();
for(String dir: extDirs) {
    Log.e("SD",dir);
    File f = new File(new File(dir),"TEST.TXT");
    try {
        if(ActivityCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)==PackageManager.PERMISSION_GRANTED) {
            f.createNewFile();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

但是我收到访问错误,尝试在两种不同的设备上进行:HTC10和Shield K1。

10-22 14:52:57.329 30280-30280/? E/SD: /mnt/media_rw/F38E-14F8
10-22 14:52:57.329 30280-30280/? W/System.err: java.io.IOException: open failed: EACCES (Permission denied)
10-22 14:52:57.329 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:939)
10-22 14:52:57.329 30280-30280/? W/System.err:     at com.myapp.activities.TestActivity.onResume(TestActivity.java:167)
10-22 14:52:57.329 30280-30280/? W/System.err:     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1326)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.Activity.performResume(Activity.java:6338)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3336)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3384)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2574)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.access$900(ActivityThread.java:150)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1399)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Looper.loop(Looper.java:168)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5885)
10-22 14:52:57.330 30280-30280/? W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:819)
10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:709)
10-22 14:52:57.330 30280-30280/? W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.Posix.open(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
10-22 14:52:57.330 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:932)
10-22 14:52:57.330 30280-30280/? W/System.err:  ... 14 more

系统应用程序可以完全访问外部SD卡存储,但其他应用程序则不能,除非操作系统和应用程序具有root访问权限
Pavneet_Singh

4
@PavneetSingh这不是真的,所有文件浏览器应用程序都可以访问外部sd卡,即使没有root用户也可以。
Vektor88

您正在谈论哪个文件浏览器?因为一些著名的,所以使用生根脚本访问SD卡
Pavneet_Singh 16-10-16

1
他们使用我告诉您的方法,进行测试,只需要安装kitkat OS(无根)并安装ES,然后尝试从中删除文件,您会收到警告(这可能使您的手机变砖),要求应用root处理过程需要您自担风险
Pavneet_Singh

1
从连接线我强烈建议你从来没有靠这个代码,就像我说的只有你的应用程序不能做到这一点,但媒体提供商是一个系统应用程序,所以你可以利用它功能来尽你所能
Pavneet_Singh

Answers:


120

概要

您可以在不同的api级别运行时为API23 +)上授予对外部SD卡的读/写访问权限

由于奇巧,权限是没有必要的,如果你使用特定应用程序的目录,需要的除外。

通用方式:

历史上说,没有写入外部SD卡普遍的方式 ,但继续...

这些事实通过设备的外部存储配置这些示例得以说明

基于API的方式:

在使用KitKat之前,请尝试使用Doomsknight方法1,否则使用方法2。

在清单(Api <23)和运行时(Api> = 23)中请求权限。

推荐方式:

当您不需要共享文件时,ContextCompat.getExternalFilesDirs解决了访问错误

共享它的安全方法是使用内容提供程序新的Storage Access Framework

隐私保护方式:

从Android Q Beta 4开始,默认情况下,面向Android 9(API级别28)或更低版本的应用程序看不到任何更改。

默认情况下(或选择启用)定位到Android Q的应用将获得过滤后的外部存储视图


1.初步答案。

在Android上写入外部SD卡的通用方法

由于不断变化,因此没有通用的方法可以在Android上写入外部SD卡

  • KitKat之前:官方Android平台完全不支持SD卡,除了例外。

  • KitKat:引入了API,使应用程序可以访问SD卡上特定于应用程序的目录中的文件。

  • 棒棒糖:添加了API,以允许应用程序请求访问其他提供商拥有的文件夹。

  • 牛轧糖:提供了简化的API以访问常见的外部存储目录。

  • ... Android Q隐私更改:应用程序范围和媒体范围的存储

在不同的API级别上授予对外部SD卡的读/写访问权限的更好方法是什么

基于Doomsknight的回答我的,和戴夫·史密斯马克·墨菲博客文章:123


2.更新了答案。

更新1。我从《毁灭战士》的答案中尝试了方法1,但无济于事:

如您所见,在尝试在SD上写入之前,我正在运行时检查权限...

我将使用特定应用程序的目录来避免更新问题的出现,并ContextCompat.getExternalFilesDirs()使用getExternalFilesDir文档作为参考。

改进启发式方法,以根据不同的api级别(例如,android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

...但是我遇到了访问错误,在两个不同的设备上进行了尝试:HTC10和Shield K1。

请记住,Android 6.0支持便携式存储设备,并且第三方应用必须通过Storage Access Framework进行检查。您的设备HTC10Shield K1可能是API 23。

您的日志显示权限被拒绝的异常访问/mnt/media_rw,例如API 19+的此修复程序

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_r" />
<group gid="sdcard_rw" />
<group gid="media_rw" /> // this line is added via root in the link to fix it.
</permission>

我从没有尝试过,所以我不能共享代码,但是我会避免for尝试在所有返回的目录上进行写操作,并根据剩余空间寻找要写入的最佳可用存储目录

也许Gizm0可以替代您的getStorageDirectories()方法,这是一个很好的起点。

ContextCompat.getExternalFilesDirs如果您不需要访问其他文件夹,则可以解决此问题


3. Android 1.0 .. Pre-KitKat。

在KitKat之前,请尝试使用Doomsknight方法1或通过Gnathonic阅读此响应

public static HashSet<String> getExternalMounts() {
    final HashSet<String> out = new HashSet<String>();
    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
    String s = "";
    try {
        final Process process = new ProcessBuilder().command("mount")
                .redirectErrorStream(true).start();
        process.waitFor();
        final InputStream is = process.getInputStream();
        final byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            s = s + new String(buffer);
        }
        is.close();
    } catch (final Exception e) {
        e.printStackTrace();
    }

    // parse output
    final String[] lines = s.split("\n");
    for (String line : lines) {
        if (!line.toLowerCase(Locale.US).contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase(Locale.US).contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

将下一个代码添加到您的代码中,AndroidManifest.xml并阅读获取对外部存储的访问权限

对外部存储的访问受到各种Android权限的保护。

从Android 1.0开始,写入访问WRITE_EXTERNAL_STORAGE权限权限保护。

从Android 4.1开始,读取访问受到该READ_EXTERNAL_STORAGE权限的保护。

为了...在外部存储设备上写入文件,您的应用必须获得...系统权限:

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

如果同时需要...,则只需要请求WRITE_EXTERNAL_STORAGE权限。

阅读Mark Murphy的解释推荐 Dianne HackbornDave Smith的帖子

  • 直到Android 4.4为止,Android才没有对可移动媒体的官方支持。从KitKat开始,FMW API中出现了“主要”和“次要”外部存储的概念。
  • 先前的应用程序仅依靠MediaStore索引,随硬件一起提供或检查安装点并应用一些启发式方法来确定代表可移动媒体的内容。

4. Android 4.4 KitKat引入了存储访问框架(SAF)

由于错误而忽略下一个注释,但是请尝试使用ContextCompat.getExternalFilesDirs()

  • 自Android 4.2以来,Google一直要求设备制造商锁定可移动媒体以提高安全性(多用户支持),并在4.4中添加了新测试。
  • 由于getExternalFilesDirs()添加了KitKat和其他方法,以在所有可用的存储卷上返回可用路径(返回的第一项是主卷)。
  • 下表说明了开发人员可能尝试做的事情以及KitKat将如何响应: 在此处输入图片说明

注意:从Android 4.4开始,如果您仅读取或写入应用程序专用的文件,则不需要这些权限。有关更多信息...,请参阅保存应用程序专用文件

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

还请阅读Paolo Rovelli的解释,并从KitKat开始尝试使用Jeff Sharkey的解决方案

在KitKat中,现在有一个公共API可与这些辅助共享存储设备进行交互。

新方法Context.getExternalFilesDirs()Context.getExternalCacheDirs()方法可以返回多个路径,包括主设备和辅助设备。

然后,您可以遍历它们并检查Environment.getStorageState()File.getFreeSpace()确定存储文件的最佳位置。

ContextCompat在support-v4库中也可以使用这些方法。

从Android 4.4开始,现在基于目录结构综合了外部存储设备上文件的所有者,组和模式。这使应用程序可以在外部存储上管理其特定于软件包的目录,而无需获得广泛的 WRITE_EXTERNAL_STORAGE许可。例如,具有包名称的应用com.example.foo现在可以Android/data/com.example.foo/在没有权限的情况下自由访问 外部存储设备。通过将原始存储设备包装在FUSE守护程序中,可以实现这些综合权限。

使用KitKat,您获得“无根”的“完整解决方案”的机会几乎为零:

Android项目肯定在这里搞砸了。没有应用程序可以完全访问外部SD卡:

  • 文件管理器:您不能使用它们来管理外部SD卡。在大多数区域中,它们只能读取而不能写入。
  • 媒体应用程序:您无法再对媒体收藏集进行重新标记/重新组织,因为这些应用程序无法对其进行写入。
  • 办公应用:几乎相同

唯一的地方3第三方应用程序可以写你的外置卡上有“自己的目录”(即 /sdcard/Android/data/<package_name_of_the_app>)。

真正修复的唯一方法是需要制造商(其中一些修复了该问题,例如,华为为其P6进行了Kitkat更新)或root用户(Izzy的解释在此处继续)


5. Android 5.0引入了更改和DocumentFile帮助器类。

getStorageState已在API 19中添加,在API 21中已弃用,请 使用getExternalStorageState(File)

这是与KitKat中的Storage Access Framework进行交互的出色教程

与Lollipop中的新API交互非常相似(Jeff Sharkey的解释)


6. Android 6.0 Marshmallow引入了新的运行时权限模型。

请求权限在运行时,如果API级别23+和阅读在运行时请求权限

从Android 6.0(API级别23)开始,用户可以在应用运行时向应用授予权限,而不是在安装应用...或更新应用...时授予用户权限。

// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_EXTERNAL_STORAGE);

Android 6.0引入了新的运行时权限模型,其中,应用程序在运行时需要时会请求功能。由于新模型包括READ/WRITE_EXTERNAL_STORAGE权限,因此平台需要动态授予存储访问权限,而不会终止或重新启动已经运行的应用程序。它通过维护所有已安装的存储设备的三个不同视图来实现此目的:

  • / mnt / runtime / default显示给没有特殊存储权限的应用...
  • / mnt / runtime / read显示给具有READ_EXTERNAL_STORAGE的应用
  • / mnt / runtime / write显示给具有WRITE_EXTERNAL_STORAGE的应用

7. Android 7.0提供了简化的API以访问外部存储目录。

作用域目录访问 在Android 7.0中,应用程序可以使用新的API请求访问特定的 外部存储目录,包括可移动媒体(例如SD卡)上的目录...

有关更多信息,请参见作用域目录访问培训

阅读Mark Murphy的文章:小心范围内的目录访问在Android Q中已弃用

请注意,Android Q中已弃用了7.0中添加的作用域目录访问。

具体来说,不建议使用StorageVolumecreateAccessIntent()上的方法。

他们添加了一个createOpenDocumentTreeIntent()可以替代的方式。


8. Android 8.0 Oreo .. Android Q Beta更改。

从Android O开始,Storage Access Framework允许自定义文档提供商 为驻留在远程数据源中的文件创建可搜索的文件描述符。

权限在Android O之前,如果某个应用程序在运行时请求了权限并被授予该权限,则系统还会错误地向该应用程序授予属于同一权限组且已在清单中注册的其余权限。

对于面向Android O的应用,此问题已得到纠正。仅向该应用授予已明确请求的权限。但是,一旦用户向该应用程序授予了权限,该权限组中所有随后的权限请求都将被自动授予。

例如,READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE...

更新: Android Q早期的Beta版本暂时将READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限替换为更细粒度的特定于媒体的权限。

注意: Google在Beta 1上引入了角色,并在Beta 2之前将其从文档中删除了...

注:该权限具体到媒体集合在早期测试releases-中引入READ_MEDIA_IMAGESREAD_MEDIA_AUDIOREAD_MEDIA_VIDEO-现已过时更多信息:

Mark Murphy撰写的Q Beta 4(最终API)评论:外部存储的消亡:Saga的终结(?)

“死亡比生命更普遍。每个人死亡,但不是每个人都活着。” ―安德鲁·萨克斯


9.相关问题和推荐答案。

如何获取Android 4.0+的外部SD卡路径?

mkdir()在内部闪存中工作,但不能在SD卡中工作吗?

getExternalFilesDir和getExternalStorageDirectory()之间的差异

为什么getExternalFilesDirs()在某些设备上不起作用?

如何使用为Android 5.0(Lollipop)提供的新SD卡访问API

在Android 5.0及更高版本中写入外部SD卡

使用SAF(存储访问框架)的Android SD卡写许可

SAFFAQ:存储访问框架常见问题


10.相关的错误和问题。

错误:在Android 6上,当使用getExternalFilesDirs时,它不允许您在结果中创建新文件

没有写许可权,无法在Lollipop上写入由getExternalCacheDir()返回的目录


2
非常深入的分析。+1。尽管我必须注意,但我没有编写该方法。它是几年前从另一个来源获得的:)希望它能对他有所帮助。
IAmGroot '16

感谢您的完整答案。在我看来,这里的问题是我应该使用SAF写入SD卡的根目录,而不是使用@Doomsknight方法1,因为它无法在更高的API级别上使用,而在KitKat以下可以使用它。
Vektor88

Np,如果您需要在应用程序目录之外编写代码,似乎是使用官方API的正确方法。看来这里解决了类似的问题,我会改天检查。
albodelu

4.4.2手机中的ES文件浏览器如何创建Folder?@albodelu能否请您解释一下
karanatwal.github.io

在第3点中,有一个链接介绍了如何针对有根设备执行此操作。该应用程序具有禁用kitKat中添加的限制的选项:工具>根资源管理器>装载R / W
albodelu

10

我相信有两种方法可以实现此目的:

方法1: (不上工作6.0及以上,由于权限更改)

我已经在许多设备版本上使用这种方法多年,没有任何问题。信用归功于原始资料,因为不是我写的。

它将在字符串目录位置列表中返回所有已装入的媒体(包括Real SD卡)。使用列表,您可以询问用户保存位置等。

您可以使用以下命令进行调用:

 HashSet<String> extDirs = getStorageDirectories();

方法:

/**
 * Returns all the possible SDCard directories
 */
public static HashSet<String> getStorageDirectories() {
    final HashSet<String> out = new HashSet<String>();
    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
    String s = "";
    try {
        final Process process = new ProcessBuilder().command("mount")
                .redirectErrorStream(true).start();
        process.waitFor();
        final InputStream is = process.getInputStream();
        final byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            s = s + new String(buffer);
        }
        is.close();
    } catch (final Exception e) {
        e.printStackTrace();
    }

    // parse output
    final String[] lines = s.split("\n");
    for (String line : lines) {
        if (!line.toLowerCase().contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase().contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

方法2:

使用v4支持库

import android.support.v4.content.ContextCompat;

只需拨打以下电话即可获得File存储位置列表。

 File[] list = ContextCompat.getExternalFilesDirs(myContext, null);

但是,位置在用法上有所不同。

将绝对路径返回到应用程序可以放置其拥有的持久文件的所有外部存储设备上的应用程序特定目录的绝对路径。这些文件是应用程序的内部文件,通常作为介质对用户不可见。

此处返回的外部存储设备被视为设备的永久部分,包括模拟的外部存储和物理介质插槽,例如电池仓中的SD卡。返回的路径不包括瞬态设备,例如USB闪存驱动器。

应用程序可以在任何或所有返回的设备上存储数据。例如,一个应用可能选择在设备上以最大可用空间存储大文件

有关ContextCompat的更多信息

它们就像应用程序特定的文件。隐藏在其他应用程序中。


1
请在方法1中共享获得代码的链接。谢谢。
Eugen Pechanec '16

2
@EugenPechanec我完全同意。但是,几年前,我从一个很棒的来源获得了此代码。我会看看
IAmGroot '16

1
我不清楚这些路径应该怎么做:如果设备不是root用户,则无法通过指定其绝对路径来获得对sdcard的写访问权限。我错了吗?也许我可以将一些数据存储在/external_sd/data/data/com.myapp ...中的专用应用程序文件夹中,但是我将无法写入/ external_sd / MyApp / Images
Vektor88 '16

1
@ vektor88您可以无根访问它。我和我的客户也从未使用过植根设备。你遇到了什么错误?您是否添加了权限。如下所述,您将目标定位为最新的并在运行时请求权限。在解决运行时权限请求之前,将target更改为20并查看它是否有效。否则请提供错误。
IAmGroot '16

1
@ Vektor88这个概念纯粹是一个测试,以查看您现在是否遇到权限问题。如果您以api 23为目标,那么它会提出问题,是否要求您在运行时授予权限。尽管这与获取SDCard位置以进行保存的愿望是一个单独的问题(最初受到质疑)。developer.android.com/training/permissions/requesting.html
IAmGroot

3

只是另一个答案。该答案仅显示5.0+,因为我认为此处发布的《末日骑士》答案是针对Android 4.4及更低版本的最佳方法。

这是我最初在这里发布的(有没有办法在Android中获取SD卡的大小?),以便在Android 5.0+上获取外部SD卡的大小

要将外部SD卡作为File

public File getExternalSdCard() {
    File externalStorage = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        File storage = new File("/storage");

        if(storage.exists()) {
            File[] files = storage.listFiles();

            for (File file : files) {
                if (file.exists()) {
                    try {
                        if (Environment.isExternalStorageRemovable(file)) {
                            externalStorage = file;
                            break;
                        }
                    } catch (Exception e) {
                        Log.e("TAG", e.toString());
                    }
                }
            }
        }
    } else {
        // do one of many old methods
        // I believe Doomsknight's method is the best option here
    }

    return externalStorage;
}

注意:我只获得“第一个”外部sd卡,但是您可以对其进行修改并返回,ArrayList<File>而不是返回File,让循环继续进行,而不是break在找到第一个卡之后调用。


感谢@ th3pat3lisExternalStorageRemovable参考developer.android.com/reference/android/os/...
albodelu

1
这只能根据您的应用程序访问外部SD卡子目录:例如,在我的LG Phone上,它会给我:/storage/0403-0201/Android/data/com.your.application/files-如果要添加到DCIM目录的文件-即使您在清单中具有权限,并且在运行时要求它们,然后在设置下进入application-> permissions并打开“ STILL GET ENOENT”(没有这样的文件或目录) )错误,如果您尝试在此处创建并打开文件。至少这就是我的LG Aristo SDK 23上发生的事情。我看到的唯一可行的方法是SAF。
MarkJoel60年

isExternalStorageRemovable仅适用于> = 21,是否可以确定<= 19 API设备上的存储是否可移动(microsd卡),以某种方式实现类似方法?
user924

3

除了所有其他不错的答案之外,我可以在这个问题上添加更多内容,以便为读者提供更广泛的覆盖范围。在这里的答案中,我将使用2个可数的资源来表示“外部存储”。

第一个资源来自《Android编程》,《大书呆子牧场指南》第二版,第16章,第294页。

本书介绍了基本的和外部的文件和目录方法。我将尝试就与您的问题有关的内容做一份简历。

本书的以下部分:

外部存储

您的照片需要的不仅仅是屏幕上的位置。完整尺寸的图片太大,无法粘贴在SQLite数据库中,更不用说了Intent。他们将需要一个放置在设备文件系统上的位置。通常,您会将它们放在私有存储中。回想一下,您曾使用私有存储来保存SQLite数据库。使用Context.getFileStreamPath(String)Context.getFilesDir()这样的方法,您也可以对常规文件执行相同的操作(这些文件将位于SQLite数据库所在的数据库子文件夹附近的子文件夹中)

中的基本文件和目录方法 上下文中的

| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File getFilesDir()                                                                      |
| - Returns a handle to the directory for private application files.                    |
|                                                                                       |
|FileInputStream openFileInput(String name)                                             |
| - Opens an existing file for input (relative to the files directory).                 |
|                                                                                       |
|FileOutputStream openFileOutput(String name, int mode)                                 |
| - Opens a file for output, possibly creating it (relative to the files directory).    |
|                                                                                       |
|File getDir(String name, int mode)                                                     |
| - Gets (and possibly creates) a subdirectory within the files directory.              |
|                                                                                       |
|String[] fileList()                                                                    |
| - Gets a list of file names in the main files directory, such as for use with         |
|   openFileInput(String).                                                              |
|                                                                                       |
|File getCacheDir()                                                                     |
| - Returns a handle to a directory you can use specifically for storing cache files.   |
|   You should take care to keep this directory tidy and use as little space as possible|

如果要存储仅当前应用程序需要使用的文件,那么这些方法正是您所需要的。

另一方面,如果您需要另一个应用程序来写入这些文件,那么您很不走运:尽管有一个Context.MODE_WORLD_READABLE可以传递给的标志openFileOutput(String, int),但它已被弃用,并且对新设备的影响并不完全可靠。如果要存储文件以与其他应用程序共享或从其他应用程序接收文件(如存储的图片之类的文件),则需要将其存储在外部存储中。

外部存储有两种:主存储和其他所有存储。所有Android设备都有至少一个外部存储位置:主要位置,该位置位于由以下位置返回的文件夹中 Environment.getExternalStorageDirectory()。这可能是SD卡,但如今它通常更集成到设备本身中。某些设备可能具有附加的外部存储。那将属于“其他所有”。

上下文也提供了很多获取外部存储的方法。这些方法提供了获取主要外部存储的简便方法,以及获得其他所有内容的简便方法。所有这些方法也将文件存储在公共场所,因此请谨慎使用。

上下文中的外部文件和目录方法

| Method                                                                                |
| --------------------------------------------------------------------------------------|
|File getExternalCacheDir()                                                             |
| - Returns a handle to a cache folder in primary external storage. Treat it like you do|
|   getCacheDir(), except a little more carefully. Android is even less likely to clean |
|   up this folder than the private storage one.                                        |
|                                                                                       |
|File[] getExternalCacheDirs()                                                          |
| - Returns cache folders for multiple external storage locations.                      |
|                                                                                       |
|File getExternalFilesDir(String)                                                       |
| - Returns a handle to a folder on primary external storage in which to store regular  |
|   files. If you pass in a type String, you can access a specific subfolder dedicated  |
|   to a particular type of content. Type constants are defined in Environment, where   |
|   they are prefixed with DIRECTORY_.                                                  |
|   For example, pictures go in Environment.DIRECTORY_PICTURES.                         |
|                                                                                       |
|File[] getExternalFilesDirs(String)                                                    |
| - Same as getExternalFilesDir(String), but returns all possible file folders for the  |
|   given type.                                                                         |
|                                                                                       |
|File[] getExternalMediaDirs()                                                          |
| - Returns handles to all the external folders Android makes available for storing     |
|   media – pictures, movies, and music. What makes this different from calling         |
|   getExternalFilesDir(Environment.DIRECTORY_PICTURES) is that the media scanner       |
|   automatically scans this folder. The media scanner makes files available to         |
|   applications that play music, or browse movies and photos, so anything that you     |
|   put in a folder returned by getExternalMediaDirs() will automatically appear in     |
|   those apps.                                                                         |

从技术上讲,上面提供的外部文件夹可能不可用,因为某些设备将可移动SD卡用于外部存储。实际上,这几乎不是问题,因为几乎所有现代设备都将不可移动的内部存储用于其“外部”存储。因此,不值得花太多精力去解释它。但是我们建议您添加简单的代码来防止这种情况的发生,您稍后将进行处理。

外部存储权限

通常,您需要具有读写外部存储的权限。权限是您使用清单文件放入清单中的众所周知的字符串值<uses-permission>标签。他们告诉Android您想做一些Android要您请求许可的事情。

在这里,Android希望您征求许可,因为它想加强一些责任感。您告诉Android您需要访问外部存储,然后Android会告诉用户这是您的应用程序尝试安装时要做的事情之一。这样,当您开始将内容保存到他们的SD卡时,没有人会感到惊讶。

在Android 4.4(KitKat)中,他们放宽了此限制。以来Context.getExternalFilesDir(String)返回的是您应用程序专用的文件夹,因此您希望能够读取和写入其中的文件是很有意义的。因此,在Android 4.4(API 19)及更高版本上,您不需要此文件夹的此权限。(但是您仍然需要其他外部存储。)

在清单中添加一行,以请求读取外部存储的权限,但最多不超过API清单16.5的请求外部存储权限(AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.bignerdranch.android.criminalintent" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="18" />

使用maxSdkVersion属性可以使您的应用仅在比API 19 Android KitKat早的Android版本上请求此权限。请注意,您仅要求读取外部存储。也有一个WRITE_EXTERNAL_STORAGE许可,但是您不需要它。您不会在外部存储中写入任何内容:相机应用程序将为您完成此操作

第二个资源是此链接,请全部阅读,但您也可以跳至“使用外部存储”部分。

参考:

更多阅读资料:

免责声明:该信息取自《 Android编程:大书呆子牧场指南》,经作者许可。有关这本书的更多信息或购买副本,请访问bignerdranch.com。


0

这个主题有点老,但是我一直在寻找解决方案,经过一番研究,我带着下面的代码来检索可用的“外部”安装点列表,据我所知,这些安装点可在许多不同的设备上运行。

基本上,它读取可用的安装点,过滤掉无效的安装点,测试其余的安装点(如果可访问的话),并在满足所有条件的情况下添加它们。

当然,在调用代码之前必须授予必需的权限。

// Notice: FileSystemDevice is just my own wrapper class. Feel free to replace it with your own. 

private List<FileSystemDevice> getDevices() {

    List<FileSystemDevice> devices = new ArrayList<>();

    // Add default external storage if available.
    File sdCardFromSystem = null;
    switch(Environment.getExternalStorageState()) {
        case Environment.MEDIA_MOUNTED:
        case Environment.MEDIA_MOUNTED_READ_ONLY:
        case Environment.MEDIA_SHARED:
            sdCardFromSystem = Environment.getExternalStorageDirectory();
            break;
    }

    if (sdCardFromSystem != null) {
        devices.add(new FileSystemDevice(sdCardFromSystem));
    }

    // Read /proc/mounts and add all mount points that are available
    // and are not "special". Also, check if the default external storage
    // is not contained inside the mount point. 
    try {
        FileInputStream fs = new FileInputStream("/proc/mounts");
        String mounts = IOUtils.toString(fs, "UTF-8");
        for(String line : mounts.split("\n")) {
            String[] parts = line.split(" ");

            // parts[0] - mount type
            // parts[1] - mount point
            if (parts.length > 1) {
                try {

                    // Skip "special" mount points and mount points that can be accessed
                    // directly by Android's functions. 
                    if (parts[0].equals("proc")) { continue; }
                    if (parts[0].equals("rootfs")) { continue; }
                    if (parts[0].equals("devpts")) { continue; }
                    if (parts[0].equals("none")) { continue; }
                    if (parts[0].equals("sysfs")) { continue; }
                    if (parts[0].equals("selinuxfs")) { continue; }
                    if (parts[0].equals("debugfs")) { continue; }
                    if (parts[0].equals("tmpfs")) { continue; }
                    if (parts[1].equals(Environment.getRootDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getDataDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { continue; }

                    // Verify that the mount point is accessible by listing its content. 
                    File file = new File(parts[1]);
                    if (file.listFiles() != null) {
                        try {

                            // Get canonical path for case it's just symlink to another mount point.
                            String devPath = file.getCanonicalPath();

                            for(FileSystemDevice device : devices) {

                                if (!devices.contains(devPath)) {                        
                                    devices.add(new FileSystemDevice(new File(devPath)));
                                }

                            }
                        } catch (Exception e) {
                            // Silently skip the exception as it can only occur if the mount point is not valid. 
                            e.printStackTrace();
                        }
                    }
                } catch (Exception e) {
                    // Silently skip the exception as it can only occur if the mount point is not valid. 
                    e.printStackTrace();
                }
            }
        }

        fs.close();
    } catch (FileNotFoundException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable. 
        // Possibly, another detection method can be called here.
        e.printStackTrace();
    } catch (IOException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable.
        // Possibly, another detection method can be called here.
        e.printStackTrace();            
    }

    return devices;
}

-1

这是在外部存储设备(如果设备中存在SDCard或设备外部存储设备中的SDCard)中创建新文件的方法。只需将“ foldername”替换为所需目标文件夹的名称,并将“ filename”替换为要保存的文件的名称。当然,您可以在此处看到如何保存通用文件,现在可以在此处或文件中搜索如何保存图像。

try {
            File dir =  new File(Environment.getExternalStorageDirectory() + "/foldername/");
            if (!dir.exists()){
                dir.mkdirs();
            }
            File sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileName );
            int num = 1;
            String fileNameAux = fileName;
            while (sdCardFile.exists()){
                fileNameAux = fileName+"_"+num;
                sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileNameAux);
                num++;
            }

这也可以控制该文件的存在,并在新文件名的末尾添加一个数字以保存它。

希望能帮助到你!

编辑:对不起,我忘记了您必须<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />在清单中要求(或者以编程方式,如果您喜欢棉花糖)


-3

对于棉花糖以下的版本,您可以直接在清单中提供权限。

但是对于具有棉花糖及更高版本的设备,您需要授予运行时权限。

通过使用

Environment.getExternalStorageDirectory();

您可以直接访问外部SD卡(安装在其中)希望对您有所帮助。


Environment.getExternalStorageDirectory();将为您提供设备内部SD卡的路径。
Vektor88

现在,通过Android Lollipop,您有两个选择,可以将外部SD卡用作内部存储或像典型的sd卡一样,这也取决于该选择。
Geet Choubey


2
这段代码为我提供了安装外部SD卡的路径。然后怎样呢?我仍然不能写。
Vektor88

尝试写入外部SD卡时遇到什么错误?
Geet Choubey
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.