StatusBar的创建与图标控制 分析了状态栏上系统图标区图标的控制流程,本文承接上文,分析状态栏上通知区图标的控制流程。
本文是基于Android 9.0源码。
初始化通知图标区
由前文可知,状态栏视图的创建是在 StatusBar#makeStatusBarView()
中。同时,通知图标区的初始化也是在这里
protected void makeStatusBarView() {
// 1. 创建通知图标控制器
mNotificationIconAreaController = SystemUIFactory.getInstance()
.createNotificationIconAreaController(context, this);
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
// 2. 用通知图标控制器,初始化通知图标区
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
// ...
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
}
NotificationIconAreaController
是通知图标控制器,控制着状态栏上通知图标区所有的图标。在它的构造函数中,会调用initializeNotificationAreaViews()
方法,实例化一个布局,这个布局就是状态栏通知图标的父布局。
protected void initializeNotificationAreaViews(Context context) {
// ...
LayoutInflater layoutInflater = LayoutInflater.from(context);
// 加载通知图标的父布局
mNotificationIconArea = inflater.inflate(R.layout.notification_icon_area, null);
// 真正存放图标的区域
mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
R.id.notificationIcons);
// ...
}
notification_icon_area.xml
布局如下
<com.android.keyguard.AlphaOptimizedLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notification_icon_area_inner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false">
<com.android.systemui.statusbar.phone.NotificationIconContainer
android:id="@+id/notificationIcons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:clipChildren="false"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
NotificationIconAreaController
实例化通知图标的父View, 然后在CollapsedStatusBarFragment
创建状态栏时,调用 CollapsedStatusBarFragment#initNotificationIconArea()
方法把刚创建出来的通知图标的父View,添加到状态栏上的通知图标容器中。
public void initNotificationIconArea(NotificationIconAreaController
notificationIconAreaController) {
// 获取状态栏上的通知图标容器
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
// 添加通知图标控制器中获取的通知图标的父View
mNotificationIconAreaInner =
notificationIconAreaController.getNotificationInnerAreaView();
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
// 设置通知图标区域可见
showNotificationIconArea(false);
}
监听通知更新
现在已经知道通知图标是由 NotificationIconAreaController
控制的,但是要显示通知图标,首先还得获取通知信息,这就需要监听 NotificationManagerService
。这一监听步骤就是从 StatusBar#start()
中开始
public void start() {
// NotificationListener是用来监听通知的
mNotificationListener = Dependency.get(NotificationListener.class);
// 向NotificationManagerService注册一个服务,用于获取通知更新消息
mNotificationListener.setUpWithPresenter(this, mEntryManager);
}
NotificationListener#setUpWithPresenter()
会调用父类的 registerAsSystemService()
向通知的服务端 NotificationManagerService
注册一个"服务"
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
// NotificationListenerWrapper是用于向 NotificationManagerService 注册的服务
if (mWrapper == null) {
mWrapper = new NotificationListenerWrapper();
}
mSystemContext = context;
INotificationManager noMan = getNotificationInterface();
mHandler = new MyHandler(context.getMainLooper());
mCurrentUser = currentUser;
// 向 NotificationManagerService 注册服务
noMan.registerListener(mWrapper, componentName, currentUser);
}
当这个服务注册成功后,通知的服务端 NotificationManagerService
会保存这个服务,并通过 NotificationListenerWrapper#onListenerConnected()
回调客户端,通知客户端服务已经建立连接,并且还会附带客户端可见的所有通知信息。客户端会根据这些通知信息,显示这些通知信息,例如可以在状态栏上显示通知图标,也可以在下拉通知面板中显示通知信息。
获取新通知
可能我们更关心的的是,当一条新通知来临时,SystemUI 如何显示通知图标,以及在下拉通知面板中显示通知信息。那么接下来,就分析这一过程中通知图标如何被添加到状态栏上,并且还附带着会分析到下拉通知面板中,通知信息如何被添加的。
当通知服务端 NotificationManagerService
收到一条新通知信息后,它会通过 NotificationListenerWrapper#onNotificationPosted()
通知 SystemUI
protected class NotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
// 获取通知
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
return;
}
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
synchronized (mLock) {
applyUpdateLocked(update);
if (sbn != null) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = mRankingMap;
// 通过onNotificationPosted()处理
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
args).sendToTarget();
} else {
}
}
}
}
onNotificationPosted(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update)
的第一个参数代表新的通知信息,第二个参数代表 SystemUI 可见的所有通知信息。
NotificationListenerWrapper
获取到这些通知信息后,会调用子类的 onNotificationPosted()
来处理,这个方法由 NotificationListener
实现
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mPresenter.getHandler().post(() -> {
String key = sbn.getKey();
// 判断是否已经有了这个通知
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
// ...
if (isUpdate) {
// 更新通知
mEntryManager.updateNotification(sbn, rankingMap);
} else {
// 添加通知
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
NotificationListener#onNotificationPosted()
会把新来的通知信息交给 NotificationEntryManager
处理。
处理通知
NotificationEntryManager
是用来管理通知的,例如添加,删除,更新通知,而且还包括通知视图的加载,等等操作。这里通过 NotificationEntryManager#addNotification()
来分析它是如何实现通知的添加操作。
NotificationEntryManager#addNotification()
内部是由 addNotificationInternal()
实现的,它会调用 NotificationEntryManager#createNotificationViews()
来为这条通知创建通知图标以及通知视图的容器。
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
throws InflationException {
NotificationData.Entry entry = new NotificationData.Entry(sbn);
// 1. 创建图标
entry.createIcons(mContext, sbn);
// 2. 实例化一个通知视图的容器
inflateViews(entry, mListContainer.getViewParentForNotification(entry));
return entry;
}
private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
final StatusBarNotification sbn = entry.notification;
if (entry.row != null) {
// ...
} else {
// 使用异步线程加载视图,然后回调
new RowInflaterTask().inflate(mContext, parent, entry,
// 这个lambda表达式就是视图加载完成的回调,参数row就是加载完成的视图
row -> {
// 为通知视图绑定信息
bindRow(entry, pmUser, sbn, row);
// entry根据数据创建真正的通知视图,并添加到row中,然后进行回调
// 这个回调,是在上面的bindRow()中设置的
updateNotification(entry, pmUser, sbn, row);
});
}
}
RowInflaterTask
使用一个异步线程,通过 LayoutInflater
加载 status_bar_notification_row.xml
布局作为通知视图的容器。注意,此时只是创建通知视图的父View而已,还没有创建真正显示通知信息的View。
RowInflaterTask#inflate()
加载完通知视图的父View后,就会通过最后一个参数进行回调,在这个回调才会真正创建显示通知信息的View。
回调中,首先调用 NotificationEntryManager#bindRow()
来为这个通知容器绑定各种信息,其中与创建通知View相关的绑定如下
private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
// ...
// 设置通知视图加载完成的回调,这个回调方法是onAsyncInflationFinished()
row.setInflationCallback(this);
// ...
}
与此同时,第二个回调NotificationEntryManager#updateNotification()
就触发了通知视图的加载
protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
// ...
// entry保存通知视图的父View
entry.row = row;
// ...
// 用数据创建通知视图,并添加到参数父View中
row.updateNotification(entry);
}
ExpandableNotificationRow#updateNotification()
会根据各种参数来决定创建哪些种类的通知视图,并把这个通知视图添加到刚才创建的父View中,最后会通过 onAsyncInflationFinished()
进行回调,而这个回调就是刚才在 NotificationEntryManager#bindRow()
设置的,由 NotificationEntryManager
实现。
public void onAsyncInflationFinished(NotificationData.Entry entry) {
mPendingNotifications.remove(entry.key);
boolean isNew = mNotificationData.get(entry.key) == null;
if (isNew && !entry.row.isRemoved()) {
// 保存通知,并通过updateNotificationViews()回调,通知StatusBar更新通知图标和通知视图
addEntry(entry);
} else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
}
}
NotificationEntryManager#addEntry()
会把新通知进行保存,然后对通知集合进行过滤和排序,最后通过 updateNotificationViews()
回调来通知 StatusBar
更新通知图标以及通知视图。
public void updateNotificationViews() {
// ...
// 添加到通知面板的通知容器中
mViewHierarchyManager.updateNotificationViews();
// ...
// 更新状态栏上,通知图标区的图标
mNotificationIconAreaController.updateNotificationIcons();
}
StatusBar
接收到更新通知视图的信息后,首先会通过 NotificationViewHierarchyManager#updateNotificationViews()
把新通知添加到下拉通知面板的通知容器中,然后再通过 NotificationIconAreaController
更新状态栏上的通知图标。
至此,状态栏上通知图标的控制流程已经分析完毕,那么用一副图总结下这个流程