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

问题描述 投票:65回答:7

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

摘要

你可以在grant read/write accessdifferent api levels)上使用API23+ at run time到外部SD卡。

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

通用方式:

history说,没有通用的方式写入外部SD卡but continues...

qazxsw poi演示了qazxsw poi。

基于API的方式:

This fact之前尝试使用these examples of external storage configurations for devices

请求权限KitKat(Api <23)和Doomsknight method 1, method 2 otherwise.(Api> = 23)。

推荐方式:

当您不需要共享文件时,in manifest解决了at run time问题。

分享它的安全方式是使用ContextCompat.getExternalFilesDirsthe access error

隐私感知方式:

content provider,默认情况下,针对Android 9(API级别28)或更低级别的应用程序看不到任何更改。

默认情况下(或选择加入Android)定位Android Q的应用会在外部存储空间中获得new Storage Access Framework


1.初步答复。

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

由于As of Android Q Beta 4,没有filtered viewuniversal way

  • Pre-KitKat:官方Android平台除了例外情况外根本不支持SD卡。
  • KitKat:引入了API,允许应用程序访问SD卡上特定于应用程序的目录中的文件。
  • Lollipop:添加了API,允许应用请求访问其他提供商拥有的文件夹。
  • Nougat:提供了一个简化的API来访问公共外部存储目录。
  • ... write to external SD card on Android

在不同API级别上授予对外部SD卡的读/写访问权限的更好方法是什么

基于continuous changesAndroid Q privacy change: App-scoped and media-scoped storageDoomsknight's answermineDave SmithMark Murphy blog posts: 1


2.更新了答案。

更新1.我从Doomknight的答案中尝试了方法1,但没有用:

正如您所看到的,我在尝试在SD上写入之前在运行时检查权限...

我将使用ContextCompat.getExternalFilesDirs作为参考,使用更新后问题的应用程序特定目录devices that return internal first.to avoid the issue

改进ContextCompat.getExternalFilesDirs()以确定什么代表可移动媒体基于不同的api级别,如getExternalFilesDir documentation

...但是我在两个不同的设备上尝试了访问错误:HTC10和Shield K1。

请记住,heuristics和第三方应用程序必须通过android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT。您的设备Android 6.0 supports portable storage devicesStorage Access Framework可能是API 23。

您的日志显示访问HTC10Shield K1,例如a permission denied exception用于API 19+:

/mnt/media_rw

我从来没有尝试过,所以我不能共享代码,但我会避免this fix试图在所有返回的目录上写,并寻找<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 alternativegetStorageDirectories()解决了ContextCompat.getExternalFilesDirs


3. Android 1.0 .. Pre-KitKat。

在KitKat之前尝试使用the issue或阅读Gnathonic的access to other folders

Doomsknight method 1

将下一个代码添加到this response并阅读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; }

对外部存储的访问受各种Android权限的保护。

从Android 1.0开始,写访问受AndroidManifest.xml保护。

从Android 4.1开始,读取访问权限受Getting access to external storage权限保护。

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

WRITE_EXTERNAL_STORAGE permission

如果你需要两个......,你只需要请求READ_EXTERNAL_STORAGE权限。

阅读<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> </manifest> WRITE_EXTERNAL_STORAGE Mark Murphy's explanationrecommended帖子

  • 在Android 4.4之前,Android中没有可移动媒体的官方支持,从KitKat开始,FMW API中出现了“主要”和“次要”外部存储的概念。
  • 以前的应用程序只依赖于MediaStore索引,附带硬件或检查挂载点并应用一些启发式方法来确定代表可移动媒体的内容。

4. Android 4.4 KitKat引入了Dianne Hackborn

由于错误而忽略下一个注释,但尝试使用Dave Smith

  • 自Android 4.2以来,谷歌一直要求设备制造商锁定可移动媒体以提高安全性(多用户支持),并在4.4中添加了新的测试。
  • 由于添加了KitKat the Storage Access Framework (SAF)和其他方法以在所有可用存储卷上返回可用路径(返回的第一个项目是主卷)。
  • 下表显示了开发人员可能尝试做什么以及KitKat将如何响应:ContextCompat.getExternalFilesDirs()

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

enter image description here

