系列目录 :第一篇:全景图与调用链路概览 | 第二篇:内核层---USB驱动与uevent | 第三篇:Native层---vold与NetlinkManager | 第四篇:Framework层(上)---UsbHostManager | 第五篇:Framework层(下)---MountService | 第六篇:广播分发与SystemUI响应 | 第七篇:应用层---MediaScanner与SAF | 第八篇:实战调试与案例分析
一、引言
上一篇文章我们拆解了 Framework 层的 USB 设备感知链(UsbHostManager)。本篇聚焦真正让 U 盘"可用"的角色------MountService。
在 Android 7(Nougat)中,它叫 MountService,而不是后来版本中的 StorageManagerService。它的职责可以概括为两句话:
- 向下:通过 NDC(NativeDaemonConnector)与 vold 通信,控制磁盘/卷的挂载、卸载、格式化
- 向上:向应用层广播存储状态变化,提供 StorageManager API
它是 vold(C++ Native)与 Android 应用层之间的唯一桥梁。
二、启动链路
2.1 SystemServer 中的启动
源码路径 :frameworks/base/services/java/com/android/server/SystemServer.java
java
// MountService 在 startOtherServices() 阶段启动
try {
mSystemServiceManager.startService(MOUNT_SERVICE_CLASS);
mountService = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
} catch (Throwable e) {
reportWtf("starting MountService", e);
}
服务发布的 Binder 名称是 "mount"。
2.2 类结构与构造函数
源码路径 :frameworks/base/services/core/java/com/android/server/MountService.java(3891行)
java
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
// ★ 与 vold 通信的 NDC 连接器
private final NativeDaemonConnector mConnector;
// ★ 消息处理线程
private final MountServiceHandler mHandler;
// ★ 磁盘和卷集合
private final ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>();
private final ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
// ★ StorageEventListener 回调管理
private final Callbacks mCallbacks;
public MountService(Context context) {
mContext = context;
mCallbacks = new Callbacks(FgThread.get().getLooper());
// 创建专用 HandlerThread
HandlerThread hthread = new HandlerThread(TAG);
hthread.start();
mHandler = new MountServiceHandler(hthread.getLooper());
// ★ 创建 NDC 连接器(与 vold 通信)
mConnector = new NativeDaemonConnector(this, "vold", 500,
VOLD_TAG, 25, null);
mConnector.setDebug(true);
}
}
关键设计 :Android 7 使用
NativeDaemonConnector(NDC)与 vold 通信,这是基于 Unix Domain Socket 的文本协议。这与后来版本使用的 Binder 通信完全不同。
三、NDC 通信机制
3.1 协议格式
命令(Java → vold): <code> <command> [args...]
"10 volume mount public:8,1 0 0"
响应(vold → Java): {<code> <key> <value>...}
"{650 public:8,1 0 \"disk:8,0\" \"\"}"
3.2 onEvent() ------ 接收 vold 回调
java
@Override
public boolean onEvent(int code, String raw, String[] cooked) {
synchronized (mLock) {
return onEventLocked(code, raw, cooked);
}
}
private boolean onEventLocked(int code, String raw, String[] cooked) {
switch (code) {
case VoldResponseCode.DISK_CREATED: { // 640
final String id = cooked[1]; // "disk:8,0"
int flags = Integer.parseInt(cooked[2]); // 8
mDisks.put(id, new DiskInfo(id, flags));
break;
}
case VoldResponseCode.VOLUME_CREATED: { // 650
final String id = cooked[1]; // "public:8,1"
final int type = Integer.parseInt(cooked[2]); // 0=TYPE_PUBLIC
final String diskId = TextUtils.nullIfEmpty(cooked[3]);
final String partGuid = TextUtils.nullIfEmpty(cooked[4]);
final DiskInfo disk = mDisks.get(diskId);
final VolumeInfo vol = new VolumeInfo(id, type, disk, partGuid);
mVolumes.put(id, vol);
onVolumeCreatedLocked(vol); // ★ 触发自动挂载
break;
}
case VoldResponseCode.VOLUME_STATE_CHANGED: { // 651
final VolumeInfo vol = mVolumes.get(cooked[1]);
if (vol != null) {
final int oldState = vol.state;
final int newState = Integer.parseInt(cooked[2]);
vol.state = newState;
onVolumeStateChangedLocked(vol, oldState, newState);
}
break;
}
// ... 更多事件
}
}
3.3 实际 NDC 通信序列
vold → MountService:
{640 disk:8,0 8} ← DISK_CREATED
{641 disk:8,0 123009761280} ← DISK_SIZE_CHANGED
{642 disk:8,0 USB} ← DISK_LABEL_CHANGED
{650 public:8,1 0 "disk:8,0" ""} ← VOLUME_CREATED
{651 public:8,1 0} ← STATE_UNMOUNTED
{651 public:8,1 1} ← STATE_CHECKING
{652 public:8,1 vfat} ← VOLUME_FS_TYPE_CHANGED
{653 public:8,1 AECD-6E85} ← VOLUME_FS_UUID_CHANGED
{656 public:8,1 /mnt/media_rw/Udisk} ← VOLUME_PATH_CHANGED
{651 public:8,1 2} ← STATE_MOUNTED ★
{200 10 Command succeeded} ← 挂载命令成功
四、Volume 状态机
4.1 状态常量
源码路径 :frameworks/base/core/java/android/os/storage/VolumeInfo.java
java
public class VolumeInfo {
public static final int STATE_UNMOUNTED = 0;
public static final int STATE_CHECKING = 1;
public static final int STATE_MOUNTED = 2;
public static final int STATE_MOUNTED_READ_ONLY = 3;
public static final int STATE_FORMATTING = 4;
public static final int STATE_EJECTING = 5;
public static final int STATE_UNMOUNTABLE = 6;
public static final int STATE_REMOVED = 7;
public static final int STATE_BAD_REMOVAL = 8;
public static final int TYPE_PUBLIC = 0;
public static final int TYPE_PRIVATE = 1;
public static final int TYPE_EMULATED = 2;
public String id; // "public:8,1"
public int type;
public String diskId; // "disk:8,0"
public String partGuid;
public int state; // ★ 当前状态
public String fsType; // "vfat"
public String fsUuid;
public String fsLabel;
public String path; // ★ 挂载路径: "/mnt/media_rw/Udisk"
public String internalPath;
}
4.2 状态流转图
U盘插入
│
▼
┌────────────────┐
│ STATE_UNMOUNTED │ ←── 初始状态
└───────┬────────┘
│ blkid 检测
▼
┌────────────────┐
│ STATE_CHECKING │ ←── fsck 文件系统检查
└───────┬────────┘
│ 检查通过
▼
┌────────────────┐ 拔出(正常/异常)
│ STATE_MOUNTED │ ───────────────┐
└───────┬────────┘ │
│ "弹出" │
▼ ▼
┌────────────────┐ ┌──────────────────┐
│ STATE_EJECTING │ │ STATE_BAD_REMOVAL │
└───────┬────────┘ └──────────────────┘
│
▼
┌────────────────┐
│ STATE_UNMOUNTED │
└────────────────┘
4.3 自动挂载触发
java
private void onVolumeCreatedLocked(VolumeInfo vol) {
if (vol.type == VolumeInfo.TYPE_PUBLIC) {
// ★ 公共卷(U盘):自动挂载
vol.mountUserId = mCurrentUserId;
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
}
}
// H_VOLUME_MOUNT 处理
case H_VOLUME_MOUNT: {
final VolumeInfo vol = (VolumeInfo) msg.obj;
mount(vol); // → 发送 NDC 命令到 vold
break;
}
五、挂载与卸载流程
5.1 mount() ------ 发送挂载命令
java
private void mount(VolumeInfo vol) {
try {
// ★ 通过 NDC 发送挂载命令到 vold
mConnector.execute("volume", "mount",
vol.id, vol.mountFlags, vol.mountUserId);
// 实际发送: "10 volume mount public:8,1 0 0"
} catch (NativeDaemonConnectorException e) {
Slog.e(TAG, "Failed to mount volume", e);
}
}
5.2 unmount() ------ 发送卸载命令
java
private void unmount(VolumeInfo vol) {
try {
mConnector.execute("volume", "unmount", vol.id);
} catch (NativeDaemonConnectorException e) {
Slog.e(TAG, "Failed to unmount volume", e);
}
}
六、广播发送
6.1 状态变化 → 广播
java
private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
// 1. 通知 IStorageEventListener 回调
mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);
// 2. ★ 发送 VOLUME_STATE_CHANGED 内部广播
if (mBootCompleted && isBroadcastWorthy(vol)) {
final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_STATE, newState);
intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.fsUuid);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
}
// 3. ★ 发送 MEDIA_* 用户广播
final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState);
final String newStateEnv = VolumeInfo.getEnvironmentForState(newState);
if (!Objects.equals(oldStateEnv, newStateEnv)) {
for (int userId : mSystemUnlockedUsers) {
if (vol.isVisibleForRead(userId)) {
final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false);
mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
}
}
}
}
6.2 广播 Action 映射
| Volume 状态 | 环境字符串 | 广播 Action |
|---|---|---|
| STATE_UNMOUNTED | MEDIA_UNMOUNTED | ACTION_MEDIA_UNMOUNTED |
| STATE_CHECKING | MEDIA_CHECKING | ACTION_MEDIA_CHECKING |
| STATE_MOUNTED | MEDIA_MOUNTED | ACTION_MEDIA_MOUNTED |
| STATE_EJECTING | MEDIA_EJECTING | ACTION_MEDIA_EJECT |
| STATE_BAD_REMOVAL | MEDIA_BAD_REMOVAL | ACTION_MEDIA_BAD_REMOVAL |
七、StorageEventListener 回调机制
MountService 提供了两种通知方式:
- 广播(Broadcast):面向所有应用的异步通知
- StorageEventListener(Binder 回调):面向系统组件的直接回调
java
// 应用通过 StorageManager 注册监听器
StorageManager sm = context.getSystemService(StorageManager.class);
sm.registerListener(new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
// 卷状态变化回调
}
@Override
public void onDiskDestroyed(DiskInfo disk) {
// 磁盘移除回调
}
});
重要限制:StorageEventListener 的回调依赖 vold 的 NDC 事件。当 USB 总线复位时,vold 不感知复位,因此回调不会触发。这是 Android 7 中一个常见的 USB 状态不一致问题的根因。
八、关键源码文件索引
frameworks/base/services/core/java/com/android/server/
├── MountService.java ★ 本文主角
│ → NativeDaemonConnector 通信
│ → onEvent() vold 事件处理
│ → mount() / unmount() 操作
│ → 广播发送
│
├── NativeDaemonConnector.java
│ → Java 层 NDC 客户端
│
├── NativeDaemonEvent.java
│ → NDC 事件解析
│
frameworks/base/core/java/android/os/storage/
├── VolumeInfo.java
│ → 状态/类型常量、id/path/fsType 等字段
├── DiskInfo.java
│ → 磁盘信息
├── StorageManager.java
│ → 公开 API(registerListener、getVolumes 等)
└── StorageEventListener.java
→ Storage 事件监听接口
system/vold/
├── CommandListener.cpp
│ → 接收和处理 NDC 命令
└── ResponseCode.h
→ NDC 响应码定义(640-656)
九、小结
本文拆解了 Android 7 MountService 的完整工作流程:
- NDC 通信 :通过
NativeDaemonConnector与 vold 进行文本协议通信 - onEvent():接收 vold 的 NDC 回调,处理 DISK_CREATED → VOLUME_CREATED → VOLUME_STATE_CHANGED 事件序列
- 自动挂载:TYPE_PUBLIC 类型的卷创建后自动触发挂载
- 两层广播 :
VOLUME_STATE_CHANGED内部广播 +MEDIA_MOUNTED/MEDIA_UNMOUNTED用户广播 - StorageEventListener:Binder 回调方式,供系统组件直接监听
从实际日志可以看到完整的 NDC 通信序列,从 {640 disk:8,0 8} 到 {651 public:8,1 2} 再到 {200 10 Command succeeded}。下一篇我们将看广播如何分发,以及 SystemUI 如何响应。