Android7 U盘插拔链路源码全解析(六)广播分发与SystemUI响应

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


一、引言

前面五篇我们走完了 Kernel → vold → UsbHostManager → MountService 的全程。到第五篇末尾,VOLUME_STATE_CHANGEDMEDIA_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 响应的完整流程:

  1. AMS 广播调度:MountService 发出的广播经过 AMS 的 BroadcastQueue 并行分发
  2. SystemUI 双重通知:StorageNotification(通知栏)+ StatusBarView(状态栏图标)
  3. 两种监听方式:BroadcastReceiver(广谱可靠) vs StorageEventListener(依赖 vold NDC 回调)
  4. 应用层接收 :BroadcastReceiver 通过 USB_DEVICE_ATTACHED/DETACHEDVOLUME_STATE_CHANGED 广播感知 U 盘插拔

理解这些机制对于排查"U 盘图标不消失"或"通知栏无反应"等问题至关重要。下一篇我们将深入应用层,分析 MediaScanner 如何扫描 U 盘文件。