android12 SystemUI下拉通知栏的加载流程

19 阅读4分钟

1. SystemUI 服务启动与窗口创建

SystemUI 是一个系统应用,由 SystemServer 进程负责启动,入口是 SystemUIApplication

启动后,核心服务 StatusBar 的 start() 方法会被调用。在这里,它会执行 createAndAddWindows() 方法,通过 WindowManager 为状态栏和通知栏创建独立的系统窗口。

//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
    //创建状态栏视图
    makeStatusBarView(result);
    //添加下拉通知面板窗口
    mNotificationShadeWindowController.attach();
    ...
}

protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
    final Context context = mContext;
    ...
    //创建下拉面板视图,创建状态栏窗口视图
    inflateStatusBarWindow();
    ...
//为下拉面板根视图设置触摸监听器,处理触摸事件分发
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());

    //初始化通知列表控制器
    mStackScrollerController =
            mNotificationPanelViewController.getNotificationStackScrollLayoutController();
    //获取 `NotificationStackScrollLayout` 视图实例
    mStackScroller = mStackScrollerController.getView();
    //获取通知列表容器接口,用于操作通知列表
    NotificationListContainer notifListContainer =
            mStackScrollerController.getNotificationListContainer();
    mNotificationLogger.setUpWithContainer(notifListContainer);

    ...

}

//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
private void inflateStatusBarWindow() {
   //创建下拉面板视图,SuperStatusBarViewFactory
    mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
    //获取构建器,将下拉面板视图作为依赖注入到组件中
    StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
            .statusBarWindowView(mNotificationShadeWindowView).build();
    //获取下拉面板窗口控制器
    mNotificationShadeWindowViewController = statusBarComponent
            .getNotificationShadeWindowViewController();
    
    ...
    //获取通知面板控制器
    mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
    ...
}

2. 加载通知面板根布局:super_notification_shade.xml

通过 SuperStatusBarViewFactory 的 getNotificationShadeWindowView() 方法加载并解析 R.layout.super_notification_shade。加载完成后,NotificationShadeWindowController 的 attach() 方法会将对应的 UI 添加到屏幕上。

//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
public NotificationShadeWindowView getNotificationShadeWindowView() {
    if (mNotificationShadeWindowView != null) {
        return mNotificationShadeWindowView;
    }

    mNotificationShadeWindowView = (NotificationShadeWindowView)
            mInjectionInflationController.injectable(
            LayoutInflater.from(mContext)).inflate(R.layout.super_notification_shade,
            /* root= */ null);
    ...

    return mNotificationShadeWindowView;
}

//frameworks/base/packages/SystemUI/res/layout/super_notification_shade.xml
<com.android.systemui.statusbar.phone.NotificationShadeWindowView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sysui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    ...

    <include layout="@layout/status_bar_expanded"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible" />

    ...
</com.android.systemui.statusbar.phone.NotificationShadeWindowView>

3. 下拉面板的视图层次:status_bar_expanded.xml

status_bar_expanded.xml 构建了下拉面板的完整视图层次:

  • 根容器是 NotificationPanelView
  • 内部包含了 NotificationsQuickSettingsContainer,它同时容纳了快捷开关面板(QS)和通知列表。
  • 通知列表的滚动容器是 NotificationStackScrollLayout
//frameworks/base/packages/SystemUI/res/layout/status_bar_expanded.xml
<com.android.systemui.statusbar.phone.NotificationPanelView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/notification_panel"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">
    ...
    <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="@integer/notification_panel_layout_gravity"
        android:id="@+id/notification_container_parent"
        android:clipToPadding="false"
        android:clipChildren="false">

        ...
        #快捷开关面板(QS)的容器
        <FrameLayout
            android:id="@+id/qs_frame"
            android:layout="@layout/qs_panel"
            android:layout_width="@dimen/qs_panel_width"
            android:layout_height="0dp"
            android:clipToPadding="false"
            android:clipChildren="false"
            systemui:viewType="com.android.systemui.plugins.qs.QS"
            systemui:layout_constraintStart_toStartOf="parent"
            systemui:layout_constraintEnd_toEndOf="parent"
            systemui:layout_constraintTop_toTopOf="parent"
            systemui:layout_constraintBottom_toBottomOf="parent"
        />

        ...

      #通知列表的滚动容器 
       <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
            android:id="@+id/notification_stack_scroller"
            android:layout_marginTop="@dimen/notification_panel_margin_top"
            android:layout_width="@dimen/notification_panel_width"
            android:layout_height="match_parent"
            android:layout_marginBottom="@dimen/close_handle_underlap"
            android:importantForAccessibility="no"
            systemui:layout_constraintStart_toStartOf="parent"
            systemui:layout_constraintEnd_toEndOf="parent"
        />

        ...
    </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>

    ...
