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

时间:2016-10-16 09:51:36

标签: java android android-sdcard android-external-storage

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

7 个答案:

答案 0 :(得分:98)

简短回答。

当您不需要共享文件时,

ContextCompat.getExternalFilesDirs会解决the access error

分享它的安全方式是使用content providernew Storage Access Framework

<强>概要

由于universal way而没有write to external SD card on Androidcontinuous changes

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

  • KitKat:推出了API,允许应用访问SD卡上特定应用目录中的文件。

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

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

  • ... Android Q privacy change: App-scoped and media-scoped storage

基于Doomsknight's answermineDave SmithMark Murphy blog posts: 123

1。 <强>权限。

您可grant read/write accessdifferent api levelsAPI23+ at run time到外部SD卡。

如果您使用特定于应用的目录,则{p> Since KitKat,权限为not necessary,否则为required

  

使用Kitkat,您有机会获得完整的解决方案&#34;没有生根   几乎为零:Android项目肯定搞砸了。   没有应用可以完全访问外部SD卡:

     
      
  • 文件管理员:您无法使用它们来管理外部SD卡。在   大多数地区,他们只能读但不能写。
  •   
  • 媒体应用:您不能   重新整理您的媒体收藏,就像那些应用一样   不能写信给他。
  •   
  • 办公室应用几乎相同
  •   
     

唯一的地方3 rd 派对应用程序可以写在你的   外部卡是他们自己的目录&#34; (即   /sdcard/Android/data/<package_name_of_the_app>)。

     

唯一的方法   确实需要制造商(其中一些是固定的)   它,例如华为将他们的Kitkat更新为P6) - 或root ... (Izzy's explanation continues here)

2。 关于通用解决方案。

history表示没有通用方式写入外部SD卡 but continues...

This factthese examples of external storage configurations for devices演示。

  

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

     

从Android 4.4开始,文件的所有者,组和模式   外部存储设备现在基于目录合成   结构体。这使应用程序可以管理其特定于程序包   外部存储上的目录,不需要他们持有广泛的   WRITE_EXTERNAL_STORAGE权限。例如,带包的应用程序   名称com.example.foo现在可以自由访问   外部存储设备上的Android/data/com.example.foo/没有   权限。这些合成的权限是通过   将原始存储设备包装在FUSE守护程序中。

     

Android 6.0在应用程序中引入了新的runtime permissions模型   在运行时需要时请求功能。因为新模式   包括READ/WRITE_EXTERNAL_STORAGE权限,平台   需要动态授予存储访问权限而不会被杀死或   重启已经运行的应用程序。它通过保持三个来做到这一点   所有已安装存储设备的不同视图:

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

更新:Android Q会将READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限替换为更细粒度的媒体特定权限。

它的测试版仍有待改变。 Mark Murphy's random musings可能是知道这个主题发生了什么以及贝塔之间发生了什么变化的最佳来源。

例如这篇精彩帖子:The Death of External Storage: What? And What Now?

Google在Beta 1上引入了roles,并在Beta 2之前将其从文档中删除......

  

&#34;死亡比生命更普遍。每个人都死了,但不是每个人都死   住&#34。 - Andrew Sachs

3。 关于您的更新1。

我会使用已更新问题的特定于应用程序的目录to avoid the issue,并使用getExternalFilesDir documentation作为参考{。}}。

根据ContextCompat.getExternalFilesDirs()

等不同的api级别改进heuristics以确定代表可移动媒体的内容

请注意,Android 6.0 supports portable storage devices和第三方应用必须通过Storage Access Framework。您的设备HTC10Shield K1可能是API 23。

您的日志显示a permission denied exception访问android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT,例如this fix访问API 19 +:

/mnt/media_rw

我从未尝试过,所以我无法共享代码,但我会避免<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> 尝试写入所有返回的目录并查找the best available storage directory to write into based on remaining space

for方法{}可能是Gizm0's alternative,这是一个很好的起点。

如果您不需要the issue

getStorageDirectories()会解决access to other folders

4。请求权限in manifest(Api&lt; 23)和at run time(Api&gt; = 23)。

将下一个代码添加到ContextCompat.getExternalFilesDirs并阅读 Getting access to external storage

  

为了...在外部存储上写文件,您的应用必须   获得...系统   权限:

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

  

注意:从Android 4.4开始,不需要这些权限   如果您只读取或写入应用程序专用的文件。   有关详情...,请参阅saving files that are app-private

ContextCompat.getExternalFilesDirs()

Request permissions在运行时,如果API级别为23+且已阅读 Requesting Permissions at Run Time

  

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

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

5. KitKat之前尝试使用Doomsknight method 1, method 2 otherwise.

阅读Mark Murphy's explanationrecommended Dianne Hackborn以及Dave Smith

  
      
  • 在Android 4.4之前,Android中没有对可移动媒体的官方支持,从KitKat开始,FMW API中出现了“主要”和“次要”外部存储的概念。
  •   
  • 之前的应用程序只依赖于MediaStore索引,附带硬件或检查挂载点并应用一些启发式方法来确定代表可移动媒体的内容。
  •   
  • 自Android 4.2以来,Google已要求设备制造商锁定可移动媒体以提高安全性(多用户支持),并在4.4中添加了新测试。
  •   
  • 由于添加了KiKat // Assume thisActivity is the current activity int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 和其他方法以在所有可用存储卷上返回可用路径(第一个   返回的项目是主卷。)
  •   
  • 下表列出了开发人员可能尝试做什么以及KitKat将如何回应:   enter image description here
  •   

在KitKat之前尝试使用Doomsknight method 1或通过Gnathonic或this response阅读gist

getExternalFilesDirs()

同时阅读Paolo Rovelli's explanation并尝试使用自KitKat后的Jeff Sharkey's solution

  

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

     

新的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; } 和   Context.getExternalFilesDirs()方法可以返回多个路径,   包括主设备和辅助设备。

     

然后你可以迭代   在他们身上并检查Context.getExternalCacheDirs()和   Environment.getStorageState()确定存储文件的最佳位置。

     

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

6。 Lollipop还引入了更改和DocumentFile帮助程序类。

File.getFreeSpace()在API 19中添加,在API 21中弃用, use getExternalStorageState(File)

  

Here's a great tutorial用于与Storage Access进行交互   KitKat中的框架。

     

