本文是基于Android 10源碼分析的。
SystemUI之狀態(tài)圖標控制 分析了狀態(tài)欄上狀態(tài)圖標(例如 wifi, bt)的控制流程,比較簡單。本文來分析下狀態(tài)欄上通知圖標的控制流程,主要分析當一個新通知來臨時幕帆,新通知的圖標是如何一步步顯示到狀態(tài)上的。
通知圖標控制器
從SystemUI之狀態(tài)圖標控制可知赖条,狀態(tài)圖標是由一個叫StatusBarIconController
接口控制顯示的失乾,而通知圖標區(qū)域也有一個控制器,叫NotificationIconAreaController
(它不是一個接口)纬乍。
在NotificationIconAreaController
的構(gòu)造函數(shù)中會調(diào)用如下方法來創(chuàng)建通知圖標的容器
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
protected void initializeNotificationAreaViews(Context context) {
// ...
LayoutInflater layoutInflater = LayoutInflater.from(context);
// 實例化通知圖標區(qū)域視圖
mNotificationIconArea = inflater.inflate(R.layout.notification_icon_area, null);
// 這個才是真正存放通知圖標的父容器
mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
// ...
}
這里加載了notification_icon_are.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>
ID為notificationIcons
的NotificationIconContainer
就是通知圖標容器,對應(yīng)于上面代碼的mNotificationIcons
變量仿贬。
初始化通知圖標區(qū)域
既然NotificationIconAreaController
自己創(chuàng)建了通知圖標容器纽竣,那么就需要加入到狀態(tài)欄視圖中,這個動作在創(chuàng)建狀態(tài)欄視圖后完成的
// 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;
// 把控制器中的通知容器加入到狀態(tài)欄的容器中
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
// ...
}).getFragmentManager()
.beginTransaction()
// CollapsedStatusBarFragment實現(xiàn)了狀態(tài)欄的添加
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
}
調(diào)用的就是CollapsedStatusBarFragment#initNotificationIconArea()
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
public void initNotificationIconArea(NotificationIconAreaController
notificationIconAreaController) {
// 通知圖標區(qū)域
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
// 這個才是通知圖標的父容器
mNotificationIconAreaInner =
notificationIconAreaController.getNotificationInnerAreaView();
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
// 把通知圖標父容器添加到通知圖標區(qū)域里
notificationIconArea.addView(mNotificationIconAreaInner);
// 省略處理中心圖標區(qū)域的代碼
// 這里其實顯示了通知圖標區(qū)域和中心圖標區(qū)域
showNotificationIconArea(false);
}
監(jiān)聽通知的服務(wù)端
當一條新通知發(fā)送后茧泪,它會存儲到通知服務(wù)端蜓氨,也就是NotificationManagerService
,那么SystemUI是如何知道新通知來臨的队伟。這就需要SystemUI向ActivityManagerService
注冊一個"服務(wù)"(一個Binder)穴吹。
這個"服務(wù)"就相當于客戶端SystemUI在服務(wù)端ActivityManagerService
注冊的一個回調(diào)。當有通知來臨的時候嗜侮,就會通過這個"服務(wù)"通知SystemUI港令。
這個注冊是在StatusBar#start()
中完成的。
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public void start() {
// 向通知服務(wù)端注冊一個"服務(wù)"棘钞,用于接收通知信息的回調(diào)
mNotificationListener = Dependency.get(NotificationListener.class);
mNotificationListener.registerAsSystemService();
}
來看下這個注冊的實現(xiàn)
// frameworks/base/core/java/android/service/notification/NotificationListenerService.java
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
if (mWrapper == null) {
// 這就是要注冊的Binder缠借,也就一個回調(diào)
mWrapper = new NotificationListenerWrapper();
}
INotificationManager noMan = getNotificationInterface();
// 向通知的服務(wù)端注冊回調(diào)
noMan.registerListener(mWrapper, componentName, currentUser);
}
這個"服務(wù)"就是NotificationListenerWrapper
。
注冊成功后宜猜,就會回調(diào)
NotificationListenerWrapper#NotificationListenerWrapper()
方法,并會附帶所有的通知信息硝逢。
顯示通知圖標
當一條新的通知來臨的時候姨拥,通知的服務(wù)端會通過NotificationListenerWrapper#onNotificationPosted()
進行回調(diào),而最終會調(diào)用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);
}
});
}
}
這里討論添加新通知的操作渠鸽,它調(diào)用的是NotificationManager#addNotification()
方法叫乌,而內(nèi)部是通過addNotificationInternal()
實現(xiàn)的
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
// ...
// NotificationEntry就代表一個通知實例
NotificationEntry entry = new NotificationEntry(notification, ranking);
// 異步加載視圖,并綁定通知信息徽缚,由NotificationRowBinderImpl實現(xiàn)
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
REASON_CANCEL));
// ...
}
首先為通知創(chuàng)建一個NotificationEntry
實例憨奸,然后再通過NotificationRowBinderImpl#inflateViews()
加載通知視圖,綁定通知信息凿试,并在通知欄添加通知視圖排宰,以及在狀態(tài)欄添加通知圖標似芝。
// 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 {
// 給通知創(chuàng)建圖標
entry.createIcons(mContext, sbn);
// 異步加載通知視圖
new RowInflaterTask().inflate(mContext, parent, entry,
// 加載完成的回調(diào),這里的加載指的僅僅是一個空視圖
row -> {
// 綁定監(jiān)聽事件和回調(diào)
bindRow(entry, pmUser, sbn, row, onDismissRunnable);
// 在視圖上更新通知信息
updateNotification(entry, pmUser, sbn, row);
});
}
}
RowInflaterTask#inflate()
會使用status_bar_notification_row.xml
布局創(chuàng)建一個通知視圖板甘,但是并沒有把它加入到父容器中党瓮,更沒有把把通知信息更新到視圖中,這些工作都是在回調(diào)中完成的盐类。
第一個回調(diào)bindRow()
寞奸,會為視圖綁定各種監(jiān)聽事件以及回調(diào)
// 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) {
// ...
// 剛才只是創(chuàng)建了視圖,并沒有綁定數(shù)據(jù)在跳,這里就是設(shè)置綁定數(shù)據(jù)后的回調(diào)枪萄,這個回調(diào)是由NotificationEntryManager實現(xiàn)
row.setInflationCallback(mInflationCallback);
// ...
}
這里只列出了與本文分析相關(guān)的回調(diào),這個回調(diào)是在視圖與通知信息綁定后的回調(diào)猫妙。
第二個回調(diào)updateNotification()
呻引,用數(shù)據(jù)更新視圖,更新完成后就會進行回調(diào)剛才綁定的回調(diào)事件吐咳,而這個回調(diào)是由NotificationEntryManager#onAsyncInflationFinished()
實現(xiàn)的
// 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實現(xiàn)
mPresenter.updateNotificationViews();
}
// ...
} else {
}
}
}
數(shù)據(jù)已經(jīng)準備完畢逻悠,那么現(xiàn)在就是要顯示視圖了,這個視圖包括通知欄里的通知韭脊,以及狀態(tài)欄時的通知圖標童谒。這個由StatusBarNotificationPresenter#updateNotificationViews()
實現(xiàn)
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
public void updateNotificationViews() {
// ...
// 1\. 把通知視圖添加到通知面版的通知欄中
mViewHierarchyManager.updateNotificationViews();
// 這里不僅僅更新了通知面版的通知視圖,也更新了狀態(tài)欄的通知圖標
mNotificationPanel.updateNotificationViews();
// ...
}
public void updateNotificationViews() {
// ...省略更新通知欄的相關(guān)視圖的代碼
updateShowEmptyShadeView();
// 2\. 調(diào)用mIconAreaController更新了狀態(tài)欄通知圖標
// 其實就是調(diào)用 mIconAreaController.updateNotificationIcons();
mNotificationStackScroller.updateIconAreaViews();
}
首先是往通知欄里添加通知視圖沪羔,然后再更新狀態(tài)欄視圖〖⒁粒現(xiàn)在只看下如何向狀態(tài)欄添加通知圖標的,它最終是由NotificationIconAreaController#updateIconsForLayout()
實現(xiàn)的
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
boolean hideCenteredIcon) {
// toShow保存即將顯示的圖標
ArrayList<StatusBarIconView> 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);
}
}
// ...
}
縱觀整個過程琅豆,它的原理是根據(jù)通知欄的通知視圖,來獲取通知圖標篓吁,然后經(jīng)過一系列的過濾過程茫因,最終把圖標添加到狀態(tài)欄通知圖標容器中。
結(jié)束
本文簡要分析了通知圖標的顯示流程杖剪,其中穿插提到了通知欄的通知視圖的添加過程冻押。掌握了大綱,就可以對細節(jié)進行考究盛嘿,甚至對SystemUI進行定制洛巢。
不管好與不好,動動你的小手為小編點個贊吧次兆,你的點贊將是小編最大的動力稿茉。