当前位置: 首页 > news >正文

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

// 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_UNMOUNTEDMEDIA_UNMOUNTEDACTION_MEDIA_UNMOUNTED
STATE_CHECKINGMEDIA_CHECKINGACTION_MEDIA_CHECKING
STATE_MOUNTEDMEDIA_MOUNTEDACTION_MEDIA_MOUNTED
STATE_EJECTINGMEDIA_EJECTINGACTION_MEDIA_EJECT
STATE_BAD_REMOVALMEDIA_BAD_REMOVALACTION_MEDIA_BAD_REMOVAL

七、StorageEventListener 回调机制

MountService 提供了两种通知方式:

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

  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 如何响应。

http://www.jsqmd.com/news/1078530/

相关文章:

  • 天硕存储(TOPSSD)观察:工业级固态硬盘全形态覆盖与极端环境适配
  • AI 代码生成与验证:当 LLM 写算法题,靠谱程度到底有多少?
  • Claude架构级更新:胶水层消亡与AI工程范式转移
  • 2026适合企业行政在会议场景解决会议内容整理繁琐的实用工具
  • pointer-cad LLM 负责根据文本指令和 GNN 提取的几何特征预测下一步操作。
  • 3步搞定知网文献批量下载:学术研究的效率革命
  • Python 描述符与元类:从 Django ORM 到自定义属性系统的进阶之路
  • AI智能体从18.75%到100%:GDPevo自进化基准实测,5条隐性规则如何决定业务正确性
  • AI 代币:实用型代币的经济模型设计——从效用锚定到通胀控制的链上经济学实践
  • 5步掌握MuseTalk:开源实时唇同步AI的完整实战指南
  • ROS C++回调机制与Spinning原理深度解析
  • AI 效率工具产品化:从技术验证到 PMF 的关键路径与决策框架
  • 《AgentX Python 专栏》03-架构篇:Agent 和「调个 API」的本质区别,在架构上长什么样?
  • 缠论量化实战:chan.py框架完整指南
  • 很反感动不动就劝人“要放下”“要看开”的鸡汤:绝大多数的豁达,都不是练出来的心态,而是攒出来的底气
  • 动物声纹分析实战:从生物声学到边缘AI部署
  • 用cleanlab清洗标签提升XGBoost准确率:数据为中心的实战闭环
  • Claude Code 实战:Agent Skills
  • 消息队列高可用架构:从顺序写到消费幂等的生产级保障
  • 大厂前端高并发架构:从虚拟列表到状态分层的性能优化实战
  • CSS 动画性能优化:从 60fps 到渲染管线的精准控制
  • 【uni-app 性能调优】从 20fps 到 60fps:用“时间切片”根治复杂表单卡顿
  • 抖音无水印下载终极指南:3分钟搞定批量下载与智能管理
  • 《软考人必看!告别手动F5,我用Python写了个“成绩解放器”,支持NAS部署秒推微信》
  • 机器学习模型监控实战:从数据漂移到业务归因的五层防御体系
  • AI 每日资讯简报
  • UI 组件的抽象边界:从复合组件模式到无障碍优先的 API 设计
  • Rust 所有权与借用:从 MIR 到汇编的零成本抽象验证
  • AI 编程工具链选型:从代码补全到智能重构的成本收益分析
  • 代数几何中的对数正则性判别准则:从对数微分到Frobenius-Witt结构