繁体   English   中英

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

[英]Universal way to write to external SD card on Android

在我的应用程序中,我需要在设备存储中存储大量图像。 此类文件往往用于设备存储,我希望允许用户能够选择外部 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

总结

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

由于 KitKat,如果您使用特定于应用程序的目录,则不需要权限, 否则需要权限。

通用方式:

历史说没有通用的方式来写入外部 SD 卡, 但继续......

这些设备的外部存储配置示例证明了这一事实

基于API的方式:

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

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

推荐方式:

ContextCompat.getExternalFilesDirs解决不需要共享文件时的访问错误

共享它的安全方式是使用内容提供者新的存储访问框架

隐私意识方式:

从 Android Q Beta 4 开始,默认情况下,面向 Android 9(API 级别 28)或更低版本的应用没有任何变化。

默认情况下(或选择加入)以 Android Q 为目标的应用程序会获得外部存储的过滤视图


  1. 初步答复。

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

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

  • Pre-KitKat:官方Android平台除例外情况外根本不支持SD卡。

  • KitKat:引入了 API,允许应用访问 SD 卡上应用特定目录中的文件。

  • Lollipop:添加 API 以允许应用请求访问其他提供商拥有的文件夹。

  • Nougat:提供了一个简化的 API 来访问常见的外部存储目录。

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

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

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


  1. 更新了答案。

更新 1 我尝试了 Doomknight 答案中的方法 1,但无济于事:

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

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

改进启发式方法,根据不同的 api 级别确定什么代表可移动媒体,例如android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

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

请记住, Android 6.0 支持便携式存储设备,第三方应用程序必须通过存储访问框架 您的设备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可以解决此问题


  1. 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 索引、随硬件一起提供或检查挂载点并应用一些启发式方法来确定什么代表可移动媒体。

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

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

  • 自 Android 4.2 以来,Google 一直要求设备制造商锁定可移动媒体以确保安全(多用户支持),并且在 4.4 中添加了新的测试。
  • 由于添加了 KitKat getExternalFilesDirs()和其他方法来返回所有可用存储卷上的可用路径(返回的第一项是主卷)。
  • 下表说明了开发人员可能会尝试做什么以及 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()以确定存储文件的最佳位置。

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

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

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

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

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

唯一允许 3 rd方应用程序在您的外部卡上写入的地方是“他们自己的目录”(即/sdcard/Android/data/<package_name_of_the_app> )。

真正解决这个问题的唯一方法需要制造商(其中一些人修复它,例如华为通过他们的 P6 Kitkat 更新) – 或 root ...... (Izzy 的解释在这里继续)


  1. Android 5.0 引入了更改和DocumentFile助手类。

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

这是与 KitKat 中的存储访问框架交互的很棒的教程

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


  1. 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 的应用程序

  1. Android 7.0 提供了一个简化的 API 来访问外部存储目录。

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

有关详细信息,请参阅范围目录访问培训

阅读 Mark Murphy 的帖子: 小心 Scoped Directory Access 它在 Android Q 中已被弃用

请注意,7.0 中添加的范围目录访问在 Android Q 中已弃用。

具体来说,不推荐使用StorageVolume上的createAccessIntent()方法。

他们添加了一个createOpenDocumentTreeIntent()可以用作替代。


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

从 Android O 开始,存储访问框架允许自定义文档提供程序为驻留在远程数据源中的文件创建可查找的文件描述符...

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

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

例如, READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE ...

更新: Android Q 早期测试版暂时用更细粒度的特定媒体权限替换了READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限。

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

注意:在早期 Beta 版本中引入的特定于媒体收藏的权限 - READ_MEDIA_IMAGESREAD_MEDIA_AUDIOREAD_MEDIA_VIDEO -现在已过时 更多信息:

Mark Murphy 的 Q Beta 4(最终 API)评论: 外部存储的死亡:传奇的终结(?)

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


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

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

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

getExternalFilesDir 和 getExternalStorageDirectory() 之间的差异

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

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

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

使用 SAF(存储访问框架)的 Android SD 卡写入权限

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


  1. 相关的错误和问题。

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

在没有写入权限的情况下,写入 Lollipop 上的 getExternalCacheDir() 返回的目录失败

我相信有两种方法可以实现这一目标:

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

我多年来一直在许多设备版本上使用这种方法,没有任何问题。 归功于原始来源,因为它不是我写的。

它将在字符串目录位置列表中返回所有已安装的媒体(包括真正的 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;
}

方法二:

使用 v4 支持库

import android.support.v4.content.ContextCompat;

只需调用以下命令即可获取存储File位置列表。

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

但是,这些位置的用法不同。

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

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

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

有关 ContextCompat 的更多信息

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

只是另一个答案。 此答案仅显示 5.0+,因为我相信此处发布的 Doomknight 的答案是适用于 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

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

第一个资源来自Android Programming,The Big Nerd Ranch Guide 2nd edition ,第 16 章,第 294 页。

本书介绍了基本的和外部的文件和目录方法。 我将尝试制作一份可能与您的问题相关的简历。

书中的以下部分:

外部存储

您的照片不仅需要在屏幕上占据一席之地。 全尺寸图片太大而无法保存在 SQLite 数据库中,更不用说Intent 他们需要在你设备的文件系统上有一个地方。 通常,您会将它们放在您的私人存储中。 回想一下,您使用私有存储来保存 SQLite 数据库。 使用Context.getFileStreamPath(String)Context.getFilesDir() ,您也可以对常规文件执行相同的操作(这些文件将位于与 SQLite 数据库所在的数据库子文件夹相邻的子文件夹中)

Context 中的基本文件和目录方法

| 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 卡,但现在它更常见地集成到设备本身中。 某些设备可能有额外的外部存储。 那将属于“其他一切”。

Context 也提供了很多获取外部存储的方法。 这些方法提供了获取主要外部存储的简单方法,以及获取其他所有内容的有点简单的方法。 所有这些方法也将文件存储在公开可用的位置,因此请小心使用它们。

Context 中的外部文件和目录方法

| 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。

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

基本上,它读取可用的挂载点,过滤掉无效的挂载点,测试其余的挂载点是否可访问,并在满足所有条件时添加它们。

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

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

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

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" /> (或者如果你喜欢 Marshmallow 以编程方式)

对于低于 Marshmallow 的版本,您可以直接在清单中授予权限。

但是对于带有 Marshmallow 及以上版本的设备,您需要在运行时授予权限。

通过使用

Environment.getExternalStorageDirectory();

您可以直接访问外部 SD 卡(已安装)希望这会有所帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM