Android 如何获取主存储中的 Android/media/包名目录路径?
应该使用哪种方式?
如果必须获取主共享存储上的 Android/media/<package name>,仍可使用 externalMediaDirs,并根据存储卷属性选择目录。该接口已弃用,但这不意味着目录已经失效。Android 11(API 30)及以上也可以通过 StorageManager.primaryStorageVolume.directory 获取主存储卷根目录,再拼接相对路径。这种方式仍需写出 Android/media。
如果只是保存应用自己的非媒体文件,可以选择 context.filesDir 或 context.getExternalFilesDir(null)。官方对 getExternalMediaDirs() 的弃用说明面向媒体存储,建议迁移到 MediaStore,但没有提供能直接返回同一目录的新专用方法。Context API 文档
为什么不能直接取数组第一个元素?
问题中的两段代码采用了不同策略:第一段获取主共享存储根目录后拼接路径,第二段直接取 externalMediaDirs 数组的第一个元素。
getExternalMediaDirs() 的公开文档没有承诺第一个元素一定对应主共享存储,因此不应根据下标判断它是否为 microSD 卡。“第一个通常是可移除存储”也不是适用于所有设备的规则。getExternalMediaDirs() 返回值说明
Android API 中的 external storage 并不等同于 microSD 卡。应根据业务需求判断存储卷是否为主卷或是否可移除。
保留 externalMediaDirs,并明确选择主存储卷
如果需要由系统直接提供应用媒体目录,可以在 API 24 及以上使用下面的方式:
import android.annotation.TargetApi
import android.content.Context
import android.os.Environment
import android.os.storage.StorageManager
import java.io.File
@TargetApi(24)
@Suppress("DEPRECATION")
fun getPrimaryMediaDir(context: Context): File? {
val storageManager = context.getSystemService(StorageManager::class.java)
?: return null
return context.externalMediaDirs
.filterNotNull()
.firstOrNull { dir ->
val volume = storageManager.getStorageVolume(dir)
volume?.isPrimary == true &&
Environment.getExternalStorageState(dir) ==
Environment.MEDIA_MOUNTED
}
}
StorageManager.getStorageVolume(File) 从 API 24 开始可用,能识别路径所属的存储卷;isPrimary 表示该卷是否为主共享存储。按此判断可以避免依赖数组顺序。StorageManager 文档、StorageVolume 文档
函数只返回处于可读写挂载状态的候选目录,找不到时返回 null。@TargetApi 不会自动处理旧系统的兼容性,调用方仍需检查系统版本。@Suppress("DEPRECATION") 只用于表达这里有意保留该调用。
如果业务还要求排除物理上可移除的存储,可以在判断中增加 !volume.isRemovable。是否为主存储卷和是否可移除是两个不同的属性,不能相互替代。
API 30 及以上:获取主卷根目录后拼接
如果要求避开这两个已弃用的入口,可以通过主存储卷获取根目录:
import android.annotation.TargetApi
import android.content.Context
import android.os.Environment
import android.os.storage.StorageManager
import java.io.File
@TargetApi(30)
fun buildPrimaryMediaPath(context: Context): File? {
val storageManager = context.getSystemService(StorageManager::class.java)
?: return null
val volume = storageManager.primaryStorageVolume
if (volume.state != Environment.MEDIA_MOUNTED) return null
val root = volume.directory ?: return null
return File(root, "Android/media/${context.packageName}")
}
这里没有硬编码 /storage/emulated/0,但仍然依赖 Android/media/<package name> 的目录布局。StorageVolume.directory 从 API 30 开始可用,卷未挂载时可能返回 null。获取根目录不会额外授予文件访问权限。StorageVolume.getDirectory() 文档
这段代码只构造路径,不会创建目录,也不能保证文件写入成功。写入时仍需处理目录创建失败、空间不足和 I/O 异常。
存储非媒体文件时如何选择目录?
文件只供应用自身使用时,通常不必依赖 Android/media:
// 应用内部存储
val internalFile = File(context.filesDir, "settings.json")
// 主共享存储上的应用专属目录;不可用时结果为 null
val externalFile: File? = context.getExternalFilesDir(null)?.let { dir ->
File(dir, "settings.json")
}
getExternalFilesDir(null) 返回主共享存储上的应用专属文件目录,常见形式为 Android/data/<package name>/files。它适合保存应用自己的持久文件,应用卸载时文件会被删除。外部存储不可用时,还需处理 null。应用专属文件存储指南
如果文件是用户需要选择保存位置、导出或长期管理的文档,可以使用 Storage Access Framework:通过 ACTION_CREATE_DOCUMENT 创建文件,或通过 ACTION_OPEN_DOCUMENT_TREE 请求用户选择可授权的目录。这些方式返回 URI,后续应使用 ContentResolver 等接口操作。共享文档访问指南
路径和使用限制
- 使用小写的
Android/media,不要与原始描述里的Android/Media混用。 /storage/emulated/0是常见路径,不能作为所有设备和用户环境的固定值。- 优先在需要时重新获取目录,避免长期保存绝对路径。
Android/media在 API 中的定位是媒体目录。即使特定环境下可以写入非媒体文件,也不足以将它视为通用非媒体存储的长期接口保证。
备注:内容仅供参考。
