SystemUI之狀態圖標控制 分析了狀態欄上狀態圖標(例如 wifi, bt)的控制流程,比較簡單。本文來分析下狀態欄上通知圖標的控制流程,主要分析當一個新通知來臨時,新通知的圖標是如何一步步顯示到狀態上的。java
從SystemUI之狀態圖標控制可知,狀態圖標是由一個叫StatusBarIconController
接口控制顯示的,而通知圖標區域也有一個控制器,叫NotificationIconAreaController
(它不是一個接口)。android
在NotificationIconAreaController
的構造函數中會調用以下方法來建立通知圖標的容器app
// 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
佈局,來看下這個佈局異步
<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
就是通知圖標容器,對應於上面代碼的mNotificationIcons
變量。ide
既然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)。post
這個"服務"就至關於客戶端SystemUI在服務端ActivityManagerService
註冊的一個回調。當有通知來臨的時候,就會經過這個"服務"通知SystemUI。ui
這個註冊是在StatusBar#start()
中完成的。spa
// 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<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);
}
}
// ...
}
複製代碼
縱觀整個過程,它的原理是根據通知欄的通知視圖,來獲取通知圖標,而後通過一系列的過濾過程,最終把圖標添加到狀態欄通知圖標容器中。
本文簡要分析了通知圖標的顯示流程,其中穿插提到了通知欄的通知視圖的添加過程。掌握了大綱,就能夠對細節進行考究,甚至對SystemUI進行定製。