1. SystemUI 服务启动与窗口创建
SystemUI 是一个系统应用,由 SystemServer 进程负责启动,入口是 SystemUIApplication。
启动后,核心服务 StatusBar 的 start() 方法会被调用。在这里,它会执行 createAndAddWindows() 方法,通过 WindowManager 为状态栏和通知栏创建独立的系统窗口。
scss
//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 添加到屏幕上。
ini
//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。
ini
//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的根布局容器。
csharp
//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) 时:
- Android 框架会检查 View 是否设置了
OnTouchListener - 如果设置了,会先调用
OnTouchListener.onTouch() - 由于
TouchHandler被注册为OnTouchListener,所以TouchHandler.onTouch()被调用 - 在
TouchHandler.onTouch()内部,会进一步调用NotificationPanelViewController的具体处理逻辑。
TouchHandler 是什么
PanelViewController.TouchHandler 是一个接口,它继承自 View.OnTouchListener,并扩展了 onInterceptTouchEvent 方法。在 NotificationPanelViewController 中,通过 createTouchHandler() 方法创建具体的实现。并通过 mView.setOnTouchListener(touchHandler) 将其注册到 NotificationPanelView 上。
typescript
//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中,呈现在你眼前。