摘要
您可以在不同的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 作为目标的应用会得到一个筛选视图以访问外部存储。
- 初始答案。
在Android上写入外部SD卡的通用方法
由于不断的变化,没有通用的方式可以在Android上写入外部SD卡:
Pre-KitKat:除了一些例外,官方Android平台根本不支持SD卡。
KitKat:引入了API,允许应用程序访问SD卡上应用程序特定目录中的文件。
Lollipop:添加了API,允许应用程序请求访问其他提供者拥有的文件夹。
Nougat:提供了一个简化的API来访问常见的外部存储目录。
... Android Q隐私更改:应用程序范围和媒体范围存储
如何在不同的API级别上授予外部SD卡的读写访问权限
根据Doomsknight的答案和我的答案,以及Dave Smith和Mark Murphy的博客文章:1,2,3:
- 更新的答案。
更新1。我尝试了Doomknight答案中的方法1,但没有成功:
如你所见,在尝试写SD卡之前,我在运行时检查权限...
我建议使用应用程序特定目录避免问题,参考getExternalFilesDir文档使用ContextCompat.getExternalFilesDirs()
。
根据不同API级别(如android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT
),改进启发式算法以确定可移动媒体的表示方式。
引用:
...但我在两个不同的设备上尝试都出现访问错误:HTC10和Shield K1。
请记住 Android 6.0 支持可移动存储设备,第三方应用程序必须通过 Storage Access Framework 进行访问。您的设备 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>
我从未尝试过,因此无法分享代码,但我会避免使用
for
尝试在所有返回的目录上进行写入,并寻找
基于剩余空间寻找可用的最佳存储目录进行写入。
也许
Gizm0的替代方法可以作为你的
getStorageDirectories()
方法的一个很好的起点。
如果您不需要
访问其他文件夹,
ContextCompat.getExternalFilesDirs
可以解决
这个问题。
- 安卓 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();
}
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
中,并阅读
获取外部存储访问权限。
Access to external storage is protected by various Android
permissions.
Starting in Android 1.0, write access is protected with
the WRITE_EXTERNAL_STORAGE
permission.
Starting in Android 4.1, read
access is protected with the READ_EXTERNAL_STORAGE
permission.
In order to ... write files on the external storage, your app must
acquire ... system
permissions:
<manifest ...>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
If you need to both..., you need to request
only the WRITE_EXTERNAL_STORAGE
permission.
阅读Mark Murphy的解释以及推荐Dianne Hackborn和Dave Smith的文章
- 在Android 4.4之前,Android中没有官方支持可移动媒体的功能。从KitKat开始,在FMW API中引入了“主”和“次”外部存储的概念。
- 早期应用程序只是依赖于MediaStore索引、随硬件一起提供或检查挂载点,并应用一些启发式算法来确定什么代表可移动媒体。
- Android 4.4 KitKat引入了存储访问框架(SAF)。
由于存在错误,请忽略下一个提示,但请尝试使用ContextCompat.getExternalFilesDirs()
:
- 自Android 4.2以来,Google要求设备制造商为了安全性(多用户支持)锁定可移动媒体,并在4.4中添加了新测试。
- 自KitKat以来,添加了
getExternalFilesDirs()
和其他方法,以返回所有可用存储卷上可用的路径(返回的第一个项目是主卷)。
- 下表显示了开发人员可能尝试执行的操作以及KitKat的响应方式:
![enter image description here](https://istack.dev59.com/53p5d.webp)
Note: Beginning with Android 4.4, these permissions are not required
if you're reading or writing only files that are private to your app.
For more info..., see saving files that
are app-private.
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
</manifest>
另外阅读Paolo Rovelli的解释并尝试使用Jeff Sharkey的解决方案,自KitKat以来:
在KitKat中,现在有一个公共API来与这些次要共享存储设备进行交互。
新的Context.getExternalFilesDirs()
和Context.getExternalCacheDirs()
方法可以返回多个路径,包括主设备和次要设备。
然后,您可以遍历它们并检查Environment.getStorageState()
和File.getFreeSpace()
来确定存储文件的最佳位置。
这些方法也可在支持v4库中的ContextCompat
上使用。
从Android 4.4开始,外部存储设备上文件的所有者、组和模式现在是基于目录结构综合的。这使得应用程序可以管理它们在外部存储上的特定于包的目录,而无需持有广泛的“WRITE_EXTERNAL_STORAGE”权限。例如,名为“com.example.foo”的应用程序现在可以自由访问外部存储设备上的“Android/data/com.example.foo/”而不需要任何权限。这些综合权限是通过在FUSE守护程序中包装原始存储设备来实现的。
在KitKat版本中,如果没有进行root操作,你很难获得一个“完整的解决方案”。
安卓项目在这里明显出了问题。没有应用程序可以完全访问外部SD卡:
- 文件管理器:您不能使用它们来管理外部SD卡。在大多数区域,它们只能读取而不能写入。
- 媒体应用程序:您不能再重新标记/重新组织媒体集合,因为这些应用程序无法对其进行写入。
- 办公应用程序:基本上相同
第三方应用程序唯一被允许写入您的外部卡的地方是“它们自己的目录”(即/sdcard/Android/data/<package_name_of_the_app>
)。
真正修复它的唯一方法要么需要制造商(其中一些已经解决了,例如华为通过P6的Kitkat更新)- 或者 root... (Izzy的解释在此继续)
- Android 5.0引入了变化和 DocumentFile助手类。
getStorageState
在API 19中添加,已在API 21中弃用,
使用getExternalStorageState(File)
这是一个很棒的教程,可以在KitKat中与存储访问框架进行交互。
与Lollipop中的新API交互非常相似(Jeff Sharkey的解释)。
Android 6.0 Marshmallow引入了一种新的
运行时权限模型。
如果 API 级别为 23 或更高,请在运行时请求权限并阅读运行时请求权限。
Beginning in Android 6.0 (API level 23), users grant permissions to
apps while the app is running, not when they install the app ... or update the app ... user can revoke the permissions.
// 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的应用程序
Android 7.0提供了一个简化的API来访问外部存储目录。
作用域目录访问
在Android 7.0中,应用程序可以使用新的API请求访问特定的外部存储目录,包括可移动介质上的目录,如SD卡...
更多信息,请参见Scoped Directory Access training。
阅读Mark Murphy的文章:小心使用Scoped Directory Access。它在Android Q中已被弃用:
请注意,Android Q中已经弃用了7.0中添加的作用域目录访问功能。具体来说,
StorageVolume上的
createAccessIntent()
方法已被弃用。他们添加了一个
createOpenDocumentTreeIntent()
作为替代方案。
- Android 8.0 Oreo .. Android Q Beta 变化。
自 Android O 开始,存储访问框架允许自定义文档提供程序为驻留在远程数据源中的文件创建可寻址的文件描述符... 在 Android O 之前,如果应用在运行时请求权限并且被授予了该权限,则系统还会错误地授予应用属于同一权限组并在清单中注册的其余权限。对于针对 Android O 的应用程序,已经纠正了这种行为。只授予应用程序明确请求的权限。但是,一旦用户向应用授予权限,该权限组中所有后续权限请求都将自动授予。例如,READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE...
权限
更新: Android Q早期的beta版本暂时替换了
READ_EXTERNAL_STORAGE
和
WRITE_EXTERNAL_STORAGE
权限,使用更精细化、媒体相关的权限。
注意: Google在Beta 1中引入了
角色,但在Beta 2之前从文档中删除了它们...
注意: 早期beta版本引入的与媒体集合相关的权限——
READ_MEDIA_IMAGES
、
READ_MEDIA_AUDIO
和
READ_MEDIA_VIDEO
现在已经过时。
更多信息:
由Mark Murphy进行的Q Beta 4(最终API)审查:
外部存储的死亡:传奇的结束?
“死亡比生命更加普遍。每个人都会死亡,但不是每个人都活过。” ― 安德鲁·萨克斯
- 相关问题和推荐答案。
如何获取Android 4.0+的外部SD卡路径?
在内部闪存中,mkdir()可以正常工作,但在SD卡中不行?
getExternalFilesDir和getExternalStorageDirectory()之间的区别是什么?
为什么getExternalFilesDirs()在某些设备上不起作用?
如何使用Android 5.0(棒棒糖)提供的新SD卡访问API?
在Android 5.0及以上版本中写入外部SD卡的方法?
使用SAF(存储访问框架)获取Android SD卡写入权限
SAF常见问题解答:存储访问框架FAQ
- 相关的错误和问题。
在安卓6上使用getExternalFilesDirs时,结果中无法创建新文件
在Lollipop上写入getExternalCacheDir()返回的目录时,没有写入权限会失败