与Lollipop is very similar (Jeff Sharkey's explanation)中的新API进行交互。

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

  

Scoped Directory Access   在Android 7.0中,应用可以使用新的API来请求访问特定的API   external storage个目录,包括可移动媒体上的目录   比如SD卡......

     

有关详细信息,请参阅Scoped Directory Access training

8。 Android O .. Q更改。

  

Starting in Android O ,.   存储访问框架允许custom documents providers   为驻留在远程中的文件创建可搜索的文件描述符   数据来源......

     

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

     

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

     

例如,getStorageStateREAD_EXTERNAL_STORAGE ...

     

但是,要让用户更好地控制他们的文件并限制文件   杂乱,Android Q改变了应用程序访问设备上文件的方式   外部存储。 Android Q取代WRITE_EXTERNAL_STORAGE和   READ_EXTERNAL_STORAGE权限更细粒度,   媒体特定的权限,以及访问自己的文件的应用程序   外部存储设备不需要特定权限。这些   更改会影响您的应用在外部保存和访问文件的方式   存储

     

Starting in Android Q,   您的应用不需要请求任何权限才能创建   并在...共享集合中修改自己的文件。如果你的应用程序   需要创建和修改其他应用程序创建的文件,   但是,它必须首先请求相应的许可:

     

访问其他应用&#39;照片&amp;中的文件视频共享集合   需要WRITE_EXTERNAL_STORAGEREAD_MEDIA_IMAGES权限,   取决于您的应用需要访问的文件类型。

     

访问其他应用&#39;音乐共享集合中的文件需要   READ_MEDIA_VIDEO权限。

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

答案 1 :(得分:9)

我相信有两种方法可以达到这个目的:

方法1:(由于权限更改, NOT 在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闪存驱动器。

     

应用程序可以在任何或所有返回的设备上存储数据。对于         例如,应用程序可以选择在设备上存储大文件         最可用的空间

More Info on ContextCompat

它们就像应用专用文件一样。隐藏在其他应用中。

答案 2 :(得分:3)

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

我最初在这里发布(Is there a way to get SD Card size in Android?)以获取Android 5.0上的外部SD卡大小

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

答案 3 :(得分:3)

除了所有其他好的答案之外,我还可以为这个问题添加更多内容,以便为读者提供更广泛的报道。在我的回答中,我将使用2个可数资源来呈现外部存储。

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

本书介绍了基本和外部文件和目录方法。我将尝试简要介绍与您的问题相关的内容。

本书的以下部分:

外部存储空间

您的照片需要的不仅仅是屏幕上的一个位置。全尺寸照片太大而无法插入 SQLite数据库,更不用说Intent了。他们需要一个可以存放在设备文件系统中的地方。 通常,您会将它们放在私人存储中。回想一下,您使用了私人存储 保存您的SQLite数据库。使用Context.getFileStreamPath(String)和。{ Context.getFilesDir(),您也可以使用常规文件执行相同的操作(它将存在于 与SQLite数据库所在的数据库子文件夹相邻的子文件夹

上下文

中的基本文件和目录方法
| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File getFilesDir()                                                                      |
| - 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设备都有 至少一个外部存储位置:主要位置,位于返回的文件夹中 Environment.getExternalStorageDirectory()。这可能是SD卡,但现在它更多 通常集成到设备本身。某些设备可能具有额外的外部存储。那 将属于“其他一切”。

Context也提供了很多获取外部存储的方法。这些方法提供了简单 如何获得主要的外部存储空间,以及实现其他目标的简单方法。所有的 这些方法也将文件存储在公共场所,所以要小心它们。

上下文

中的外部文件和目录方法
| 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卡进行外部存储。实际上,这很少是一个问题,因为几乎所有现代设备都具有不可移动的内部存储空间用于“外部”存储。所以不值得花极大的代价来解释它。但是我们建议使用简单的代码来防范这种可能性,你马上就会做。

外部存储空间权限

通常,您需要具有从外部存储写入或读取的权限。权限是您使用<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

<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属性使得您的应用仅在Android版本比Android 19的Android KitKat上请求此权限。 请注意,您只需要阅读外部存储空间。还有WRITE_EXTERNAL_STORAGE权限,但您不需要它。你不会在外部存储器上写任何东西:相机应用程序会为你做这件事

第二个资源是此link读取所有内容,但您也可以跳转到使用外部存储部分。

<强>参考:

更多阅读材料:

  

免责声明:此信息来自Android编程:The Big Nerd Ranch指南,获得作者许可。有关本书的更多信息或购买副本,请访问bignerdranch.com。

答案 4 :(得分:0)

这个主题有点陈旧,但我正在寻找一个解决方案,经过一些研究后,我带了下面的代码来检索可用的“外部”挂载点列表,根据我的知识,它可以在许多不同的设备上运行。

基本上,它读取可用的挂载点,过滤掉无效的挂载点,如果可访问则测试其余的挂载点,并在满足所有条件时添加它们。

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

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

答案 5 :(得分:-1)

以下是在外部存储中创建新文件的方法(SDCard如果存在于设备或设备外部存储器中,如果不存在)。只需将“foldername”替换为所需目标文件夹的名称,将“filename”替换为要保存的文件的名称。当然,您可以在这里看到如何保存通用文件,现在您可以搜索如何在文件中保存图像maybe here或其他任何内容。

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

这也控制该文件存在,并在新文件名称的末尾添加一个数字以保存它。

希望它有所帮助!

编辑:对不起,我忘了您必须在清单中询问<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> (或者以编程方式,如果你喜欢Marshmallow)

答案 6 :(得分:-3)

对于Marshmallow以下的版本,您可以直接在清单中提供权限。

但对于具有Marshmallow及以上版本的设备,您需要在运行时授予权限。

使用

Environment.getExternalStorageDirectory();

您可以直接访问外置SD卡(已安装) 希望这会有所帮助。