基于9.0分析一下SystemUI接收到通知并显示icon的流程。
Notification Icon在SystemUI中的布局
SystemUI服务在启动时,会从配置项 config_systemUIServiceComponents(定义在config.xml)里遍历需要实例化的组件:
config.xml:
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.Dependency</item>
<item>com.android.systemui.util.NotificationChannels</item>
<item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
<item>com.android.systemui.keyguard.KeyguardViewMediator</item>
<item>com.android.systemui.recents.Recents</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.stackdivider.Divider</item>
<item>com.android.systemui.SystemBars</item>
<item>com.android.systemui.usb.StorageNotification</item>
<item>com.android.systemui.power.PowerUI</item>
<item>com.android.systemui.media.RingtonePlayer</item>
<item>com.android.systemui.keyboard.KeyboardUI</item>
<item>com.android.systemui.pip.PipUI</item>
<item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
<item>@string/config_systemUIVendorServiceComponent</item>
<item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
<item>com.android.systemui.LatencyTester</item>
<item>com.android.systemui.globalactions.GlobalActionsComponent</item>
<item>com.android.systemui.ScreenDecorations</item>
<item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
</string-array>
其中SystemBars在start()时会根据配置项config_statusBarComponent在不通的平台创建不通的StatusBar实例,
- 车机平台是com.android.systemui.statusbar.car.CarStatusBar
- 电视平台是com.android.systemui.statusbar.tv.TvStatusBar
- 手机平台是com.android.systemui.statusbar.phone.StatusBar
下面看StatusBar, 状态栏的根视图是mStatusBarWindow,对应的布局文件是super_status_bar.xml:
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<!-- This is the combined status bar / notification panel window. -->
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.android.systemui.statusbar.BackDropView
android:id="@+id/backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
sysui:ignoreRightInset="true"
>
<ImageView android:id="@+id/backdrop_back"
android:layout_width="match_parent"
android:scaleType="centerCrop"
android:layout_height="match_parent" />
<ImageView android:id="@+id/backdrop_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="invisible" />
</com.android.systemui.statusbar.BackDropView>
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
<FrameLayout
android:id="@+id/status_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
android:layout="@layout/car_fullscreen_user_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<include layout="@layout/brightness_mirror" />
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_in_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
其中值得关注的是:
@+id/status_bar_container 状态栏部分
@layout/status_bar_expanded 下拉栏部分
@+id/fullscreen_user_switcher_stub 全屏切换用户界面
状态栏部分status_bar_container会被CollapsedStatusBarFragment替换掉:
protected void makeStatusBarView() {
final Context context = mContext;
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
updateTheme();
inflateStatusBarWindow(context);
mStatusBarWindow.setService(this);
mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener());
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
mZenController.addCallback(this);
mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
this,
mNotificationPanel,
mStackScroller);
mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
key -> {
try {
mBarService.onNotificationSettingsViewed(key);
} catch (RemoteException e) {
// if we're here we're dead
}
});
mNotificationLogger.setUpWithEntryManager(mEntryManager, mStackScroller);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
R.id.notification_container_parent));
mKeyguardStatusBar = mStatusBarWindow.findViewById(R.id.keyguard_header);
mNotificationIconAreaController = SystemUIFactory.getInstance()
.createNotificationIconAreaController(context, this);
inflateShelf();
mNotificationIconAreaController.setupShelf(mNotificationShelf);
mStackScroller.setIconAreaController(mNotificationIconAreaController);
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController);
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) fragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanel);
mStatusBarView.setScrimController(mScrimController);
mStatusBarView.setBouncerShowing(mBouncerShowing);
if (oldStatusBarView != null) {
float fraction = oldStatusBarView.getExpansionFraction();
boolean expanded = oldStatusBarView.isExpanded();
mStatusBarView.panelExpansionChanged(fraction, expanded);
}
if (mHeadsUpAppearanceController != null) {
// This view is being recreated, let's destroy the old one
mHeadsUpAppearanceController.destroy();
}
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow);
mStatusBarWindow.setStatusBarView(mStatusBarView);
setAreThereNotifications();
checkBarModes();
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
........
}
看一下CollapsedStatusBarFragment,对应的布局文件是status_bar.xml
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.status_bar, container, false);
}
status_bar.xml:
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<!-- android:background="@drawable/status_bar_closed_default_background" -->
<com.android.systemui.statusbar.phone.PhoneStatusBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height"
android:id="@+id/status_bar"
android:background="@drawable/system_bar_background"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
android:accessibilityPaneTitle="@string/status_bar"
>
<ImageView
android:id="@+id/notification_lights_out"
android:layout_width="@dimen/status_bar_icon_size"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingBottom="2dip"
android:src="@drawable/ic_sysbar_lights_out_dot_small"
android:scaleType="center"
android:visibility="gone"
/>
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end"
android:orientation="horizontal"
>
<ViewStub
android:id="@+id/operator_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout="@layout/operator_name" />
<FrameLayout
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1">
<include layout="@layout/heads_up_status_bar_layout" />
<!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the
individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and
DISABLE_NOTIFICATION_ICONS, respectively -->
<LinearLayout
android:id="@+id/status_bar_left_side"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:clipChildren="false"
>
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
android:gravity="center_vertical|start"
/>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
android:clipChildren="false"/>
</LinearLayout>
</FrameLayout>
<!-- Space should cover the notch (if it exists) and let other views lay out around it -->
<android.widget.Space
android:id="@+id/cutout_space_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center_horizontal|center_vertical"
/>
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_vertical|end"
>
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
</LinearLayout>
<ViewStub
android:id="@+id/emergency_cryptkeeper_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout="@layout/emergency_cryptkeeper_text"
/>
</com.android.systemui.statusbar.phone.PhoneStatusBarView>
其中通知icon在@+id/notification_icon_area这个framelayout里面,
看一下 CollapsedStatusBarFragment对这块布局的处理,是通过addView的方式添加进notificationIconArea中:
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);
// Default to showing until we know otherwise.
showNotificationIconArea(false);
}
实际上mNotificationIconAreaInner对应的布局文件是notification_icon_area.xml,是在NotificationIconAreaController中inflate的。
notification_icon_area.xml:
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<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>
以上,即NotificationIconContainer是所有notification icon的父容器view。
附上对systemui的布局分析:
Notification Icon在SystemUI中的显示流程
systemui中通知的起点在NotificationListener:
StatusBar:
@Override
public void start() {
........
//从Dependency获取实例
mNotificationListener = Dependency.get(NotificationListener.class);
........
//配置自己成为Presenter,实际上NotificationListener中的mPresenter就是StatusBar
mNotificationListener.setUpWithPresenter(this, mEntryManager);
........
}
NotificationListener:
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationEntryManager entryManager) {
mPresenter = presenter;//这里的mPresenter就是StatusBar,采用MVP分层思想
mEntryManager = entryManager;
try {
//向NotificationManagerService注册回调监听
registerAsSystemService(mContext,
new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
UserHandle.USER_ALL);
} catch (RemoteException e) {
Log.e(TAG, "Unable to register notification listener", e);
}
}
当NotificationManagerService发送通知时,NotificationListener中的回调会被触发:
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mPresenter.getHandler().post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
mEntryManager.removeKeyKeptForRemoteInput(key);
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since` we're not going to show them
// anyway. This is true also when the summary is canceled,
// because children are automatically canceled by NoMan in that case.
if (!ENABLE_CHILD_NOTIFICATIONS
&& mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
if (DEBUG) {
Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
}
// Remove existing notification to avoid stale data.
if (isUpdate) {
mEntryManager.removeNotification(key, rankingMap);
} else {
mEntryManager.getNotificationData()
.updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
mEntryManager.updateNotification(sbn, rankingMap); //更新通知流程
} else {
mEntryManager.addNotification(sbn, rankingMap); //添加通知流程
}
});
}
}
NotificationEntryManager:
@Override
public void addNotification(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
........
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) throws InflationException {
String key = notification.getKey();
if (DEBUG) Log.d(TAG, "addNotification key=" + key);
mNotificationData.updateRanking(ranking);
//重点关注createNotificationViews,创建通知view
NotificationData.Entry shadeEntry = createNotificationViews(notification);
boolean isHeadsUped = shouldPeek(shadeEntry);
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(shadeEntry)) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
}
} else if (mNotificationData.getImportance(key)
< NotificationManager.IMPORTANCE_HIGH) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: not important enough: "
+ key);
}
} else {
// Stop screensaver if the notification has a fullscreen intent.
// (like an incoming phone call)
SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
// not immersive & a fullscreen alert should be shown
if (DEBUG)
Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
key);
notification.getNotification().fullScreenIntent.send();
shadeEntry.notifyFullScreenIntentLaunched();
mMetricsLogger.count("note_fullscreen", 1);
} catch (PendingIntent.CanceledException e) {
}
}
}
abortExistingInflation(key);
mForegroundServiceController.addNotification(notification,
mNotificationData.getImportance(key));
mPendingNotifications.put(key, shadeEntry);
mGroupManager.onPendingEntryAdded(shadeEntry);
}
createNotificationViews重点关注这个方法,创建通知的view:
NotificationEntryManager:
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
throws InflationException {
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
//创建entry,保存notification信息
NotificationData.Entry entry = new NotificationData.Entry(sbn);
//跟踪是否有内存泄漏
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
//重点关注,渲染通知view,这里渲染的是下拉界面的通知view
inflateViews(entry, mListContainer.getViewParentForNotification(entry));
return entry;
}
重点关注inflateViews,首次接收通知应该走到else分支,新建流程,这里创建了一个RowInflaterTask进行渲染。new RowInflaterTask().inflate方法最后一个入参是一个RowInflationFinishedListener,这里使用Lambda表达式直接传入回调
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) {//更新流程
entry.reset();
updateNotification(entry, pmUser, sbn, entry.row);
} else {//新建流程
new RowInflaterTask().inflate(mContext, parent, entry,
row -> {
bindRow(entry, pmUser, sbn, row);
updateNotification(entry, pmUser, sbn, row);
});
}
}
RowInflaterTask,可以看到渲染的是status_bar_notification_row这个布局,注意这里的inflate只涉及到通知这一行的外围布局,即entry.row,至于row里面的内容绘制会在下一步处理。
public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
RowInflationFinishedListener listener) {
if (TRACE_ORIGIN) {
mInflateOrigin = new Throwable("inflate requested here");
}
mListener = listener;
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
当异步渲染任务完成后,会回调到RowInflaterTask中的onInflateFinished方法:
RowInflaterTask:
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
if (!mCancelled) {
try {
mEntry.onInflationTaskFinished();
//回调给Lambda表达式row->.....
mListener.onInflationFinished((ExpandableNotificationRow) view);
} catch (Throwable t) {
if (mInflateOrigin != null) {
Log.e(TAG, "Error in inflation finished listener: " + t, mInflateOrigin);
t.addSuppressed(mInflateOrigin);
}
throw t;
}
}
}
此时通过onInflationFinished回调给listener,即上面分析到的Lambda表达式回调
看下回调的处理:
row -> {
bindRow(entry, pmUser, sbn, row);
updateNotification(entry, pmUser, sbn, row);
}
......
private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
row.setHeadsUpManager(mHeadsUpManager);
row.setOnExpandClickListener(mPresenter);
row.setInflationCallback(this); //设置了callback为tihs,即NotificationEntryManager
row.setLongPressListener(getNotificationLongClicker());
......
}
......
protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
boolean isUpdate = mNotificationData.get(entry.key) != null;
boolean wasLowPriority = row.isLowPriority();
row.setIsLowPriority(isLowPriority);
row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
// bind the click event to the content area
mNotificationClicker.register(row, sbn);
// Extract target SDK version.
try {
ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
entry.targetSdk = info.targetSdkVersion;
} catch (PackageManager.NameNotFoundException ex) {
Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
}
row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
&& entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
entry.row = row;
entry.row.setOnActivatedListener(mPresenter);
boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
mNotificationData.getImportance(sbn.getKey()));
boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
//重点关注updateNotification
row.updateNotification(entry);
}
经过一系列处理会调用到row.updateNotification(entry)这个方法:
ExpandableNotificationRow:
public void updateNotification(NotificationData.Entry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
//重点关注inflateNotificationViews
mNotificationInflater.inflateNotificationViews();
cacheIsSystemNotification();
}
现在开始渲染通知row里面的内容,根据entry里面准备的数据。经过一系列调用会走到:
NotificationInflater,又是一个异步渲染task(AsyncInflationTask是NotificationInflater的内部类)
@VisibleForTesting
void inflateNotificationViews(int reInflateFlags) {
if (mRow.isRemoved()) {
// We don't want to reinflate anything for removed notifications. Otherwise views might
// be readded to the stack, leading to leaks. This may happen with low-priority groups
// where the removal of already removed children can lead to a reinflation.
return;
}
StatusBarNotification sbn = mRow.getEntry().notification;
AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mRow,
mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
mCallback, mRemoteViewClickHandler);
if (mCallback != null && mCallback.doInflateSynchronous()) {
task.onPostExecute(task.doInBackground());
} else {
task.execute();
}
}
当sbn内容渲染完成后开始执行回调AsyncInflationTask.onAsyncInflationFinished:
@Override
public void onAsyncInflationFinished(NotificationData.Entry entry) {
mRow.getEntry().onInflationTaskFinished();
mRow.onNotificationUpdated();
//这里的mCallback实际上是上面分析到的bindRow中绑定的回调,即NotificationEntryManager
mCallback.onAsyncInflationFinished(mRow.getEntry());
}
看一下NotificationEntryManager里面的接收到的回调,首次创建通知,调用的是addEntry方法:
@Override
public void onAsyncInflationFinished(NotificationData.Entry entry) {
mPendingNotifications.remove(entry.key);
// If there was an async task started after the removal, we don't want to add it back to
// the list, otherwise we might get leaks.
boolean isNew = mNotificationData.get(entry.key) == null;
if (isNew && !entry.row.isRemoved()) {
//关注addEntry方法
addEntry(entry);
} else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
//走更新流程
mVisualStabilityManager.onLowPriorityUpdated(entry);
mPresenter.updateNotificationViews();
}
entry.row.setLowPriorityStateUpdated(false);
}
......
private void addEntry(NotificationData.Entry shadeEntry) {
boolean isHeadsUped = shouldPeek(shadeEntry);
if (isHeadsUped) {
mHeadsUpManager.showNotification(shadeEntry);
// Mark as seen immediately
setNotificationShown(shadeEntry.notification);
}
//重点关注addNotificationViews
addNotificationViews(shadeEntry);
mCallback.onNotificationAdded(shadeEntry);
}
......
protected void addNotificationViews(NotificationData.Entry entry) {
if (entry == null) {
return;
}
// Add the expanded view and icon.
//mNotificationData内部维护了mEntries,保存了当前系统中的所有通知信息
mNotificationData.add(entry);
tagForeground(entry.notification);
//重点关注updateNotifications
updateNotifications();
}
......
public void updateNotifications() {
mNotificationData.filterAndSort();
mPresenter.updateNotificationViews();
}
可以看到NotificationEntryManager里面的updateNotifications比较简单,先执行通知的过滤和排序,在调用presenter的updateNotificationViews。此处的mPreseneter实际上是StatusBar
看下StatusBar里面对回调的处理:
@Override
public void updateNotificationViews() {
// The function updateRowStates depends on both of these being non-null, so check them here.
// We may be called before they are set from DeviceProvisionedController's callback.
if (mStackScroller == null || mScrimController == null) return;
// Do not modify the notifications during collapse.
if (isCollapsing()) {
addPostCollapseAction(this::updateNotificationViews);
return;
}
mViewHierarchyManager.updateNotificationViews();
updateSpeedBumpIndex();
updateFooter();
updateEmptyShadeView();
updateQsExpansionEnabled();
// Let's also update the icons
mNotificationIconAreaController.updateNotificationIcons();
}
重点关注最后一步,更新statusBar上面的icon: mNotificationIconAreaController.updateNotificationIcons();
/**
* Updates the notifications with the given list of notifications to display.
*/
public void updateNotificationIcons() {
//更新icon
updateStatusBarIcons();
updateShelfIcons();
updateHasShelfIconsWhenFullyDark();
applyNotificationIconsTint();
}
......
public void updateStatusBarIcons() {
updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
false /* showAmbient */, true /* hideDismissed */, true /* hideRepliedMessages */);
}
......
private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed,
boolean hideRepliedMessages) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
// Filter out ambient notifications and notification children.
for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed,
hideRepliedMessages)) {
toShow.add(function.apply(ent));
}
}
}
// In case we are changing the suppression of a group, the replacement shouldn't flicker
// and it should just be replaced instead. We therefore look for notifications that were
// just replaced by the child or vice-versa to suppress this.
ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
ArrayList<View> toRemove = new ArrayList<>();
for (int i = 0; i < hostLayout.getChildCount(); i++) {
View child = hostLayout.getChildAt(i);
if (!(child instanceof StatusBarIconView)) {
continue;
}
if (!toShow.contains(child)) {
boolean iconWasReplaced = false;
StatusBarIconView removedIcon = (StatusBarIconView) child;
String removedGroupKey = removedIcon.getNotification().getGroupKey();
for (int j = 0; j < toShow.size(); j++) {
StatusBarIconView candidate = toShow.get(j);
if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
&& candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
if (!iconWasReplaced) {
iconWasReplaced = true;
} else {
iconWasReplaced = false;
break;
}
}
}
if (iconWasReplaced) {
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
if (statusBarIcons == null) {
statusBarIcons = new ArrayList<>();
replacingIcons.put(removedGroupKey, statusBarIcons);
}
statusBarIcons.add(removedIcon.getStatusBarIcon());
}
toRemove.add(removedIcon);
}
}
// removing all duplicates
ArrayList<String> duplicates = new ArrayList<>();
for (String key : replacingIcons.keySet()) {
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
if (statusBarIcons.size() != 1) {
duplicates.add(key);
}
}
replacingIcons.removeAll(duplicates);
hostLayout.setReplacingIcons(replacingIcons);
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
hostLayout.removeView(toRemove.get(i));
}
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);
}
}
hostLayout.setChangingViewPositions(true);
// Re-sort notification icons
final int childCount = hostLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
View actual = hostLayout.getChildAt(i);
StatusBarIconView expected = toShow.get(i);
if (actual == expected) {
continue;
}
hostLayout.removeView(expected);
hostLayout.addView(expected, i);
}
hostLayout.setChangingViewPositions(false);
hostLayout.setReplacingIcons(null);
}
至此SystemUI 通知流程分析完成。附上时序图: