本文是基于Android 10源码分析的。
SystemUI之状态图标控制 分析了状态栏上状态图标(例如 wifi, bt)的控制流程,比较简单。本文来分析下状态栏上通知图标的控制流程,主要分析当一个新通知来临时,新通知的图标是如何一步步显示到状态上的。
通知图标控制器
从SystemUI之状态图标控制可知,状态图标是由一个叫StatusBarIconController
接口控制显示的,而通知图标区域也有一个控制器,叫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);
// ...
}
这里加载了notification_icon_are.xml
布局,来看下这个布局
ID为notificationIcons
的NotificationIconContainer
就是通知图标容器,对应于上面代码的mNotificationIcons
变量。
初始化通知图标区域
既然NotificationIconAreaController
自己创建了通知图标容器,那么就需要加入到状态栏视图中,这个动作在创建状态栏视图后完成的
// packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
// ...
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#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向ActivityManagerService
注册一个"服务"(一个Binder)。
这个"服务"就相当于客户端SystemUI在服务端ActivityManagerService
注册的一个回调。当有通知来临的时候,就会通过这个"服务"通知SystemUI。
这个注册是在StatusBar#start()
中完成的。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public void start() {
// 向通知服务端注册一个"服务",用于接收通知信息的回调
mNotificationListener = Dependency.get(NotificationListener.class);
mNotificationListener.registerAsSystemService();
}
来看下这个注册的实现
// frameworks/base/core/java/android/service/notification/NotificationListenerService.java
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
if (mWrapper == null) {
// 这就是要注册的Binder,也就一个回调
mWrapper = new NotificationListenerWrapper();
}
INotificationManager noMan = getNotificationInterface();
// 向通知的服务端注册回调
noMan.registerListener(mWrapper, componentName, currentUser);
}
这个"服务"就是NotificationListenerWrapper
。
注册成功后,就会回调
NotificationListenerWrapper#NotificationListenerWrapper()
方法,并会附带所有的通知信息。
显示通知图标
当一条新的通知来临的时候,通知的服务端会通过NotificationListenerWrapper#onNotificationPosted()
进行回调,而最终会调用NotificationListener#onNotificationPosted()
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
// 在主线程中进行更新
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
if (isUpdate) {
// 更新通知操作
mEntryManager.updateNotification(sbn, rankingMap);
} else {
// 添加新通知操作
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
这里讨论添加新通知的操作,它调用的是NotificationManager#addNotification()
方法,而内部是通过addNotificationInternal()
实现的
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
// ...
// NotificationEntry就代表一个通知实例
NotificationEntry entry = new NotificationEntry(notification, ranking);
// 异步加载视图,并绑定通知信息,由NotificationRowBinderImpl实现
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
REASON_CANCEL));
// ...
}
首先为通知创建一个NotificationEntry
实例,然后再通过NotificationRowBinderImpl#inflateViews()
加载通知视图,绑定通知信息,并在通知栏添加通知视图,以及在状态栏添加通知图标。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
public void inflateViews(
NotificationEntry entry,
Runnable onDismissRunnable)
throws InflationException {
// ...
if (entry.rowExists()) {
} else {
// 给通知创建图标
entry.createIcons(mContext, sbn);
// 异步加载通知视图
new RowInflaterTask().inflate(mContext, parent, entry,
// 加载完成的回调,这里的加载指的仅仅是一个空视图
row -> {
// 绑定监听事件和回调
bindRow(entry, pmUser, sbn, row, onDismissRunnable);
// 在视图上更新通知信息
updateNotification(entry, pmUser, sbn, row);
});
}
}
RowInflaterTask#inflate()
会使用status_bar_notification_row.xml
布局创建一个通知视图,但是并没有把它加入到父容器中,更没有把把通知信息更新到视图中,这些工作都是在回调中完成的。
第一个回调bindRow()
,会为视图绑定各种监听事件以及回调
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
private void bindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row,
Runnable onDismissRunnable) {
// ...
// 刚才只是创建了视图,并没有绑定数据,这里就是设置绑定数据后的回调,这个回调是由NotificationEntryManager实现
row.setInflationCallback(mInflationCallback);
// ...
}
这里只列出了与本文分析相关的回调,这个回调是在视图与通知信息绑定后的回调。
第二个回调updateNotification()
,用数据更新视图,更新完成后就会进行回调刚才绑定的回调事件,而这个回调是由NotificationEntryManager#onAsyncInflationFinished()
实现的
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
mPendingNotifications.remove(entry.key);
if (!entry.isRowRemoved()) {
boolean isNew = mNotificationData.get(entry.key) == null;
if (isNew) {
// ...
if (mPresenter != null) {
// 显示视图
// 这个由StatusBarNotificationPresenter实现
mPresenter.updateNotificationViews();
}
// ...
} else {
}
}
}
数据已经准备完毕,那么现在就是要显示视图了,这个视图包括通知栏里的通知,以及状态栏时的通知图标。这个由StatusBarNotificationPresenter#updateNotificationViews()
实现
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
public void updateNotificationViews() {
// ...
// 1\. 把通知视图添加到通知面版的通知栏中
mViewHierarchyManager.updateNotificationViews();
// 这里不仅仅更新了通知面版的通知视图,也更新了状态栏的通知图标
mNotificationPanel.updateNotificationViews();
// ...
}
public void updateNotificationViews() {
// ...省略更新通知栏的相关视图的代码
updateShowEmptyShadeView();
// 2\. 调用mIconAreaController更新了状态栏通知图标
// 其实就是调用 mIconAreaController.updateNotificationIcons();
mNotificationStackScroller.updateIconAreaViews();
}
首先是往通知栏里添加通知视图,然后再更新状态栏视图。现在只看下如何向状态栏添加通知图标的,它最终是由NotificationIconAreaController#updateIconsForLayout()
实现的
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
private void updateIconsForLayout(Function function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
boolean hideCenteredIcon) {
// toShow保存即将显示的图标
ArrayList toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
// 过滤通知,并保存需要显示的通知图标
for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
// 获取一个通知视图
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
hideRepliedMessages, hideCurrentMedia, hideCenteredIcon)) {
// 获取图标
StatusBarIconView iconView = function.apply(ent);
if (iconView != null) {
toShow.add(iconView);
}
}
}
}
// ...
// 把需要显示的图标添加到hostLayout中
final FrameLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < toShow.size(); i++) {
StatusBarIconView v = toShow.get(i);
// The view might still be transiently added if it was just removed and added again
hostLayout.removeTransientView(v);
if (v.getParent() == null) {
if (hideDismissed) {
v.setOnDismissListener(mUpdateStatusBarIcons);
}
hostLayout.addView(v, i, params);
}
}
// ...
}
纵观整个过程,它的原理是根据通知栏的通知视图,来获取通知图标,然后经过一系列的过滤过程,最终把图标添加到状态栏通知图标容器中。
结束
本文简要分析了通知图标的显示流程,其中穿插提到了通知栏的通知视图的添加过程。掌握了大纲,就可以对细节进行考究,甚至对SystemUI进行定制。
不管好与不好,动动你的小手为小编点个赞吧,你的点赞将是小编最大的动力。