系列目录:第一篇:全景图与调用链路概览 | 第二篇:内核层—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
// MountService 在 startOtherServices() 阶段启动try{mSystemServiceManager.startService(MOUNT_SERVICE_CLASS);mountService=IMountService.Stub.asInterface(ServiceManager.getService("mount"));}catch(Throwablee){reportWtf("starting MountService",e);}服务发布的 Binder 名称是"mount"。
2.2 类结构与构造函数
源码路径:frameworks/base/services/core/java/com/android/server/MountService.java(3891行)
classMountServiceextendsIMountService.StubimplementsINativeDaemonConnectorCallbacks,Watchdog.Monitor{// ★ 与 vold 通信的 NDC 连接器privatefinalNativeDaemonConnectormConnector;// ★ 消息处理线程privatefinalMountServiceHandlermHandler;// ★ 磁盘和卷集合privatefinalArrayMap<String,DiskInfo>mDisks=newArrayMap<>();privatefinalArrayMap<String,VolumeInfo>mVolumes=newArrayMap<>();// ★ StorageEventListener 回调管理privatefinalCallbacksmCallbacks;publicMountService(Contextcontext){mContext=context;mCallbacks=newCallbacks(FgThread.get().getLooper());// 创建专用 HandlerThreadHandlerThreadhthread=newHandlerThread(TAG);hthread.start();mHandler=newMountServiceHandler(hthread.getLooper());// ★ 创建 NDC 连接器(与 vold 通信)mConnector=newNativeDaemonConnector(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 回调
@OverridepublicbooleanonEvent(intcode,Stringraw,String[]cooked){synchronized(mLock){returnonEventLocked(code,raw,cooked);}}privatebooleanonEventLocked(intcode,Stringraw,String[]cooked){switch(code){caseVoldResponseCode.DISK_CREATED:{// 640finalStringid=cooked[1];// "disk:8,0"intflags=Integer.parseInt(cooked[2]);// 8mDisks.put(id,newDiskInfo(id,flags));break;}caseVoldResponseCode.VOLUME_CREATED:{// 650finalStringid=cooked[1];// "public:8,1"finalinttype=Integer.parseInt(cooked[2]);// 0=TYPE_PUBLICfinalStringdiskId=TextUtils.nullIfEmpty(cooked[3]);finalStringpartGuid=TextUtils.nullIfEmpty(cooked[4]);finalDiskInfodisk=mDisks.get(diskId);finalVolumeInfovol=newVolumeInfo(id,type,disk,partGuid);mVolumes.put(id,vol);onVolumeCreatedLocked(vol);// ★ 触发自动挂载break;}caseVoldResponseCode.VOLUME_STATE_CHANGED:{// 651finalVolumeInfovol=mVolumes.get(cooked[1]);if(vol!=null){finalintoldState=vol.state;finalintnewState=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
publicclassVolumeInfo{publicstaticfinalintSTATE_UNMOUNTED=0;publicstaticfinalintSTATE_CHECKING=1;publicstaticfinalintSTATE_MOUNTED=2;publicstaticfinalintSTATE_MOUNTED_READ_ONLY=3;publicstaticfinalintSTATE_FORMATTING=4;publicstaticfinalintSTATE_EJECTING=5;publicstaticfinalintSTATE_UNMOUNTABLE=6;publicstaticfinalintSTATE_REMOVED=7;publicstaticfinalintSTATE_BAD_REMOVAL=8;publicstaticfinalintTYPE_PUBLIC=0;publicstaticfinalintTYPE_PRIVATE=1;publicstaticfinalintTYPE_EMULATED=2;publicStringid;// "public:8,1"publicinttype;publicStringdiskId;// "disk:8,0"publicStringpartGuid;publicintstate;// ★ 当前状态publicStringfsType;// "vfat"publicStringfsUuid;publicStringfsLabel;publicStringpath;// ★ 挂载路径: "/mnt/media_rw/Udisk"publicStringinternalPath;}4.2 状态流转图
U盘插入 │ ▼ ┌────────────────┐ │ STATE_UNMOUNTED │ ←── 初始状态 └───────┬────────┘ │ blkid 检测 ▼ ┌────────────────┐ │ STATE_CHECKING │ ←── fsck 文件系统检查 └───────┬────────┘ │ 检查通过 ▼ ┌────────────────┐ 拔出(正常/异常) │ STATE_MOUNTED │ ───────────────┐ └───────┬────────┘ │ │ "弹出" │ ▼ ▼ ┌────────────────┐ ┌──────────────────┐ │ STATE_EJECTING │ │ STATE_BAD_REMOVAL │ └───────┬────────┘ └──────────────────┘ │ ▼ ┌────────────────┐ │ STATE_UNMOUNTED │ └────────────────┘4.3 自动挂载触发
privatevoidonVolumeCreatedLocked(VolumeInfovol){if(vol.type==VolumeInfo.TYPE_PUBLIC){// ★ 公共卷(U盘):自动挂载vol.mountUserId=mCurrentUserId;mHandler.obtainMessage(H_VOLUME_MOUNT,vol).sendToTarget();}}// H_VOLUME_MOUNT 处理caseH_VOLUME_MOUNT:{finalVolumeInfovol=(VolumeInfo)msg.obj;mount(vol);// → 发送 NDC 命令到 voldbreak;}五、挂载与卸载流程
5.1 mount() —— 发送挂载命令
privatevoidmount(VolumeInfovol){try{// ★ 通过 NDC 发送挂载命令到 voldmConnector.execute("volume","mount",vol.id,vol.mountFlags,vol.mountUserId);// 实际发送: "10 volume mount public:8,1 0 0"}catch(NativeDaemonConnectorExceptione){Slog.e(TAG,"Failed to mount volume",e);}}5.2 unmount() —— 发送卸载命令
privatevoidunmount(VolumeInfovol){try{mConnector.execute("volume","unmount",vol.id);}catch(NativeDaemonConnectorExceptione){Slog.e(TAG,"Failed to unmount volume",e);}}六、广播发送
6.1 状态变化 → 广播
privatevoidonVolumeStateChangedLocked(VolumeInfovol,intoldState,intnewState){// 1. 通知 IStorageEventListener 回调mCallbacks.notifyVolumeStateChanged(vol,oldState,newState);// 2. ★ 发送 VOLUME_STATE_CHANGED 内部广播if(mBootCompleted&&isBroadcastWorthy(vol)){finalIntentintent=newIntent(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_* 用户广播finalStringoldStateEnv=VolumeInfo.getEnvironmentForState(oldState);finalStringnewStateEnv=VolumeInfo.getEnvironmentForState(newState);if(!Objects.equals(oldStateEnv,newStateEnv)){for(intuserId:mSystemUnlockedUsers){if(vol.isVisibleForRead(userId)){finalStorageVolumeuserVol=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 回调):面向系统组件的直接回调
// 应用通过 StorageManager 注册监听器StorageManagersm=context.getSystemService(StorageManager.class);sm.registerListener(newStorageEventListener(){@OverridepublicvoidonVolumeStateChanged(VolumeInfovol,intoldState,intnewState){// 卷状态变化回调}@OverridepublicvoidonDiskDestroyed(DiskInfodisk){// 磁盘移除回调}});重要限制: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 如何响应。