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

在我的应用程序中,我需要在设备存储上存储大量的图像。 这样的图像倾向于实现设备存储,我想让用户能够select外部SD卡作为目标文件夹。

我到处都看到,Android不允许用户写外部SD卡,SD卡是指外部和可挂载的SD卡,而不是外部存储器 ,但是文件pipe理器应用程序设法在所有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和盾牌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 

1. 总结

由于持续变化 ,Android上的外部SD卡没有通用的写法 :

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

  • KitKat:引入了可让应用程序访问SD卡上应用程序特定目录中的文件的API。

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

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

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

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

对于Kitkat来说,没有生根的“完整解决scheme”的机会几乎为零:Android项目已经在这里搞砸了。 没有应用程序可以完全访问外部SD卡:

  • 文件pipe理器:你不能使用它们来pipe理你的外部SD卡。 在大部分地区,他们只能阅读,而不能写作。
  • 媒体应用程序:您无法再重新组织您的媒体collections,因为这些应用程序无法写入。
  • 办公应用程序:几乎相同

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

唯一的解决办法就是要求制造商(其中一些公司,例如华为公司为他们提供了P6的Kitkat更新),或者是根源… (Izzy的解释继续)

ContextCompat.getExternalFilesDirs解决了这个问题,如果你不需要访问其他文件夹 。


2. 关于通用解决scheme。

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

这个事实是通过设备的外部存储configuration的这些例子来展示的 。

访问外部存储受到各种Android权限的保护。 从Android 1.0开始,写访问受到WRITE_EXTERNAL_STORAGE权限的保护。 从Android 4.1开始,读访问受到READ_EXTERNAL_STORAGE权限的保护。

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

Android 6.0引入了一个新的运行时权限模型,应用程序在运行时需要能力。 由于新模型包含READ/WRITE_EXTERNAL_STORAGE权限,因此平台需要dynamic授予存储访问权限,而不会杀死或重新启动已运行的应用程序。 它通过维护所有安装的存储设备的三个不同视图来实现这一点:

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

3. 关于你的更新1。

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

改进启发式,以确定什么代表可移动媒体基于不同的api水平,如android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

请记住, Android 6.0支持便携式存储设备 ,第三方应用程序必须通过存储访问框架 。 您的设备HTC10和Shield K1可能是API 23。

您的日志显示访问/mnt/media_rw 的权限被拒绝exception ,如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替代了getStorageDirectories()方法,这是一个很好的起点。

ContextCompat.getExternalFilesDirs解决了这个问题,如果你不需要访问其他文件夹 。


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

将下一个代码添加到您的AndroidManifest.xml并阅读 获取对外部存储的访问权限

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

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

如果您同时需要…,您只需要请求WRITE_EXTERNAL_STORAGE权限。

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

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

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

如果API级别为23+,请 在运行时请求权限, 在运行时 读取 请求权限

从Android 6.0(API级别23)开始,用户在应用程序运行时为应用程序授予权限,而不是在安装应用程序时进行授权…或者更新应用程序…用户可以撤消权限。

 // Assume thisActivity is the current activity int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 

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

请阅读Mark Murphy的解释,并推荐 Dianne Hackborn和Dave Smith

  • 在Android 4.4之前,没有官方支持Android的可移动媒体,从KitKat开始,FMW API中出现了“主要”和“次要”外部存储的概念。
  • 以前的应用程序只是依靠MediaStore索引,附带硬件或检查挂载点,并应用一些启发式来确定代表可移动媒体。
  • 自从Android 4.2以来,Google就要求设备制造商locking可移动媒体以提高安全性(多用户支持),并在4.4中添加了新的testing。
  • 由于已添加KiKat getExternalFilesDirs()和其他方法以返回所有可用存储卷上的可用path(返回的第一个项目是主卷)。
  • 下表列出了开发者可能会尝试做什么以及KitKat如何回应: 在这里输入图像说明

在KitKat之前尝试使用Doomsknight方法1,或者阅读Gnathonic或gist的 这个回应 :

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

也请阅读Paolo Rovelli的解释,并尝试从KitKat开始使用Jeff Sharkey的解决scheme :

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

新的Context.getExternalFilesDirs()Context.getExternalCacheDirs()方法可以返回多个path,包括主设备和辅助设备。

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

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


6.棒棒糖还介绍了更改和DocumentFile助手类。

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

这里有一个与KitKat中的存储访问框架交互的很好的教程 。

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


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

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

有关更多信息,请参阅Scoped Directory Access培训 。


8. Android O更改。

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

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

对于定位Android O的应用 ,此行为已得到更正。 该应用程序只被授予它明确要求的权限。 但是,一旦用户授予应用程序的权限,该权限组中的所有后续权限请求都将自动授予。

例如, READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE


9.相关问题和build议的答案。

如何获得Android 4.0以上的外部SD卡path?

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

getExternalFilesDir和getExternalStorageDirectory()之间的差异

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

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

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

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

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


10. 相关的错误和问题。

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

