当前位置: 首页 > 知识库问答 >
问题:

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

包建义
2023-03-14

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

共有3个答案

谯德佑
2023-03-14

只是另一个答案。这个答案只显示5.0,因为我相信Doomknight在这里发布的答案是适用于Android 4.4及以下版本的最佳方式。

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

要将外部SD卡作为文件获取,请执行以下操作:

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

柯栋
2023-03-14

我认为有两种方法可以实现这一点:

方法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的更多信息

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

米丰
2023-03-14

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

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

通用方式:

历史告诉我们,没有通用的方式来写入外部SD卡,但仍在继续。。。

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

基于API的方式:

在KitKat之前,尝试使用末日骑士方法1,否则使用方法2。

请求清单中的权限(Api

推荐方式:

ContextCompat.get外部文件解决了不需要共享文件时的访问错误。

共享信息的安全方式是使用内容提供商或新的存储访问框架。

隐私感知方式:

从Android Q Beta 4开始,默认情况下,针对Android 9(API级别28)或更低版本的应用不会发生变化。

默认情况下,针对Android Q的应用程序(或选择使用Android Q的应用程序)会被提供到外部存储的过滤视图。

  1. 初始答案

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

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

>

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

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

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

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

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

根据Doomsknight的答案和我,以及Dave Smith和Mark Murphy的博客文章:1,2,3:

  • 理想情况下,使用Jared Rummler指出的Storage Access Framework和DocumentFile。或者:
  • 使用您的应用程序特定路径/store/extSdCard/Android/data/com.myapp.example/files
  • 为pre-KitKat添加读/写权限以清单,此路径稍后不需要权限。
  • 考虑到KitKat和Samsung案例,尝试使用您的App路径和Doomsknight的方法。
  • 在KitKat之前过滤并使用getStorageDirectory、您的应用程序路径和读/写权限。
  • ContextCompat.get自KitKat以来的外部文件。考虑先返回内部的设备。

更新1。我尝试了Doomknight的答案中的方法1,但没有成功:

正如您所看到的,在尝试在SD上写入之前,我正在运行时检查权限。。。

我会使用特定于应用程序的目录来避免您的更新问题和上下文兼容性问题。getExternalFilesDirs()使用getExternalFilesDir文档作为参考。

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

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

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

我从未尝试过,因此无法共享代码,但我会避免尝试在所有返回的目录上写入,并根据剩余空间查找可写入的最佳存储目录。

也许Gizm0是getStorageDirectory()方法的替代方案,这是一个很好的起点。

ContextCompat。如果您不需要访问其他文件夹,getExternalFilesDirs可以解决这个问题。

在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开始,写入访问受写入外部存储权限的保护。

从Android 4.1开始,读取访问受到外部存储权限的保护。

为了在外部存储器上写入文件,你的应用程序必须获取。。。系统权限:

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

如果你需要两者都。。。,您只需请求“写入外部存储”权限。

阅读马克·墨菲的解释并推荐黛安娜·哈克伯恩和戴夫·史密斯的帖子

  • 在Android 4.4之前,Android中没有对可移动媒体的官方支持,从KitKat开始,“主要”和“次要”外部存储的概念出现在FMW API中
  • 以前的应用程序仅仅依赖于MediaStore索引,随硬件提供或检查装入点,并应用一些启发式方法来确定什么是可移动媒体

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

  • 自Android 4.2以来,谷歌一直要求设备制造商锁定可移动媒体以确保安全(多用户支持),并在4.4中添加了新的测试
  • 因为添加了KitKat和其他方法来返回所有可用存储卷上的可用路径(返回的第一项是主卷)
  • 下表说明了开发人员可能会尝试做什么以及KitKat将如何响应:

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

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

还要阅读Paolo Rovelli的解释,并尝试使用Jeff Sharkey自KitKat以来的解决方案:

在KitKat中,现在有了一个用于与这些辅助共享存储设备交互的公共API。

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

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

这些方法也可以在support-v4库的ContextCompat上找到。

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

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

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

  • 文件管理器:您不能使用它们来管理外部SD卡。在大多数地区,他们只能读不能写

第三方应用程序只能在外部卡上写“他们自己的目录”(即SD卡/Android/数据/

真正修复的唯一方法需要制造商(其中一些制造商已修复,例如华为对P6进行了Kitkat更新)或根目录。。。(Izzy的解释在此继续)

API 19中添加了getStorageState,API 21中已弃用,请使用getExternalStorageState(文件)

以下是与KitKat中的存储访问框架交互的精彩教程。

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

如果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引入了一种新的运行时权限模型,应用程序可以在运行时需要时请求功能。由于新模型包含读/写外部存储权限,因此平台需要动态授予存储访问权限,而无需终止或重新启动已经运行的应用程序。它通过维护所有已装载存储设备的三个不同视图来实现此目的:

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

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

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

阅读马克·墨菲的帖子:小心使用作用域目录访问。它在Android Q中被弃用:

请注意,7.0中添加的作用域目录访问在Android Q中不受欢迎。

具体来说,StorageVolume上的createAccessIntent()方法不受欢迎。

他们添加了一个可作为替代方案的CreateOpenDocumentTreeContent()。

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

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

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

例如,READ\u EXTERNAL\u STORAGEWRITE\u EXTERNAL\u STORAGE。。。

更新:Android Q早期的测试版暂时用更细粒度的、特定于媒体的权限取代了读取外部存储和写入外部存储。

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

注意:早期beta版本中引入的特定于媒体集合的权限-READ_MEDIA_IMAGESREAD_MEDIA_AUDIOREAD_MEDIA_VIDEO-现在已经过时。更多信息:

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

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

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

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

getExternalFilesDir和getExternalStorageDirectory()之间的差异

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

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

在Android 5.0及以上版本中写入外部SD卡

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

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

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

 类似资料:
  • 我的设备是Android6.0.1操作系统,但无法将文件写入外部SD卡,从日志中,没有权限,但我有权申请成功,并在AndroidManifest中获得权限。xml文件; 我使用“adb shell dumpsys package packagename”命令,将读写权限返回到true;然后我看了一下设置- 我必须使用外部SD卡 有人遇到过这个问题吗我觉得这很奇怪 错误日志: 带系统。错误:java

  • 我无法在物理SD卡上写入文件。我想请求用户写入整个SD卡的权限。用户授予权限。现在我想在SD卡上的特定目录中创建文件(目录已经存在)。当我尝试打开输出流时,我得到异常: Java。Lang.SecurityException:权限拒绝:正在写入com . Android . external stage . external stage provider uri content://com . A

  • 本文向大家介绍Android 数据库SQLite 写入SD卡的方法,包括了Android 数据库SQLite 写入SD卡的方法的使用技巧和注意事项,需要的朋友参考一下 如果手机没有root,数据库文件是无法查看到的,不方便调试。 最好的办法是把数据库写进SD卡。 修改的地方有两处: 1.在你的helper类中把数据库文件名称 DATABASE_NAME 由原来的一个文件名,修改成路径的形式。 修改

  • 问题内容: 我正在使用以下代码从服务器下载文件,然后将其写入sd卡的根目录,一切正常: 但是,;表示文件将始终写入root /mnt/sdcard。是否可以指定某个文件夹来写入文件? 例如: 问题答案:

  • 问题内容: 我希望我的应用程序将应用程序数据库归档到SD卡。在我的代码中,我检查目录是否存在,如果不存在,则抛出。在此特定情况下,我尝试将db文件复制到SD卡上的根目录,但是会抛出。如何更改文件夹/文件的权限以能够写入? 问题答案: 没错,SD卡目录位于其中,但您不应该对其进行硬编码。而是调用以获取目录: 如果尚未执行此操作,则需要在清单中添加以下行,以授予您的应用正确的写入SD卡的权限:

  • 我的舱单中有权限条目: 编辑:我正在使用sdk 23