一、StatusBar简介
Statusbar包含导航栏(NavigationBar, 位于左侧、右侧或者底部)和状态栏(StatusBar, 位于顶部, 可下拉)两个部分。
StatusBar顶部状态栏由三部分组成:
1、最左边的一部分显示运营商,时间,通知图标。
2、最右边的一部分显示系统图标,它由状态图标(例如 wifi ,bt)和电池图标组成。
3、中间还有一块区域。
从上图可以比较直观的看出来顶层树是super_status_bar,之后会走两个分支status_bar_container和status_bar_expanded,status_bar_container这个分支主要呈现的是状态栏界面,状态栏细分左边和右边,左边是通知栏,右边是系统功能的状态图标显示,status_bar_expanded这个分支主要呈现的下拉菜单界面,其实下拉菜单中又分快捷图标和短信通知栏。
二、StatuBar的创建
StatusBar也是继承SystemUI,启动流程和SystemUI一致。并在start的时候添加创建StatusBar相关的view。
我们从StatusBar的start()方法开始看。
frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java
@Override public void start() { // 省略部分代码...... // 创建整个SystemUI视图并添加到WindowManager中 createAndAddWindows();//这个重点方法,创建相关的视图 // 省略部分代码...... } public void createAndAddWindows(@Nullable RegisterStatusBarResult result) { // 创建整个SystemUI视图 makeStatusBarView(result); // 把视图添加到Window中 mStatusBarWindowController = Dependency.get(StatusBarWindowController.class); //管理状态栏,导航栏、面板的状态切换 mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight()); }
根据上面代码可知,视图的创建在makeStatusbarView(result)方法中;
//创建状态栏,即初始化通知图标区域 protected void makeStatusBarView(RegisterStatusBarResult result) { //1.初始化资源文件:导航栏高度 状态栏高度 通知面板位置和高度等 final Context context = mContext; updateDisplaySize(); // populates mDisplayMetrics updateResources(); updateTheme(); //加载layout文件super_status_bar,mStatusBarWindow inflateStatusBarWindow(context); // ... FragmentHostManager.get(mStatusBarWindow) .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment; // 把控制器中的通知容器加入到状态栏的容器中 statusBarFragment.initNotificationIconArea(mNotificationIconAreaController); // ... }).getFragmentManager() .beginTransaction() // CollapsedStatusBarFragment实现了状态栏的添加 .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG) .commit(); // ... }
注意在这里添加了CollapsedStatusBarFragment,这个CollapsedStatusBarFragment就是作为加载状态栏的Fragment。
在看 CollapsedStatusBarFragment 之前,我们先看看 NotificationIconAreaController(通知图标区域控制器),在 NotificationIconAreaController 的构造函数中会调用如下方法来创建通知图标的容器,即初始化布局。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java protected void initializeNotificationAreaViews(Context context) { // ... LayoutInflater layoutInflater = LayoutInflater.from(context); // 实例化通知图标区域视图 mNotificationIconArea = inflater.inflate(R.layout.notification_icon_area, null); // 这个才是真正存放通知图标的父容器 mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons); // ... }
这个类加载的是 R.layout.notification_icon_are.xml 布局。这里先回到 makeStatusBarView(RegisterStatusBarResult result) 方法,我们看 statusBarFragment.initNotificationIconArea(mNotificationIconAreaController) ,这句话的作用就是 将 NotificationIconAreaController 自己创建的通知图标容器,加入到状态栏视图中。
接下来继续看 CollapsedStatusBarFragment,在看之前我们应该带着一个问题去看:这些图标都是动态添加的,是怎么被添加和移除的。
由 CollapsedStatusBarFragment onCreateView() 可知,加载的布局是 R.layout.status_bar。
在该布局里包含了这么几个关键的视图:
1、@+id/status_bar_contents 这个LinearLayout,里面包含了@+id/status_bar_left_side ,从名字来看就知道是状态栏左边部分。这个状态栏左边部分包含了时钟@+id/clock和短信通知@+id/notification_icon_area。
2、@+id/system_icon_area这个也是一个LinearLayout包含了
@layout/system_icons,这部分就是状态栏右边部分,里面包含了电池图标和系统状态图标等等。
由 statusBarFragment.initNotificationIconArea(mNotificationIconAreaController) 可知,会进入到CollapsedStatusBarFragment 的 initNotificationIconArea 方法里
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java public void initNotificationIconArea(NotificationIconAreaController notificationIconAreaController) { // 通知图标区域 ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area); // 这个才是通知图标的父容器 mNotificationIconAreaInner = notificationIconAreaController.getNotificationInnerAreaView(); //移除自己,再添加自己 if (mNotificationIconAreaInner.getParent() != null) { ((ViewGroup) mNotificationIconAreaInner.getParent()) .removeView(mNotificationIconAreaInner); } // 把通知图标父容器添加到通知图标区域里 notificationIconArea.addView(mNotificationIconAreaInner); // 省略处理中心图标区域的代码 // 这里其实显示了通知图标区域和中心图标区域 showNotificationIconArea(false); }
三、监听通知的服务端
当一条新通知发送后,它会存储到通知服务端,也就是NotificationManagerService,那么SystemUI是如何知道新通知来临的。这就需要SystemUI向NotificationManagerService注册一个"服务"(一个Binder)。
这个"服务"就相当于客户端 SystemUI 在服务端 NotificationManagerService 注册的一个回调。当有通知来临的时候,就会通过这个"服务"通知SystemUI。通过 notificationListener.registerAsSystemService(),进行注册。
可以来看下这个注册的实现:
/** * Directly register this service with the Notification Manager. * * <p>Only system services may use this call. It will fail for non-system callers. * Apps should ask the user to add their listener in Settings. * * @param context Context required for accessing resources. Since this service isn't * launched as a real Service when using this method, a context has to be passed in. * @param componentName the component that will consume the notification information * @param currentUser the user to use as the stream filter * @hide * @removed */ @SystemApi public void registerAsSystemService(Context context, ComponentName componentName, int currentUser) throws RemoteException { if (mWrapper == null) { // 这就是要注册的Binder,也就一个回调 mWrapper = new NotificationListenerWrapper(); } mSystemContext = context; INotificationManager noMan = getNotificationInterface(); // ... // 向通知的服务端注册回调 noMan.registerListener(mWrapper, componentName, currentUser); }
SystemUI注册的这个“服务”其实就是:NotificationListenerWithPlugins。
注册成功后,就会回调NotificationListenerWithPlugins中的相关方法,并会附带所有的通知信息。
四、通知图标的显示
当一条新的通知来临的时候,通知的服务端会通过NotificationListenerWithPlugins中的onNotificationPosted()进行回调,而最终会调用NotificationListener中的onNotificationPosted()。
@Override public void onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) { // 省略部分代码... // 进行回调 handler.onNotificationPosted(sbn, rankingMap); // 省略部分代码 ... } }
这个回调,将调到 NotificationEntryManager 类里的 onNotificationPosted()方法。
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey()); if (isUpdateToInflatedNotif) { updateNotification(sbn, rankingMap); } else { addNotification(sbn, rankingMap); } }
这里只关注 addNotification(sbn, rankingMap) ,而它内部时调用 addNotificationInternal() 方法实现的。
private void addNotificationInternal( StatusBarNotification notification, RankingMap rankingMap) throws InflationException { // 省略部分代码 ... NotificationEntry entry = mPendingNotifications.get(key); // 省略部分代码 ... // Construct the expanded view. if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mNotificationRowBinderLazy.get() .inflateViews( entry, () -> performRemoveNotification(notification, REASON_CANCEL), mInflationCallback); } // 省略部分代码 ... }
首先为通知创建一个NotificationEntry实例,然后再通过NotificationRowBinderImpl中的inflateViews()加载通知视图,绑定通知信息,并在通知栏添加通知视图,以及在状态栏添加通知图标。
到此获取到了数据、图标后面就是更新视图进行显示了,由 NotificationRowBinderImpl#inflateViews → NotificationEntryManager#onAsyncInflationFinished → StatusBarNotificationPresenter#updateNotificationViews
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java public void updateNotificationViews() { // ... // 把通知视图添加到通知面版的通知栏中 mViewHierarchyManager.updateNotificationViews(); // 这里不仅仅更新了通知面版的通知视图,也更新了状态栏的通知图标 mNotificationPanel.updateNotificationViews(reason); // ... }
最终会调用 NotificationIconAreaController#updateNotificationIcons()进行视图更新。