</com.android.systemui.statusbar.phone.NotificationPanelView>

4. 触发面板显示:makeExpandedVisible()

在触摸事件处理阶段,当手势确认要展开面板时,PhoneStatusBarView 的 onPanelPeeked() 方法会被调用。该方法进而调用 StatusBar 的 makeExpandedVisible()  方法。这个方法的核心作用,就是将整个下拉面板的窗口(NotificationShadeWindowView)设置为可见状态。PhoneStatusBarView是状态栏status_bar.xml的根布局容器。

//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
public boolean onTouchEvent(MotionEvent event) {
    //mBar是StatusBar
    boolean barConsumedEvent = mBar.interceptTouchEvent(event);
    ...
    //如果 StatusBar 消费了事件,返回 true;否则交给父类处理(默认的 View 触摸处理)
    return barConsumedEvent || super.onTouchEvent(event);
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
public boolean onTouchEvent(MotionEvent event) {
    //检查面板是否启用
    if (!panelEnabled()) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
        }
        return false; //面板被禁用,不处理事件
    }

    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        final PanelViewController panel = mPanel;
        if (panel == null) {
            // 面板不存在,消费掉这个手势
            return true;
        }
        // 检查面板是否启用
        boolean enabled = panel.isEnabled();
        
        if (!enabled) {
            // 面板被禁用,消费掉这个手势
            return true;
        }
    }
    //将事件转发给面板的视图
    //mPanel = PanelViewController是抽象类,实现类NotificationPanelViewController
    //`mPanel.getView()`返回的是`NotificationPanelView`
    return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
}

mPanel.getView() 返回的是 NotificationPanelView。当调用它的 dispatchTouchEvent(event) 时:

  1. Android 框架会检查 View 是否设置了 OnTouchListener
  2. 如果设置了,会先调用 OnTouchListener.onTouch()
  3. 由于 TouchHandler 被注册为 OnTouchListener,所以 TouchHandler.onTouch() 被调用
  4. 在 TouchHandler.onTouch() 内部,会进一步调用 NotificationPanelViewController 的具体处理逻辑。

TouchHandler 是什么

PanelViewController.TouchHandler 是一个接口,它继承自 View.OnTouchListener,并扩展了 onInterceptTouchEvent 方法。在 NotificationPanelViewController 中,通过 createTouchHandler() 方法创建具体的实现。并通过 mView.setOnTouchListener(touchHandler) 将其注册到 NotificationPanelView 上。

//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
public boolean onInterceptTouchEvent(MotionEvent event) {
    //PanelViewController.TouchHandler mTouchHandler
    return mTouchHandler.onInterceptTouchEvent(event);
}


//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
public PanelViewController(PanelView view,
        FalsingManager falsingManager, DozeLog dozeLog,
        KeyguardStateController keyguardStateController,
        ...) {
    ...
    mView.setOnTouchListener(createTouchHandler());
}

protected TouchHandler createTouchHandler() {
    return new TouchHandler();
}


public class TouchHandler implements View.OnTouchListener {
    public boolean onInterceptTouchEvent(MotionEvent event) {
        ...
        return (mView.getVisibility() != View.VISIBLE);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        ...
        return !mGestureWaitForTouchSlop || mTracking;
    }
}

5. 填充通知内容:NotificationContentInflater

面板可见后,就需要向 NotificationStackScrollLayout 中填充具体的通知内容了。这个构建过程由 NotificationContentInflater 类负责。

  • 收到通知:当应用发出通知,NotificationManager 服务会通知 SystemUI。
  • 开始构建:SystemUI 收到后,会调用 NotificationContentInflater 的 inflateNotificationViews() 方法。
  • 创建视图:该方法会根据通知的样式(如标准、大图等),加载不同的布局文件,为每条通知创建独立的视图对象。
  • 添加到列表:最后,这些构建好的通知视图会被添加到 NotificationStackScrollLayout 中,呈现在你眼前。