同时阅读saving files that are app-private并尝试使用自KitKat以来的<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> </manifest>

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

新的Paolo Rovelli's explanationJeff Sharkey's solution方法可以返回多个路径,包括主设备和辅助设备。

然后,您可以迭代它们并检查Context.getExternalFilesDirs()Context.getExternalCacheDirs()以确定存储文件的最佳位置。

这些方法也可以在support-v4库中的Environment.getStorageState()上找到。

从Android 4.4开始,外部存储设备上的所有者,组和文件模式现在基于目录结构进行合成。这使应用程序可以在外部存储上管理其特定于程序包的目录,而无需拥有广泛的File.getFreeSpace()权限。例如,包名为ContextCompat的应用程序现在可以在没有权限的外部存储设备上自由访问WRITE_EXTERNAL_STORAGE。通过将原始存储设备包装在FUSE守护程序中来完成这些合成权限。

使用KitKat,您可以在没有生根的情况下获得“完整解决方案”,这几乎是零:

Android项目肯定搞砸了。没有应用可以完全访问外部SD卡:

  • 文件管理器:您无法使用它们来管理外部SD卡。在大多数地区,他们只能阅读但不能写。
  • 媒体应用:您不能再对媒体收藏重新整理/重新整理,因为这些应用无法写入媒体收藏。
  • 办公室应用程序:几乎一样

允许第三方应用程序在外部卡上写入的唯一地方是“他们自己的目录”(即com.example.foo)。

真正解决这个问题的唯一方法是制造商(其中一些人修复了它,例如华为用P6的Kitkat更新) - 或根... Android/data/com.example.foo/


5. Android 5.0引入了更改和/sdcard/Android/data/<package_name_of_the_app>助手类。

(Izzy's explanation continues here)在API 19中添加,在API 21中弃用,DocumentFile

getStorageState用于与KitKat中的存储访问框架进行交互。

与Lollipop use getExternalStorageState(File)中的新API交互。


6. Android 6.0 Marshmallow引入了新的Here's a great tutorial模型。

is very similar (Jeff Sharkey's explanation)在运行时如果API等级23+并且读取runtime permissions

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

Request permissions

Android 6.0引入了一个新的Requesting Permissions at Run Time模型,其中应用程序在运行时需要时请求功能。由于新模型包含// Assume thisActivity is the current activity int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 权限,因此平台需要动态授予存储访问权限,而不会终止或重新启动已在运行的应用程序。它通过维护所有已安装存储设备的三个不同视图来实现此目的:

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

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

runtime permissions在Android 7.0中,应用程序可以使用新的API来请求访问特定的READ/WRITE_EXTERNAL_STORAGE目录,包括SD卡等可移动媒体上的目录......

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

阅读Mark Murphy帖子:external storageScoped Directory Access training

请注意,在Android Q中不推荐使用7.0中添加的作用域目录访问。

具体来说,Be Careful with Scoped Directory Access上的It was deprecated in Android Q方法已被弃用。

他们添加了一个可用作替代品的createAccessIntent()


8. Android 8.0 Oreo .. Android Q Beta的变化。

StorageVolume,存储访问框架允许createOpenDocumentTreeIntent()为驻留在远程数据源中的文件创建可搜索的文件描述符...

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

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

例如,custom documents providersPermissions ......

更新:Android Q早期版测试版暂时将READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限替换为更精细,特定于媒体的权限。

注意:Google在Beta 1上引入了READ_EXTERNAL_STORAGE,并在Beta 2之前从文档中删除了它们...

注意:特定于早期beta版本中引入的媒体集合的权限 - WRITE_EXTERNAL_STORAGErolesREAD_MEDIA_IMAGES-READ_MEDIA_AUDIO。更多信息:

Mark Murphy的Q Beta 4(最终API)评论:READ_MEDIA_VIDEO

“死亡比生命更普遍。每个人都死了,但不是每个人都死。” - are now obsolete


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

The Death of External Storage: The End of the Saga(?)

Andrew Sachs

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


10.相关的错误和问题。

Android SD Card Write Permission using SAF (Storage Access Framework)

SAFFAQ: The Storage Access Framework FAQ


9
投票

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

方法1 :(由于权限更改,不适用于6.0及更高版本)

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

它将所有已安装的媒体(包括Real SD卡)返回到字符串目录位置列表中。通过该列表,您可以询问用户保存位置等。

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

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

方法2:

使用v4支持库

 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; } 存储位置列表。

import android.support.v4.content.ContextCompat;

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

返回应用程序可以放置其拥有的永久文件的所有外部存储设备上特定于应用程序的目录的绝对路径。这些文件是应用程序的内部文件,通常不会被用户视为媒体。

此处返回的外部存储设备被视为设备的永久部分,包括模拟外部存储和物理介质插槽,如电池盒中的SD卡。返回的路径不包括瞬态设备,例如USB闪存驱动器。

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

File

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


3
投票

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

这个最初发布在这里( File[] list = ContextCompat.getExternalFilesDirs(myContext, null); )由我来获取Android 5.0+上的外部SD卡的大小

要将外部SD卡作为More Info on ContextCompat

Is there a way to get SD Card size in Android?

注意:我只获得“第一个”外部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; } 并让循环继续而不是在找到第一个之后调用ArrayList<File>


