在我的应用程序中,我需要在设备存储中存储大量图像 . 这些文件往往满足设备存储,我想让用户能够选择外部SD卡作为目标文件夹 .
我到处都读到Android不允许用户写入外部SD卡,SD卡我的意思是外部和可安装的SD卡和 not the external storage ,但文件管理器应用程序设法在所有Android版本上写入外部SD .
在不同的API级别(Pre-KitKat,KitKat,Lollipop)上授予对外部SD卡的读/写访问权限的更好方法是什么?
Update 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
8 回答
简答 .
当您不需要共享文件时,ContextCompat.getExternalFilesDirs解决the access error .
共享它的安全方式是使用content provider或new Storage Access Framework .
摘要 .
由于continuous changes,没有universal way到write to external SD card on Android:
Pre-KitKat:官方Android平台除了例外情况外根本不支持SD卡 .
KitKat:引入了API,允许应用访问SD卡上特定于应用程序的目录中的文件 .
Lollipop:添加了API,允许应用请求访问其他提供商拥有的文件夹 .
Nougat:提供了一个简化的API来访问公共外部存储目录 .
基于Doomsknight's answer和mine,Dave Smith和Mark Murphy blog posts: 1,2,3:
理想情况下,使用Storage Access Framework和DocumentFile作为Jared Rummler pointed . 要么:
使用your app specific path
/storage/extSdCard/Android/data/com.myapp.example/files
.为pre-KitKat添加read/write permission to manifest,为此路径添加no permission required later .
尝试使用您的App路径和Doomsknight's methods考虑KitKat and Samsung case .
在KitKat之前过滤并使用getStorageDirectories,您的应用程序路径和读/写权限 .
自KitKat以来
ContextCompat.getExternalFilesDirs考虑devices that return internal first.
1.权限 .
您可以different api levels(API23+ at run time)上的外部SD卡 .
如果您使用特定于应用程序的目录,则Since KitKat,权限为not necessary,否则为required:
2.关于通用解决方案 .
The history says that there is not universal way to write to external SD card but continues...
This fact is demostrated by these examples of external storage configurations for devices.
3.关于您的更新1 .
我将使用您更新的问题的特定于应用程序的目录to avoid the issue和使用getExternalFilesDir documentation作为参考的
ContextCompat.getExternalFilesDirs()
.改进heuristics以根据不同的api级别确定代表可移动媒体的内容,例如
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT
请记住,Android 6.0 supports portable storage devices和第三方应用必须通过Storage Access Framework . 您的设备HTC10和Shield K1可能是API 23 .
您的日志显示a permission denied exception访问
/mnt/media_rw
,如_1967956_ for API 19:我从来没有尝试过,所以我不能共享代码,但我会避免
for
尝试写入所有返回的目录并寻找the best available storage directory to write into based on remaining space .也许Gizm0's alternative对你的
getStorageDirectories()
方法来说,这是一个很好的起点 .ContextCompat.getExternalFilesDirs
如果您不需要access to other folders,则解决the issue .4.清单(Api <23)和运行时(Api> = 23)中的请求权限 .
Add the next code to your AndroidManifest.xml and read Getting access to external storage
Ignore the next note due to bugs, but try to use ContextCompat.getExternalFilesDirs():
Request permissions at runtime if API level 23+ and read Requesting Permissions at Run Time
5.在KitKat之前尝试使用Doomsknight方法1,否则使用方法2 .
Read Mark Murphy's explanation and recommended Dianne Hackborn and Dave Smith ones
Prior to KitKat try to use Doomsknight method 1 or read this response by Gnathonic or gist:
Also read Paolo Rovelli's explanation and try to use Jeff Sharkey's solution since KitKat:
6. Lollipop还引入了更改和DocumentFile助手类 .
getStorageState
在API 19中添加,在API 21中弃用,use getExternalStorageState(File)7. Android 7.0提供了一个简化的API来访问外部存储目录 .
8. Android O更改 .
9.相关问题和推荐答案 .
How can I get external SD card path for Android 4.0+?
mkdir() works while inside internal flash storage, but not SD card?
Diff between getExternalFilesDir and getExternalStorageDirectory()
Why getExternalFilesDirs() doesn't work on some devices?
How to use the new SD card access API presented for Android 5.0 (Lollipop)
Writing to external SD card in Android 5.0 and above
Android SD Card Write Permission using SAF (Storage Access Framework)
SAFFAQ: The Storage Access Framework FAQ
10.相关的错误和问题 .
Bug: On Android 6, when using getExternalFilesDirs, it won't let you create new files in its results
Writing to directory returned by getExternalCacheDir() on Lollipop fails without write permission
我相信有两种方法可以达到这个目的:
METHOD 1: (由于权限更改, NOT 在6.0及以上版本上工作)
我已经在许多设备版本上使用这种方法多年没有问题 . 信用是由于原始来源,因为不是我写了它 .
它将在字符串目录位置列表中返回 all mounted media (including Real SD Cards) . 通过该列表,您可以询问用户保存位置等 .
你可以用以下方法调用它:
方法:
METHOD 2:
使用v4支持库
只需调用以下内容即可获取
File
存储位置列表 .但是,这些地点的用途不同 .
More Info on ContextCompat
它们就像应用程序特定的文件 . 隐藏在其他应用中 .
只是另一个答案 . 这个答案只显示5.0,因为我相信Doomknight在这里发布的答案是Android 4.4及以下版本的最佳方式 .
这个最初发布在这里(Is there a way to get SD Card size in Android?),以获得Android 5.0上的外部SD卡的大小
要将外部SD卡作为
File
:注意:我只获取"first"外部SD卡但是您可以修改它并返回
ArrayList<File>
而不是File
并让循环继续而不是在找到第一个之后调用break
.除了所有其他不错的答案之外,我还可以为这个问题添加更多内容,以便为读者提供更广泛的报道 . 在我的回答中,我将使用2个可数资源来呈现外部存储 .
第一个资源来自 Android Programming, The Big Nerd Ranch Guide 2nd edition ,第16章,第294页 .
本书介绍了基本和外部文件和目录方法 . 我将尝试制作一份与您的问题相关的简历 .
本书的以下部分:
External Storage
您的照片需要的不仅仅是屏幕上的位置 . 全尺寸图片太大而无法粘贴在SQLite数据库中,更不用说
Intent
了 . 他们需要一个可以存放在设备文件系统中的地方 . 通常,您会将它们放在私人存储中 . 回想一下,您使用私有存储来保存SQLite数据库 . 使用Context.getFileStreamPath(String)
和Context.getFilesDir()
之类的方法,您也可以使用常规文件执行相同的操作(它将存在于SQLite数据库所在的数据库子文件夹旁边的子文件夹中)Context 中的基本文件和目录方法
如果您要存储只有当前应用程序需要使用的文件,那么这些方法正是您所需要的 .
另一方面,如果您需要另一个应用程序来写入这些文件,那么您运气不好:虽然有一个
Context.MODE_WORLD_READABLE
标志可以传入openFileOutput(String, int)
,但它已被弃用,并且对新设备的影响并不完全可靠 . 如果要存储文件以与其他应用程序共享或从其他应用程序(存储的图片等文件)接收文件,则需要将它们存储在外部存储中 .外部存储有两种:主存储和其他所有存储 . 所有Android设备至少有一个外部存储位置:主要位置,位于
Environment.getExternalStorageDirectory()
返回的文件夹中 . 这个可能是SD卡,但现在它更常见地集成到设备本身 . 某些设备可能具有额外的外部存储 . 那将属于“其他一切” .Context也提供了很多获取外部存储的方法 . 这些方法提供了简单的方法来获取主要的外部存储,并且有点方便地获取其他所有内容 . 所有这些方法也将文件存储在公共场所,所以要小心它们 .
Context 中的外部文件和目录方法
从技术上讲,上面提供的外部文件夹可能不可用,因为某些设备使用可移动SD卡进行外部存储 . 实际上,这很少是一个问题,因为几乎所有现代设备都具有不可移动的内部存储空间用于“外部”存储 . 所以不值得花极大的代价来解释它 . 但是我们建议使用简单的代码来防范这种可能性,你马上就会做 .
External storage permission
通常,您需要具有从外部存储写入或读取的权限 . 权限是使用
<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
)maxSdkVersion属性使得您的应用只会在早于API 19的Android KitKat版本的Android上请求此权限 . 请注意,您只需要阅读外部存储空间 . 还有
WRITE_EXTERNAL_STORAGE
权限,但您不需要它 . 您不会向外部存储器写任何内容:相机应用程序将为您执行此操作第二个资源是link读取了所有内容,但您也可以跳转到 Using the External Storage 部分 .
Reference:
Android存储选项:https://developer.android.com/guide/topics/data/data-storage.html
图书网站:https://www.bignerdranch.com/we-write/android-programming
More reading stuff:
https://developer.android.com/reference/android/os/Environment.html#getExternalStoragePublicDirectory%28java.lang.String%29
https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_PICTURES
https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_MOVIES
How can I get external SD card path for Android 4.0+?
这个主题有点陈旧,但我正在寻找一个解决方案,经过一些研究后,我带了下面的代码来检索可用的“外部”挂载点列表,根据我的知识,它可以在许多不同的设备上运行 .
基本上,它读取可用的挂载点,过滤掉无效的挂载点,如果可访问则测试其余的挂载点,并在满足所有条件时添加它们 .
当然,必须在调用代码之前授予所需的权限 .
以下是在外部存储器中创建新文件的方法(如果存在于设备或设备外部存储器中,则为SD卡(如果不存在) . 只需将"foldername"替换为所需目标文件夹的名称,将"filename"替换为要保存的文件的名称 . 当然,在这里您可以看到如何保存通用文件,现在您可以搜索如何在文件中保存图像maybe here或其他任何内容 .
这也控制该文件存在,并在新文件名称的末尾添加一个数字以保存它 .
希望能帮助到你!
编辑:对不起,我忘了你必须在你的清单中要求
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
(或者如果您喜欢Marshmallow,请以编程方式提出)输出:
使用Note 3 Android 5.0 .
对于Marshmallow以下的版本,您可以直接在清单中提供权限 .
但对于具有Marshmallow及更高版本的设备,您需要在运行时授予权限 .
通过使用
你可以直接访问外部SD卡(已安装)希望这一点帮助 .