写入由棒棒糖上的getExternalCacheDir()返回的目录没有写入权限就会失败

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

方法1 🙁由于权限的变化, 不能在6.0及更高版本上运行)

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

它会将所有已安装的媒体(包括Real SD卡)返回到string目录位置列表中。 随着清单,你可以问用户在哪里保存等

你可以用下面的方法来调用它:

  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); 

不过,使用地点不同。

返回应用程序可以放置拥有的持久性文件的所有外部存储设备上特定于应用程序的目录的绝对path。 这些文件是应用程序的内部文件,对于用户而言通常不可见为媒体。

这里返回的外部存储设备被视为设备的永久部分,包括仿真的外部存储和物理介质插槽,如电池舱中的SD卡。 返回的path不包括瞬时设备,如USB闪存驱动器。

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

关于ContextCompat的更多信息

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

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

这是最初张贴在这里( 有没有办法让SD卡的大小在Android? )由我得到的外部SD卡的大小在Android 5.0 +

要将外部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并让循环继续,而不是在find第一个循环后调用break

关于所有好的答案,我填写,我可以多加一点这个问题,这样可以给读者更广泛的覆盖面。 在我的答案中,我将使用2个可数资源来呈现外部存储。

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

本书描述了基本的和外部的文件和目录方法。 我会尽量写一份与您的问题相关的简历。

本书的以下部分:

外置储存

你的照片需要的不仅仅是屏幕上的一个地方。 全尺寸的图片太大,不能粘在SQLite数据库中,更不用说Intent 。 他们将需要一个地方居住在您的设备的文件系统。 通常情况下,你会把它们放在你的私人存储空间。 回想一下你使用你的私有存储保存你的SQLite数据库。 像Context.getFileStreamPath(String)Context.getFilesDir() ,你也可以对普通文件做同样的事情(它将存在于你的SQLite数据库所在的数据库子文件夹的子文件夹中)

上下文中的基本文件和目录方法

 | Method | |---------------------------------------------------------------------------------------| |File etFilesDir() | | - 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设备至less有一个外部存储位置:主要位置,位于Environment.getExternalStorageDirectory()返回的文件夹中。 这可能是一个SD卡,但现在它更常被集成到设备本身。 有些设备可能有额外的外部存储。 那将落在“其他一切”之下。

上下文提供了很多获取外部存储的方法。 这些方法提供了简单的方法来获得主要的外部存储空间,并且可以轻松地完成其他任何事情。 所有这些方法都将文件存储在公共可用的位置,所以要小心。

上下文中的外部文件和目录方法

 | 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卡作为外部存储。 在实践中,这几乎不是问题,因为几乎所有现代设备都具有用于其“外部”存储的不可移动的内部存储。 所以说这是不值得的。 但是,我们build议使用简单的代码来防范可能性,这一点你可以马上做。

外部存储权限

一般来说,您需要获得外部存储的写入或读取权限。 权限是使用<uses-permission>标记放入清单中的众所周知的string值。 他们告诉Android,你想做一些Android想要你允许的东西。

在这里,Android希望你要求权限,因为它要强制执行一些问责制。 您告诉Android您需要访问外部存储,然后Android会告诉用户这是您的应用程序在尝试安装时所做的一件事情。 这样,当你开始把东西存入SD卡时,没有人会感到惊讶。

在Android 4.4,KitKat中,他们放松了这个限制。 由于Context.getExternalFilesDir(String)返回特定于您的应用程序的文件夹,所以您希望能够读取和写入其中存在的文件。 所以在Android 4.4(API 19)以上,你不需要这个文件夹的权限。 (但你仍然需要它来用于其他types的外部存储。)

清单中添加一行,用于请求读取外部存储的权限,但仅限于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存储选项: https : //developer.android.com/guide/topics/data/data-storage.html
  • 图书网站: https : //www.bignerdranch.com/we-write/android-programming

更多的阅读材料:

免责声明:本信息取自作者许可下的Android Programming:The Big Nerd Ranch Guide。 有关本书的更多信息或购买副本,请访问bignerdranch.com。

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

基本上,它读取可用的挂载点,过滤出无效的挂载点,如果可以访问,则testing其余部分,如果所有条件都满足,则添加它们。

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

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

Here is a way of creating a new file in the External Storage (SDCard if present in the device or device External Storage if not). Just replace "foldername" with the name of your desired destination folder and "filename" with the name of the file you are saving. Of course here you can see how to save a generic File, now you can search for how to save images maybe here or whatever in a file.

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

This also controls that file exists and adds a number in the end of the name of the new file to save it.

希望能帮助到你!

EDIT: Sorry, i forgot you have to ask for <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> in your manifest (or programatically if you prefer from Marshmallow)

For versions below Marshmallow you can directly give the permissions in the manifest.

But for devices with Marshmallow and above you need to grant the permissions on run time.

通过使用

 Environment.getExternalStorageDirectory(); 

you can directly access the External SD card (Mounted one) Hope this helps.