首页 文章

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

提问于
浏览
62

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

  • 2

    简答 .

    当您不需要共享文件时,ContextCompat.getExternalFilesDirs解决the access error .

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


    摘要 .

    由于continuous changes,没有universal waywrite to external SD card on Android

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

    • KitKat:引入了API,允许应用访问SD卡上特定于应用程序的目录中的文件 .

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

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

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


    1.权限 .

    您可以different api levelsAPI23+ at run time)上的外部SD卡 .

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

    使用Kitkat,没有生根的“完整解决方案”的机会几乎为零:Android项目肯定搞砸了 . 没有应用程序可以完全访问外部SD卡:文件管理器:您无法使用它们来管理外部SD卡 . 在大多数地区,他们只能阅读但不能写 . 媒体应用:您不能再对媒体收藏重新整理/重新整理,因为这些应用无法写入媒体收藏 . 办公室应用程序:几乎相同允许第三方应用程序在外部卡上写入的唯一位置是“他们自己的目录”(即/ sdcard / Android / data / <package_name_of_the_app>) . 真正解决这个问题的唯一方法是制造商(其中一些人修复了它,例如华为用他们的P6的Kitkat更新) - 或根...(Izzy的解释在这里继续)


    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.

    访问外部存储受各种Android权限的保护 . 从Android 1.0开始,写访问受WRITE_EXTERNAL_STORAGE权限保护 . 从Android 4.1开始,读取访问受READ_EXTERNAL_STORAGE权限保护 . 从Android 4.4开始,外部存储设备上的所有者,组和文件模式现在基于目录结构进行合成 . 这使应用程序可以在外部存储上管理其特定于程序包的目录,而无需拥有广泛的WRITE_EXTERNAL_STORAGE权限 . 例如,包名为com.example.foo的应用程序现在可以在没有权限的外部存储设备上自由访问Android / data / com.example.foo / . 通过将原始存储设备包装在FUSE守护程序中来完成这些合成权限 . Android 6.0引入了一种新的运行时权限模型,应用程序在运行时需要时请求功能 . 由于新模型包含READ / WRITE_EXTERNAL_STORAGE权限,因此平台需要动态授予存储访问权限,而不会终止或重新启动已在运行的应用程序 . 它通过维护所有已安装存储设备的三个不同视图来实现此目的:/ mnt / runtime / default显示给没有特殊存储权限的应用程序... / mnt / runtime / read显示给具有READ_EXTERNAL_STORAGE / mnt / runtime / write的应用程序被证明是使用WRITE_EXTERNAL_STORAGE的应用


    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 . 您的设备HTC10Shield K1可能是API 23 .

    您的日志显示a permission denied exception访问 /mnt/media_rw ,如_1967956_ for 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 尝试写入所有返回的目录并寻找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

    为了...在外部存储上写入文件,您的应用必须获得...系统权限:<manifest ...>
    <uses-permission android:name =“android.permission.READ_EXTERNAL_STORAGE”/>
    <uses-permission android:name =“android.permission.WRITE_EXTERNAL_STORAGE”/>
    </清单>
    如果您需要两个...,则只需要请求WRITE_EXTERNAL_STORAGE权限 .

    Ignore the next note due to bugs, but try to use ContextCompat.getExternalFilesDirs():

    注意:从Android 4.4开始,如果您只读取或写入应用程序专用的文件,则不需要这些权限 . 有关详细信息...,请参阅保存app-private文件 . <manifest ...>
    <uses-permission android:name =“android.permission.WRITE_EXTERNAL_STORAGE”
    android:maxSdkVersion =“18”/>
    </清单>

    Request permissions at runtime if API level 23+ and read Requesting Permissions at Run Time

    从Android 6.0(API级别23)开始,用户在应用程序运行时向应用程序授予权限,而不是在安装应用程序时...或更新应用程序...用户可以撤消权限 . //假设thisActivity是当前活动
    int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
    Manifest.permission.WRITE_EXTERNAL_STORAGE);


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

    Read Mark Murphy's explanation and recommended Dianne Hackborn and Dave Smith ones

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

    Prior to KitKat try to use Doomsknight method 1 or read this response by Gnathonic or 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;
    }
    

    Also read Paolo Rovelli's explanation and try to use Jeff Sharkey's solution since KitKat:

    在KitKat中,现在有一个用于与这些辅助共享存储设备交互的公共API . 新的Context.getExternalFilesDirs()和Context.getExternalCacheDirs()方法可以返回多个路径,包括主设备和辅助设备 . 然后,您可以迭代它们并检查Environment.getStorageState()和File.getFreeSpace()以确定存储文件的最佳位置 . 这些方法也可以在support-v4库中的ContextCompat上使用 .


    6. Lollipop还引入了更改和DocumentFile助手类 .

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

    这是一个很好的教程,可以与KitKat中的存储访问框架进行交互 . 与Lollipop中的新API交互非常相似(Jeff Sharkey的解释) .


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

    范围目录访问在Android 7.0中,应用程序可以使用新的API来请求访问特定的外部存储目录,包括可移动介质(如SD卡)上的目录...有关详细信息,请参阅范围内目录访问培训 .


    8. Android O更改 .

    从Android O开始,存储访问框架允许自定义文档提供程序为驻留在远程数据源中的文件创建可搜索文件描述符...如果应用程序在运行时请求了权限并且授予了权限,则在Android O之前的权限, 系统还错误地授予应用程序其他属于同一权限组的权限,并且已在清单中注册 . 对于定位Android O的应用,此行为已得到纠正 . 该应用程序仅被授予其明确请求的权限 . 但是,一旦用户向应用程序授予权限,则会自动授予该权限组中所有后续权限请求 . 例如,READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE ......


    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

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

    METHOD 1: (由于权限更改, NOT 在6.0及以上版本上工作)

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

    它将在字符串目录位置列表中返回 all mounted media (including Real SD Cards) . 通过该列表,您可以询问用户保存位置等 .

    你可以用以下方法调用它:

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

    METHOD 2:

    使用v4支持库

    import android.support.v4.content.ContextCompat;
    

    只需调用以下内容即可获取 File 存储位置列表 .

    File[] list = ContextCompat.getExternalFilesDirs(myContext, null);
    

    但是,这些地点的用途不同 .

    返回应用程序可以放置其拥有的永久文件的所有外部存储设备上特定于应用程序的目录的绝对路径 . 这些文件是应用程序的内部文件,通常不会被用户视为媒体 . 此处返回的外部存储设备被视为设备的永久部分,包括模拟外部存储和物理介质插槽,如电池盒中的SD卡 . 返回的路径不包括瞬态设备,例如USB闪存驱动器 . 应用程序可以在任何或所有返回的设备上存储数据 . 例如,应用可以选择在具有最多可用空间的设备上存储大文件

    More Info on ContextCompat

    它们就像应用程序特定的文件 . 隐藏在其他应用中 .

  • 87

    只是另一个答案 . 这个答案只显示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;
    }
    

    注意:我只获取"first"外部SD卡但是您可以修改它并返回 ArrayList<File> 而不是 File 并让循环继续而不是在找到第一个之后调用 break .

  • 0

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

    第一个资源来自 Android Programming, The Big Nerd Ranch Guide 2nd edition ,第16章,第294页 .

    本书介绍了基本和外部文件和目录方法 . 我将尝试制作一份与您的问题相关的简历 .

    本书的以下部分:

    External Storage

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

    Context 中的基本文件和目录方法

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

    Context也提供了很多获取外部存储的方法 . 这些方法提供了简单的方法来获取主要的外部存储,并且有点方便地获取其他所有内容 . 所有这些方法也将文件存储在公共场所,所以要小心它们 .

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

    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

    <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 权限,但您不需要它 . 您不会向外部存储器写任何内容:相机应用程序将为您执行此操作

    第二个资源是link读取了所有内容,但您也可以跳转到 Using the External Storage 部分 .

    Reference:

    More reading stuff:

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

  • -2

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

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

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

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

    以下是在外部存储器中创建新文件的方法(如果存在于设备或设备外部存储器中,则为SD卡(如果不存在) . 只需将"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,请以编程方式提出)

  • -3
    `Log.d(TAG, System.getenv("SECONDARY_STORAGE"));`
    

    输出:

    `D/MainActivity: /storage/extSdCard`
    

    使用Note 3 Android 5.0 .

  • 9

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

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

    通过使用

    Environment.getExternalStorageDirectory();
    

    你可以直接访问外部SD卡(已安装)希望这一点帮助 .

相关问题