3
投票

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

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

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

本书的以下部分:

外部存储

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

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

Context.getFileStreamPath(String)

如果您要存储只有当前应用程序需要使用的文件,那么这些方法正是您所需要的。

另一方面,如果你需要另一个应用程序写入这些文件,你就不走运了:虽然有一个Context.getFilesDir()标志你可以传入| 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| ,但它已被弃用,并且对新设备的影响并不完全可靠。如果要存储文件以与其他应用程序共享或从其他应用程序(存储图片等文件)接收文件,则需要将它们存储在外部存储上。

外部存储有两种:主存储和其他所有存储。所有Android设备至少有一个外部存储位置:主要位置,位于Context.MODE_WORLD_READABLE返回的文件夹中。这可能是SD卡,但现在它更常集成到设备本身。某些设备可能具有额外的外部存储。那将属于“其他一切”。

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

Context中的外部文件和目录方法

openFileOutput(String, int)

从技术上讲,上面提供的外部文件夹可能不可用,因为某些设备使用可移动SD卡进行外部存储。实际上,这很少是一个问题,因为几乎所有现代设备都具有不可移动的内部存储空间用于“外部”存储。所以不值得花极大的代价来解释它。但是我们建议使用简单的代码来防范这种可能性,你马上就会做。

外部存储权限

通常,您需要具有从外部存储写入或读取的权限。权限是您使用Environment.getExternalStorageDirectory()标记放入清单中的众所周知的字符串值。他们告诉Android你想要做一些Android要求你许可的东西。

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

在Android 4.4,KitKat中,他们放松了这个限制。由于| 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. | 返回了一个特定于您的应用程序的文件夹,因此您希望能够读取和写入那里的文件是有意义的。因此,在Android 4.4(API 19)及更高版本中,您不需要此文件夹的此权限。 (但是你仍然需要它用于其他类型的外部存储。)

在清单中添加一行,请求读取外部存储的权限,但仅限于API清单16.5请求外部存储权限(<uses-permission>

Context.getExternalFilesDir(String)

maxSdkVersion属性使得您的应用只会在早于API 19的Android KitKat版本的Android上请求此权限。请注意,您只需要阅读外部存储空间。还有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" /> 读取所有内容,但您也可以跳转到使用外部存储部分。

参考:

  • Android存储选项:WRITE_EXTERNAL_STORAGE
  • 书籍网站:link

更多阅读材料:

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


0
投票

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

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

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

https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_MOVIES

-1
投票

以下是在外部存储器中创建新文件的方法(如果存在于设备或设备外部存储器中,则为SD卡(如果不存在)。只需将“foldername”替换为所需目标文件夹的名称,将“filename”替换为要保存的文件的名称。当然,在这里你可以看到如何保存通用文件,现在你可以搜索如何保存图像How can I get external SD card path for Android 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;
}

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

希望能帮助到你!

编辑:对不起,我忘了你要在你的清单中要求maybe here(或者如果您喜欢Marshmallow,请以编程方式)


-3
投票

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

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

通过使用

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

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

© www.soinside.com 2019 - 2024. All rights reserved.