系列目录 :第一篇:全景图与调用链路概览 | 第二篇:内核层---USB驱动与uevent | 第三篇:Native层---vold与NetlinkManager | 第四篇:Framework层(上)---UsbHostManager | 第五篇:Framework层(下)---MountService | 第六篇:广播分发与SystemUI响应 | 第七篇:应用层---MediaScanner与SAF | 第八篇:实战调试与案例分析
一、引言
前面五篇我们走完了 Kernel → vold → UsbHostManager → MountService 的全程。到第五篇末尾,VOLUME_STATE_CHANGED 和 MEDIA_MOUNTED 广播已经发出去了。
但这里有一个容易被忽略的事实:
广播发出去 ≠ 用户看到效果。
从 sendBroadcastAsUser() 到通知栏弹出"U 盘已插入",中间还有 AMS 的广播调度、SystemUI 的注册接收、通知构建与展示三个关键环节。
本文聚焦这一"最后一公里"------广播如何分发、哪些应用会收到、SystemUI 如何展示通知。
二、广播分发全链路
2.1 ActivityManagerService 的广播入队
当 MountService 调用 sendBroadcastAsUser() 后,广播首先进入 AMS:
java
// ContextImpl → AMS 的跨进程调用
public final int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle resultExtras,
String[] requiredPermissions, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
// 1. 权限检查
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
// 2. 校验 Intent 合法性
intent = verifyBroadcastLocked(intent);
// 3. 找到匹配的 BroadcastReceiver
List<ResolveInfo> receivers = collectReceiverComponents(
intent, resolvedType, callingUid, users);
// 4. ★ 创建 BroadcastRecord 并入队
BroadcastRecord r = new BroadcastRecord(queue, intent, ...);
// 5. ★ 加入广播队列
queue.enqueueParallelBroadcastLocked(r); // 普通广播 → 并行队列
// 6. ★ 触发调度
queue.scheduleBroadcastsLocked();
}
}
MountService 发出的广播都是普通广播(非 ordered),走的是并行队列------所有接收者同时收到,无序。
2.2 广播投递到 App 进程
java
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered, int index) {
// 1. 权限检查
if (filter.requiredPermission != null) {
if (checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true) != PERMISSION_GRANTED) {
return; // 权限不足,跳过
}
}
// 2. ★ 跨进程发送广播
try {
filter.receiverList.app.thread.scheduleRegisteredReceiver(
filter.receiverList.receiver, r.intent,
r.resultCode, r.resultData, r.resultExtras,
r.ordered, r.initialSticky, r.userId);
} catch (RemoteException e) {
// 进程挂了
}
}
三、接收方全景:谁会收到这些广播
| 接收者 | 注册方式 | 关注广播 | 作用 |
|---|---|---|---|
| SystemUI | 动态注册 | MEDIA_MOUNTED / MEDIA_UNMOUNTED |
通知栏展示 |
| MediaProvider | 静态(manifest) | MEDIA_MOUNTED / MEDIA_UNMOUNTED |
MediaScanner 扫描 |
| Settings | 动态 + 静态 | MEDIA_MOUNTED |
存储设置页面刷新 |
| DocumentsUI | 静态(manifest) | MEDIA_MOUNTED |
SAF 文件选择器更新 |
| 第三方 App | 动态注册 | MEDIA_MOUNTED / USB_DEVICE_ATTACHED |
文件管理/设备管理 |
四、SystemUI 响应
4.1 方式一:StorageNotification(通知栏)
源码路径 :frameworks/base/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
java
public class StorageNotification extends SystemUI {
@Override
public void start() {
// ★ 动态注册广播接收者
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
filter.addAction(Intent.ACTION_MEDIA_CHECKING);
filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
filter.addAction(Intent.ACTION_MEDIA_EJECT);
filter.addDataScheme("file");
mContext.registerReceiver(mReceiver, filter);
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Uri uri = intent.getData();
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
onMediaMounted(uri, intent); // ★ 显示通知
} else if (Intent.ACTION_MEDIA_UNMOUNTED.equals(action)) {
onMediaUnmounted(uri, intent); // 取消通知
} else if (Intent.ACTION_MEDIA_BAD_REMOVAL.equals(action)) {
onMediaBadRemoval(uri, intent); // 警告通知
}
}
};
}
五、广播时序总结
完整的时间线(以插入为例):
T=0ms Kernel: uevent KOBJ_ADD
T≈50ms vold: NetlinkHandler 捕获
T≈100ms vold: Disk::create() + readPartitions()
T≈150ms vold: PublicVolume::create()
T≈200ms vold → MountService: {640 disk:8,0 8}
T≈200ms vold → MountService: {650 public:8,1 0}
T≈250ms MountService: mountVolume() → NDC: "volume mount public:8,1"
T≈300ms vold: doMount() → mount(2) → FUSE 启动
T≈350ms vold → MountService: {651 public:8,1 2} (STATE_MOUNTED)
T≈360ms MountService: 发送 VOLUME_STATE_CHANGED
T≈360ms MountService: 发送 ACTION_MEDIA_MOUNTED
T≈380ms SystemUI: StorageNotification 收到广播 → 显示通知
T≈450ms MediaScannerReceiver: 开始扫描文件
六、关键源码文件索引
frameworks/base/packages/SystemUI/
├── src/com/android/systemui/usb/
│ └── StorageNotification.java ★ 通知栏 USB 通知
│
frameworks/base/services/core/java/com/android/server/am/
├── BroadcastQueue.java ★ 广播队列管理
└── ActivityManagerService.java ★ 广播分发入口
frameworks/base/core/java/android/os/storage/
├── StorageEventListener.java ★ Storage 事件监听接口
└── StorageManager.java ★ 公开 API
七、小结
本文拆解了 Android 7 中广播分发与 SystemUI 响应的完整流程:
- AMS 广播调度:MountService 发出的广播经过 AMS 的 BroadcastQueue 并行分发
- SystemUI 双重通知:StorageNotification(通知栏)+ StatusBarView(状态栏图标)
- 两种监听方式:BroadcastReceiver(广谱可靠) vs StorageEventListener(依赖 vold NDC 回调)
- 应用层接收 :BroadcastReceiver 通过
USB_DEVICE_ATTACHED/DETACHED和VOLUME_STATE_CHANGED广播感知 U 盘插拔
理解这些机制对于排查"U 盘图标不消失"或"通知栏无反应"等问题至关重要。下一篇我们将深入应用层,分析 MediaScanner 如何扫描 U 盘文件。