PAYATHON 2026

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 中的定位是媒体目录。即使特定环境下可以写入非媒体文件,也不足以将它视为通用非媒体存储的长期接口保证。

备注:内容仅供参考。