1. 项目缘起:一个被忽视的“基础”需求
在Android应用开发中,处理外置存储设备(比如U盘、SD卡、移动硬盘)的读取和监听,听起来像是一个基础功能,但实际做起来,你会发现官方文档语焉不详,社区资料零散,很多开发者要么用一些“野路子”凑合,要么干脆放弃这个需求。我最近在做一个车载多媒体项目,核心功能之一就是自动识别并播放用户插入的U盘里的音乐和视频。这个需求把我逼到了墙角,不得不把Android存储系统的这块“硬骨头”啃下来。
为什么说它“硬”?因为从Android 4.4(KitKat)引入存储访问框架(SAF)开始,到Android 10(Q)强制推行分区存储(Scoped Storage),再到Android 11(R)对文件访问权限的进一步收紧,Google一直在试图收紧应用对共享存储空间的随意访问。外置存储设备,作为典型的“外部共享存储”,其访问方式也在这股浪潮中几经变迁。很多老方法(比如直接通过 /storage 路径遍历)在新系统上要么完全失效,要么行为诡异。如果你还在用 Environment.getExternalStorageDirectory() 来指代“外部存储”,那在Android 10及以上的设备上,这很可能指向的是应用自身的私有目录,而非真正的SD卡或U盘。
所以,这个标题背后,其实是一系列问题的集合:在分区存储的新世界里,应用如何合法、安全地发现和读取用户主动连接的外置存储设备?又如何能像系统自带的“文件”应用一样,实时感知到设备的插拔事件?这不仅仅是调用几个API那么简单,它涉及到存储卷(Storage Volume)的管理、媒体库(MediaStore)的查询、内容提供者(ContentProvider)的交互,以及广播(Broadcast)机制的正确使用。接下来,我将结合实战,把这套机制掰开揉碎了讲清楚。
2. 核心概念厘清:什么是“外置存储设备”?
在深入代码之前,我们必须统一语言。在Android的语境下,“外置存储设备”是一个容易混淆的概念,它至少包含两层含义,而我们的目标通常是第二层。
2.1 广义的“外部存储” vs 狭义的“可移动存储”
-
广义外部存储(External Storage) :这是一个历史遗留的、容易误导人的术语。在早期Android中,它指的是设备内部的一块从系统存储中划分出来、用于存放用户媒体文件(照片、音乐等)的存储区域。这块区域在物理上位于设备内部,但对用户和应用而言是“外部”的、可共享的。
Environment.getExternalStorageDirectory()返回的就是这个路径(如/storage/emulated/0)。在分区存储下,应用对此区域的直接文件路径访问受到严格限制。 -
狭义的可移动存储(Removable Storage) :这才是我们标题里所指的“外置存储设备”,即用户物理上可以插拔的存储介质。主要包括:
- SD卡(MicroSD Card) :通过卡槽插入。
- USB存储设备(USB Mass Storage) :如U盘、移动硬盘,通过OTG(On-The-Go)数据线连接。
我们的核心目标,就是发现、枚举并访问这些 物理上可插拔的存储卷 。
2.2 存储卷(StorageVolume)与挂载点
Android系统将每一个可用的存储区域抽象为一个 StorageVolume 对象。每个 StorageVolume 包含以下关键信息:
- 描述 :用户可见的名称,如“SD卡”、“USB驱动器”。
- UUID :卷的唯一标识符。
- 路径 :该卷在文件系统中的挂载点(Mount Point)。 重要提示 :在Android 10及以上,应用通常无法直接通过这个路径字符串进行文件操作(
java.io.FileAPI),必须通过MediaStore或Storage Access Framework来访问。 - 是否可移除(Removable) :判断是否为SD卡或USB设备。
- 是否为主存储(Primary) :判断是否为设备内置的主共享存储。
理解这些概念是后续所有操作的基础。我们的任务就是获取到所有 isRemovable() == true 的 StorageVolume ,并监听它们的状态变化。
3. 方案选型与权限配置:为新存储模型做好准备
在动手写代码前,必须根据你的目标API级别( targetSdkVersion )确定技术方案,并配置正确的权限。这里假设你的应用需要适配Android 10(API 29)及以上版本,这是目前的主流和强制要求。
3.1 Android 10+ 的强制分区存储(Scoped Storage)
分区存储的核心思想是:应用默认只能访问自身的私有目录和通过特定API(如 MediaStore )授予访问权限的公共媒体文件。对于外置存储设备上的文件,也不例外。
这意味着:
- 不能 再使用
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />来直接获取访问所有共享文件的权限。在Android 10+上,这个权限的作用域被大大缩小了。 - 不能 再通过
File类直接遍历/storage或/mnt目录来发现设备。 - 正确的姿势是使用系统提供的
StorageManager和MediaStoreAPI。
3.2 必需的权限声明
在 AndroidManifest.xml 中,你需要声明以下权限:
<!-- 用于请求访问所有文件的管理权限(谨慎使用) -->
<!-- 在Android 11(R, API 30)及以上,需要此权限才能使用MANAGE_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<!-- 在Android 10(Q, API 29)上,可能还需要声明旧权限,但运行时请求无效 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
重要警告 : MANAGE_EXTERNAL_STORAGE 权限授予应用访问设备上所有文件的能力,包括其他应用的数据。Google Play对使用此权限的应用审核非常严格,你必须提供充分的理由(如文件管理器、备份还原、杀毒软件等),否则很可能被拒。对于只是读取U盘媒体文件的多媒体应用,

的发现、监听与安全访问实战&spm=1001.2101.3001.5002&articleId=93280730&d=1&t=3&u=b3e3c85c5c5f46cc90aece5651434d48)
2761

被折叠的 条评论
为什么被折叠?



