Android7 U盘插拔链路源码全解析(五)Framework层(下) MountService

系列目录第一篇:全景图与调用链路概览 | 第二篇:内核层---USB驱动与uevent | 第三篇:Native层---vold与NetlinkManager | 第四篇:Framework层(上)---UsbHostManager | 第五篇:Framework层(下)---MountService | 第六篇:广播分发与SystemUI响应 | 第七篇:应用层---MediaScanner与SAF | 第八篇:实战调试与案例分析


一、引言

上一篇文章我们拆解了 Framework 层的 USB 设备感知链(UsbHostManager)。本篇聚焦真正让 U 盘"可用"的角色------MountService

在 Android 7(Nougat)中,它叫 MountService,而不是后来版本中的 StorageManagerService。它的职责可以概括为两句话:

  1. 向下:通过 NDC(NativeDaemonConnector)与 vold 通信,控制磁盘/卷的挂载、卸载、格式化
  2. 向上:向应用层广播存储状态变化,提供 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 提供了两种通知方式:

  1. 广播(Broadcast):面向所有应用的异步通知
  2. 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 的完整工作流程:

  1. NDC 通信 :通过 NativeDaemonConnector 与 vold 进行文本协议通信
  2. onEvent():接收 vold 的 NDC 回调,处理 DISK_CREATED → VOLUME_CREATED → VOLUME_STATE_CHANGED 事件序列
  3. 自动挂载:TYPE_PUBLIC 类型的卷创建后自动触发挂载
  4. 两层广播VOLUME_STATE_CHANGED 内部广播 + MEDIA_MOUNTED/MEDIA_UNMOUNTED 用户广播
  5. StorageEventListener:Binder 回调方式,供系统组件直接监听

从实际日志可以看到完整的 NDC 通信序列,从 {640 disk:8,0 8}{651 public:8,1 2} 再到 {200 10 Command succeeded}。下一篇我们将看广播如何分发,以及 SystemUI 如何响应。