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

Android7 U盘插拔链路源码全解析(五)Framework层(下) MountService 系列目录第一篇全景图与调用链路概览 | 第二篇内核层—USB驱动与uevent | 第三篇Native层—vold与NetlinkManager | 第四篇Framework层(上)—UsbHostManager |第五篇Framework层(下)—MountService| 第六篇广播分发与SystemUI响应 | 第七篇应用层—MediaScanner与SAF | 第八篇实战调试与案例分析一、引言上一篇文章我们拆解了 Framework 层的 USB 设备感知链UsbHostManager。本篇聚焦真正让 U 盘可用的角色——MountService。在 Android 7Nougat中它叫MountService而不是后来版本中的 StorageManagerService。它的职责可以概括为两句话向下通过 NDCNativeDaemonConnector与 vold 通信控制磁盘/卷的挂载、卸载、格式化向上向应用层广播存储状态变化提供 StorageManager API它是 voldC Native与 Android 应用层之间的唯一桥梁。二、启动链路2.1 SystemServer 中的启动源码路径frameworks/base/services/java/com/android/server/SystemServer.java// MountService 在 startOtherServices() 阶段启动try{mSystemServiceManager.startService(MOUNT_SERVICE_CLASS);mountServiceIMountService.Stub.asInterface(ServiceManager.getService(mount));}catch(Throwablee){reportWtf(starting MountService,e);}服务发布的 Binder 名称是mount。2.2 类结构与构造函数源码路径frameworks/base/services/core/java/com/android/server/MountService.java3891行classMountServiceextendsIMountService.StubimplementsINativeDaemonConnectorCallbacks,Watchdog.Monitor{// ★ 与 vold 通信的 NDC 连接器privatefinalNativeDaemonConnectormConnector;// ★ 消息处理线程privatefinalMountServiceHandlermHandler;// ★ 磁盘和卷集合privatefinalArrayMapString,DiskInfomDisksnewArrayMap();privatefinalArrayMapString,VolumeInfomVolumesnewArrayMap();// ★ StorageEventListener 回调管理privatefinalCallbacksmCallbacks;publicMountService(Contextcontext){mContextcontext;mCallbacksnewCallbacks(FgThread.get().getLooper());// 创建专用 HandlerThreadHandlerThreadhthreadnewHandlerThread(TAG);hthread.start();mHandlernewMountServiceHandler(hthread.getLooper());// ★ 创建 NDC 连接器与 vold 通信mConnectornewNativeDaemonConnector(this,vold,500,VOLD_TAG,25,null);mConnector.setDebug(true);}}关键设计Android 7 使用NativeDaemonConnectorNDC与 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:{// 640finalStringidcooked[1];// disk:8,0intflagsInteger.parseInt(cooked[2]);// 8mDisks.put(id,newDiskInfo(id,flags));break;}caseVoldResponseCode.VOLUME_CREATED:{// 650finalStringidcooked[1];// public:8,1finalinttypeInteger.parseInt(cooked[2]);// 0TYPE_PUBLICfinalStringdiskIdTextUtils.nullIfEmpty(cooked[3]);finalStringpartGuidTextUtils.nullIfEmpty(cooked[4]);finalDiskInfodiskmDisks.get(diskId);finalVolumeInfovolnewVolumeInfo(id,type,disk,partGuid);mVolumes.put(id,vol);onVolumeCreatedLocked(vol);// ★ 触发自动挂载break;}caseVoldResponseCode.VOLUME_STATE_CHANGED:{// 651finalVolumeInfovolmVolumes.get(cooked[1]);if(vol!null){finalintoldStatevol.state;finalintnewStateInteger.parseInt(cooked[2]);vol.statenewState;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.javapublicclassVolumeInfo{publicstaticfinalintSTATE_UNMOUNTED0;publicstaticfinalintSTATE_CHECKING1;publicstaticfinalintSTATE_MOUNTED2;publicstaticfinalintSTATE_MOUNTED_READ_ONLY3;publicstaticfinalintSTATE_FORMATTING4;publicstaticfinalintSTATE_EJECTING5;publicstaticfinalintSTATE_UNMOUNTABLE6;publicstaticfinalintSTATE_REMOVED7;publicstaticfinalintSTATE_BAD_REMOVAL8;publicstaticfinalintTYPE_PUBLIC0;publicstaticfinalintTYPE_PRIVATE1;publicstaticfinalintTYPE_EMULATED2;publicStringid;// public:8,1publicinttype;publicStringdiskId;// disk:8,0publicStringpartGuid;publicintstate;// ★ 当前状态publicStringfsType;// vfatpublicStringfsUuid;publicStringfsLabel;publicStringpath;// ★ 挂载路径: /mnt/media_rw/UdiskpublicStringinternalPath;}4.2 状态流转图U盘插入 │ ▼ ┌────────────────┐ │ STATE_UNMOUNTED │ ←── 初始状态 └───────┬────────┘ │ blkid 检测 ▼ ┌────────────────┐ │ STATE_CHECKING │ ←── fsck 文件系统检查 └───────┬────────┘ │ 检查通过 ▼ ┌────────────────┐ 拔出正常/异常 │ STATE_MOUNTED │ ───────────────┐ └───────┬────────┘ │ │ 弹出 │ ▼ ▼ ┌────────────────┐ ┌──────────────────┐ │ STATE_EJECTING │ │ STATE_BAD_REMOVAL │ └───────┬────────┘ └──────────────────┘ │ ▼ ┌────────────────┐ │ STATE_UNMOUNTED │ └────────────────┘4.3 自动挂载触发privatevoidonVolumeCreatedLocked(VolumeInfovol){if(vol.typeVolumeInfo.TYPE_PUBLIC){// ★ 公共卷U盘自动挂载vol.mountUserIdmCurrentUserId;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(mBootCompletedisBroadcastWorthy(vol)){finalIntentintentnewIntent(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_* 用户广播finalStringoldStateEnvVolumeInfo.getEnvironmentForState(oldState);finalStringnewStateEnvVolumeInfo.getEnvironmentForState(newState);if(!Objects.equals(oldStateEnv,newStateEnv)){for(intuserId:mSystemUnlockedUsers){if(vol.isVisibleForRead(userId)){finalStorageVolumeuserVolvol.buildStorageVolume(mContext,userId,false);mHandler.obtainMessage(H_VOLUME_BROADCAST,userVol).sendToTarget();}}}}6.2 广播 Action 映射Volume 状态环境字符串广播 ActionSTATE_UNMOUNTEDMEDIA_UNMOUNTEDACTION_MEDIA_UNMOUNTEDSTATE_CHECKINGMEDIA_CHECKINGACTION_MEDIA_CHECKINGSTATE_MOUNTEDMEDIA_MOUNTEDACTION_MEDIA_MOUNTEDSTATE_EJECTINGMEDIA_EJECTINGACTION_MEDIA_EJECTSTATE_BAD_REMOVALMEDIA_BAD_REMOVALACTION_MEDIA_BAD_REMOVAL七、StorageEventListener 回调机制MountService 提供了两种通知方式广播Broadcast面向所有应用的异步通知StorageEventListenerBinder 回调面向系统组件的直接回调// 应用通过 StorageManager 注册监听器StorageManagersmcontext.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 │ → 公开 APIregisterListener、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用户广播StorageEventListenerBinder 回调方式供系统组件直接监听从实际日志可以看到完整的 NDC 通信序列从{640 disk:8,0 8}到{651 public:8,1 2}再到{200 10 Command succeeded}。下一篇我们将看广播如何分发以及 SystemUI 如何响应。