在我的应用程序中,我需要在设备存储中存储大量图像。此类文件往往会满足设备存储,我希望允许用户能够选择外部 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
原文由 Vektor88 发布,翻译遵循 CC BY-SA 4.0 许可协议
概括
您可以在 不同的 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 卡的 方法:
Pre-KitKat:Android 官方平台除例外情况外,完全不支持 SD 卡。
KitKat:引入了 API,让应用程序可以访问 SD 卡上应用程序特定目录中的文件。
Lollipop:添加了 API 以允许应用程序请求访问其他提供商拥有的文件夹。
Nougat:提供了一个简化的 API 来访问常见的外部存储目录。
… Android Q 隐私更改:应用范围和媒体范围的存储
基于 Doomsknight 的回答 和 我 的,以及 Dave Smith 和 Mark Murphy 的 博客文章: 1、2、3 :
/storage/extSdCard/Android/data/com.myapp.example/files
。我会使用特定于应用程序的目录 来避免更新问题的 问题,并且
ContextCompat.getExternalFilesDirs()
使用 getExternalFilesDir 文档 作为参考。改进 启发式方法,根据不同的 API 级别确定代表可移动媒体的内容,例如
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT
请记住, Android 6.0 支持便携式存储设备,第三方应用程序必须通过 存储访问框架。您的设备 HTC10 和 Shield K1 可能是 API 23。
您的日志显示 权限被拒绝的异常 访问
/mnt/media_rw
,例如 API 19+ 的 修复:我从来没有尝试过,所以我不能共享代码,但我会避免
for
尝试写入所有返回的目录,并 根据剩余空间寻找最佳可用存储目录写入。也许 Gizm0 替代 您的
getStorageDirectories()
方法是一个很好的起点。ContextCompat.getExternalFilesDirs
如果您不需要 访问其他文件夹, 则可以解决 此问题。在 KitKat 之前尝试使用 Doomsknight 方法 1 或阅读 Gnathonic 的 回复。
将下一个代码添加到您的
AndroidManifest.xml
并阅读 获取外部存储的访问权限阅读 Mark Murphy 的解释 和 推荐 的 Dianne Hackborn 和 Dave Smith 的帖子
由于错误请忽略下一条注释,但请尝试使用
ContextCompat.getExternalFilesDirs()
:另请阅读 Paolo Rovelli 的解释 并尝试使用 Jeff Sharkey 的自 KitKat 以来的解决方案:
使用 KitKat,您无需 root 即可获得“完整解决方案”的机会几乎为零:
getStorageState
在API 19中添加,在API 21中弃用, 使用getExternalStorageState(File)
如果 API 级别 23+,则在运行时请求 权限 并阅读 在运行时请求权限
阅读 Mark Murphy 的帖子: 谨慎使用范围目录访问。 它在 Android Q 中被弃用:
更新: Android Q 早期测试版暂时用更细粒度的媒体特定权限取代了
READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
权限。注意: 谷歌在 Beta 1 中引入了 角色,并在 Beta 2 之前从文档中删除了它们……
注意: 在早期测试版中引入的特定于媒体收藏的权限
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 (Lollipop) 提供的新 SD 卡访问 API
Android 5.0及以上系统写入外置SD卡
使用 SAF(存储访问框架)的 Android SD 卡写权限
SAFFAQ:存储访问框架常见问题解答
错误:在 Android 6 上,当使用 getExternalFilesDirs 时,它不会让您在其结果中创建新文件
在没有写入权限的情况下写入 Lollipop 上 getExternalCacheDir() 返回的目录失败