三星 Galaxy S3具有外部SD卡插槽,它被挂载到 /mnt/extSdCard
。
我该如何像Environment.getExternalStorageDirectory()
这样获取此路径?
这将返回mnt/sdcard
,我找不到用于外部SD卡的API。(或某些平板电脑上的可移动USB存储器。)
三星 Galaxy S3具有外部SD卡插槽,它被挂载到 /mnt/extSdCard
。
我该如何像Environment.getExternalStorageDirectory()
这样获取此路径?
这将返回mnt/sdcard
,我找不到用于外部SD卡的API。(或某些平板电脑上的可移动USB存储器。)
我找到的解决方案有一些变化,源自这里
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 版本。
我已经使用以下设备测试了我的修改版:
以及一些将 sd 卡作为主要存储设备的单卡设备:
除了 Incredible 外,所有这些设备只返回其可移动存储。我可能需要进行一些额外的检查,但这至少比我目前找到的任何解决方案都要好一点。
我找到了一种更可靠的方法来获取系统中所有SD卡的路径。该方法适用于所有Android版本,并返回所有存储(包括模拟存储)的路径。
在我所有的设备上都可以正常工作。
P.S.:基于Environment类的源代码。
private static final Pattern DIR_SEPORATOR = Pattern.compile("/");
/**
* Raturns all available SD-Cards in the system (include emulated)
*
* Warning: Hack! Based on Android source code of version 4.3 (API 18)
* Because there is no standart way to get it.
* TODO: Test on future Android versions 4.4+
*
* @return paths to all available SD-Cards in the system (include emulated)
*/
public static String[] getStorageDirectories()
{
// Final set of paths
final Set<String> rv = new HashSet<String>();
// Primary physical SD-CARD (not emulated)
final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
// All Secondary SD-CARDs (all exclude primary) separated by ":"
final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
// Primary emulated SD-CARD
final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
if(TextUtils.isEmpty(rawEmulatedStorageTarget))
{
// Device has physical external storage; use plain paths.
if(TextUtils.isEmpty(rawExternalStorage))
{
// EXTERNAL_STORAGE undefined; falling back to default.
rv.add("/storage/sdcard0");
}
else
{
rv.add(rawExternalStorage);
}
}
else
{
// Device has emulated storage; external storage paths should have
// userId burned into them.
final String rawUserId;
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
{
rawUserId = "";
}
else
{
final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
final String[] folders = DIR_SEPORATOR.split(path);
final String lastFolder = folders[folders.length - 1];
boolean isDigit = false;
try
{
Integer.valueOf(lastFolder);
isDigit = true;
}
catch(NumberFormatException ignored)
{
}
rawUserId = isDigit ? lastFolder : "";
}
// /storage/emulated/0[1,2,...]
if(TextUtils.isEmpty(rawUserId))
{
rv.add(rawEmulatedStorageTarget);
}
else
{
rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
}
}
// Add all secondary storages
if(!TextUtils.isEmpty(rawSecondaryStoragesStr))
{
// All Secondary SD-CARDs splited into array
final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
Collections.addAll(rv, rawSecondaryStorages);
}
return rv.toArray(new String[rv.size()]);
}
/
的 Pattern
。 - Ankit BansalSystem.getenv("SECONDARY_STORAGE")
返回 /storage/sdcard1
。那个位置不存在,但实际位置是 /storage/42CE-FD0A
,其中 42CE-FD0A 是已格式化分区的卷序列号。 - DaveAlden我猜想要使用外部SD卡,你需要使用这个:
new File("/mnt/external_sd/")
或者
new File("/mnt/extSdCard/")
对于你的情况...
替换 Environment.getExternalStorageDirectory()
。
这对我有效。你应该首先检查mnt目录中的内容,然后从那里开始操作。
你应该使用某种选择方法来选择要使用的SD卡:
File storageDir = new File("/mnt/");
if(storageDir.isDirectory()){
String[] dirList = storageDir.list();
//TODO some type of selecton method?
}
我曾使用 Dmitriy Lozenko 的解决方案,但在 Asus Zenfone2, Marshmallow 6.0.1 上测试时发现该解决方案无法工作。具体而言,当获取微型SD卡路径 /storage/F99C-10F4/ 时,该解决方案会失败。我修改了代码,直接从模拟应用程序路径中获取模拟根路径,并添加了更多已知的特定于手机型号的物理路径:context.getExternalFilesDirs(null);
。
为了让我们的生活更加轻松,我创建了一个库here。您可以通过gradle、maven、sbt和leiningen构建系统使用它。
如果您喜欢老式的方法,也可以直接从here复制粘贴文件,但您将无法在未手动检查更新的情况下知道是否有更新。
如果您有任何问题或建议,请告诉我
context.getExternalFilesDirs(null)
将为其中一个文件返回null
,并且在接下来的循环中会出现异常。我建议在循环的第一行添加if (file == null) continue;
。 - Koger为了获取所有外部存储(无论它们是SD卡还是内部不可移除存储),您可以使用以下代码:
final String state = Environment.getExternalStorageState();
if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) { // we can read the External Storage...
//Retrieve the primary External Storage:
final File primaryExternalStorage = Environment.getExternalStorageDirectory();
//Retrieve the External Storages root directory:
final String externalStorageRootDir;
if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) { // no parent...
Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
}
else {
final File externalStorageRoot = new File( externalStorageRootDir );
final File[] files = externalStorageRoot.listFiles();
for ( final File file : files ) {
if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) { // it is a real directory (not a USB drive)...
Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
}
}
}
}
或者,您可以使用System.getenv("EXTERNAL_STORAGE")来检索主要的外部存储目录(例如"/storage/sdcard0"),并使用System.getenv("SECONDARY_STORAGE")来检索所有次要目录的列表(例如"/storage/extSdCard:/storage/UsbDriveA:/storage/UsbDriveB")。请注意,在这种情况下,您可能需要过滤次要目录列表以排除USB驱动器。
无论如何,请注意,使用硬编码路径始终是一种不好的方法(尤其是当每个制造商都可以按照自己的意愿更改路径时)。
好消息!在KitKat中,现在有一个公共API可以与这些次要共享存储设备进行交互。
新的Context.getExternalFilesDirs()
和Context.getExternalCacheDirs()
方法可以返回多个路径,包括主设备和次要设备。您可以遍历它们并检查Environment.getStorageState()
和File.getFreeSpace()
以确定存储文件的最佳位置。这些方法也可在支持v4库中的ContextCompat
上使用。
还要注意,如果您只想使用由Context
返回的目录,则不再需要READ_
或WRITE_EXTERNAL_STORAGE
权限。从现在开始,您将始终可以读/写访问这些目录,无需额外的权限。
应用程序也可以通过结束生命周期请求权限来继续在旧设备上工作,例如:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
File primaryExtSd=Environment.getExternalStorageDirectory();
你可以获取到主要外部SD卡的路径,然后使用以下代码:
File parentDir=new File(primaryExtSd.getParent());
您可以获取主外部存储的父目录,它也是所有外部SD卡的父目录。现在,您可以列出所有存储并选择您想要的存储。
希望这对您有所帮助。
发现一种更正式的方法,从Android N开始(如果在此之前,您可以尝试我上面写的方法),特别是从Android R开始,使用StorageManager(基于我在这里所提供的解决方案):
class MainActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getSdCardPaths(this, true)?.forEach { volumePath ->
Log.d("AppLog", "volumePath:$volumePath")
}
}
/**
* returns a list of all available sd cards paths, or null if not found.
*
* @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
*/
fun getSdCardPaths(context: Context, includePrimaryExternalStorage: Boolean): List<String>? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumes = storageManager.storageVolumes
if (!storageVolumes.isNullOrEmpty()) {
val primaryVolume = storageManager.primaryStorageVolume
val result = ArrayList<String>(storageVolumes.size)
for (storageVolume in storageVolumes) {
val volumePath = getVolumePath(storageVolume) ?: continue
if (storageVolume.uuid == primaryVolume.uuid || storageVolume.isPrimary) {
if (includePrimaryExternalStorage)
result.add(volumePath)
continue
}
result.add(volumePath)
}
return if (result.isEmpty()) null else result
}
}
val externalCacheDirs = ContextCompat.getExternalCacheDirs(context)
if (externalCacheDirs.isEmpty())
return null
if (externalCacheDirs.size == 1) {
if (externalCacheDirs[0] == null)
return null
val storageState = EnvironmentCompat.getStorageState(externalCacheDirs[0])
if (Environment.MEDIA_MOUNTED != storageState)
return null
if (!includePrimaryExternalStorage && Environment.isExternalStorageEmulated())
return null
}
val result = ArrayList<String>()
if (externalCacheDirs[0] != null && (includePrimaryExternalStorage || externalCacheDirs.size == 1))
result.add(getRootOfInnerSdCardFolder(context, externalCacheDirs[0]))
for (i in 1 until externalCacheDirs.size) {
val file = externalCacheDirs[i] ?: continue
val storageState = EnvironmentCompat.getStorageState(file)
if (Environment.MEDIA_MOUNTED == storageState)
result.add(getRootOfInnerSdCardFolder(context, externalCacheDirs[i]))
}
return if (result.isEmpty()) null else result
}
fun getRootOfInnerSdCardFolder(context: Context, inputFile: File): String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
storageManager.getStorageVolume(inputFile)?.let {
val result = getVolumePath(it)
if (result != null)
return result
}
}
var file: File = inputFile
val totalSpace = file.totalSpace
while (true) {
val parentFile = file.parentFile
if (parentFile == null || parentFile.totalSpace != totalSpace || !parentFile.canRead())
return file.absolutePath
file = parentFile
}
}
@RequiresApi(Build.VERSION_CODES.N)
fun getVolumePath(storageVolume: StorageVolume): String? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
return storageVolume.directory?.absolutePath
try {
val storageVolumeClazz = StorageVolume::class.java
val getPath = storageVolumeClazz.getMethod("getPath")
return getPath.invoke(storageVolume) as String
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
}
感谢大家提供的线索,尤其是 @SmartLemon,我找到了解决方案。如果还有其他人需要,我将我的最终解决方案放在这里(查找第一个列出的外部 SD 卡):
public File getExternalSDCardDirectory()
{
File innerDir = Environment.getExternalStorageDirectory();
File rootDir = innerDir.getParentFile();
File firstExtSdCard = innerDir ;
File[] files = rootDir.listFiles();
for (File file : files) {
if (file.compareTo(innerDir) != 0) {
firstExtSdCard = file;
break;
}
}
//Log.i("2", firstExtSdCard.getAbsolutePath().toString());
return firstExtSdCard;
}
请参考我的代码,希望对您有所帮助:
Runtime runtime = Runtime.getRuntime();
Process proc = runtime.exec("mount");
InputStream is = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
String line;
String mount = new String();
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null) {
if (line.contains("secure")) continue;
if (line.contains("asec")) continue;
if (line.contains("fat")) {//TF card
String columns[] = line.split(" ");
if (columns != null && columns.length > 1) {
mount = mount.concat("*" + columns[1] + "\n");
}
} else if (line.contains("fuse")) {//internal storage
String columns[] = line.split(" ");
if (columns != null && columns.length > 1) {
mount = mount.concat(columns[1] + "\n");
}
}
}
txtView.setText(mount);