上篇文章简要介绍了下手机状态栏,今天主要说下SystemUI中Notification的显示,系统或第三方应用都能够发送通知,通过调用NotificationManager的notify或notifyAsUser(仅限系统签名应用使用,当然通第三方应用通过反射方式也能调用)通知到statusbar,下面来看看具体流程。
1、通过Notification.Builder构建通知,调用NotificationManager@notify(int id, Notification notification)发出通知
2、调用NotificationManager@notify后,会通过
NotificationManagerService@enqueueNotificationWithTag把通知放入队列
3、通知加入到队列后,通过NotificationManagerService@notifyPosted通知到SystemUI中BaseStatusBar.java
以上差不多就是一个通知被StatusBar接收到的流程,下面先简要看看这部分代码,首先我们先看看发送通知的代码
Notification.Builder builder = new Notification.Builder(context);
//省略设置通知图标、内容以及一些属性
Notification notification = builder.build();
mNotificationMgr.notify(MISSED_CALL_NOTIFICATION_ID, notification);
//mNotificationMgr.cancel(MISSED_CALL_NOTIFICATION_ID); 取消通知
当上面的notify被调用后,就会依次调用NotificationManager@notify(String tag, int id, Notification notification)->NotificationManager@notifyAsUser(String tag, int id, Notification notification, UserHandle user)
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (notification.getSmallIcon() == null) {
throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+ notification);
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, idOut, user.getIdentifier());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
在上面会调用NotificationManagerService@enqueueNotificationWithTag
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int[] idOut, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, idOut, userId);
}
NotificationManagerService@enqueueNotificationInternal->NotificationManagerService.EnqueueNotificationRunnable@run->
private class EnqueueNotificationRunnable implements Runnable {
...
@Override
public void run() {
synchronized (mNotificationList) {
...
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
} else {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(n);
}
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid
// notifications
Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+ n.getPackageName());
}
buzzBeepBlinkLocked(r);
}
...
}
}
在notifyPostedLocked里调用notifyPosted
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener)info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
而在SystemUI中的BaseStatusBar.java里
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
try { mNotificationListener.registerAsSystemService(mContext,
new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
UserHandle.USER_ALL);
} catch (RemoteException e) {
Log.e(TAG, "Unable to register notification listener", e);
}
private final NotificationListenerService mNotificationListener =
new NotificationListenerService() {
@Override
public void onListenerConnected() {
...
}
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
...
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap, null /* oldEntry */);//子类PhoneStatusBar.java实现
}
}
});
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
...
}
@Override
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
...
}
};
在PhoneStatusBar.java里,addNotification会把通知添加到NotificationData集合中
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@Override
public void addNotification(StatusBarNotification notification, RankingMap ranking,
Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
mNotificationData.updateRanking(ranking);
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
boolean isHeadsUped = shouldPeek(shadeEntry);
if (isHeadsUped) {
mHeadsUpManager.showNotification(shadeEntry);
// Mark as seen immediately
setNotificationShown(notification);
}
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(notification.getKey())) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + notification.getKey());
}
} else if (mNotificationData.getImportance(notification.getKey())
< NotificationListenerService.Ranking.IMPORTANCE_MAX) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: not important enough: "
+ notification.getKey());
}
} else {
// Stop screensaver if the notification has a full-screen intent.
// (like an incoming phone call)
awakenDreams();
// not immersive & a full-screen alert should be shown
if (DEBUG)
Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
notification.getKey());
notification.getNotification().fullScreenIntent.send();
shadeEntry.notifyFullScreenIntentLaunched();
MetricsLogger.count(mContext, "note_fullscreen", 1);
} catch (PendingIntent.CanceledException e) {
}
}
}
addNotificationViews(shadeEntry, ranking);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
}
PhoneStatusBar@addNotificationViews->PhoneStatusBar@updateNotifications
@Override
protected void updateNotifications() {
mNotificationData.filterAndSort();
updateNotificationShade(); mIconController.updateNotificationIcons(mNotificationData);
}
在这个方法中会对NotificationData进行过滤和排序、更新通知栏通知和状态栏通知图标,在updateNotificationShade中会把通知添加
到NotificationStackScrollLayout中,每条通知对应一个
ExpandableNotificationRow
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
private void updateNotificationShade() {
if (mStackScroller == null) return;
...
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
final int N = activeNotifications.size();
for (int i=0; i<N; i++) {
Entry ent = activeNotifications.get(i);
int vis = ent.notification.getNotification().visibility;
// Display public version of the notification if we need to redact.
final boolean hideSensitive =
!userAllowsPrivateNotificationsInPublic(ent.notification.getUserId());
boolean sensitiveNote = vis == Notification.VISIBILITY_PRIVATE;
boolean sensitivePackage = packageHasVisibilityOverride(ent.notification.getKey());
boolean sensitive = (sensitiveNote && hideSensitive) || sensitivePackage;
boolean showingPublic = sensitive && isLockscreenPublicMode();
if (showingPublic) {
updatePublicContentView(ent, ent.notification);
}
ent.row.setSensitive(sensitive, hideSensitive);
if (ent.autoRedacted && ent.legacy) {
// TODO: Also fade this? Or, maybe easier (and better), provide a dark redacted form
// for legacy auto redacted notifications.
if (showingPublic) {
ent.row.setShowingLegacyBackground(false);
} else {
ent.row.setShowingLegacyBackground(true);
}
}
if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
ent.row.getStatusBarNotification());
List<ExpandableNotificationRow> orderedChildren =
mTmpChildOrderMap.get(summary);
if (orderedChildren == null) {
orderedChildren = new ArrayList<>();
mTmpChildOrderMap.put(summary, orderedChildren);
}
orderedChildren.add(ent.row);
} else {
toShow.add(ent.row);
}
}
ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
for (int i=0; i< mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
toRemove.add((ExpandableNotificationRow) child);
}
}
for (ExpandableNotificationRow remove : toRemove) {
if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
// we are only transfering this notification to its parent, don't generate an animation
mStackScroller.setChildTransferInProgress(true);
}
if (remove.isSummaryWithChildren()) {
remove.removeAllChildren();
}
mStackScroller.removeView(remove);
mStackScroller.setChildTransferInProgress(false);
}
removeNotificationChildren();
for (int i=0; i<toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) {
mStackScroller.addView(v);//把需要显示的通知添加到NotificationStackScrollLayout中
}
}
...
}
在mIconController.updateNotificationIcons里更新状态栏左边显示的通知图标
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
public void updateNotificationIcons(NotificationData notificationData) {
final LinearLayout.LayoutParams params = generateIconLayoutParams();
ArrayList<NotificationData.Entry> activeNotifications = notificationData.getActiveNotifications();
final int size = activeNotifications.size();
ArrayList<StatusBarIconView> toShow = new ArrayList<>(size);
// Filter out ambient notifications and notification children.
for (int i = 0; i < size; i++) {
NotificationData.Entry ent = activeNotifications.get(i);
if (shouldShowNotification(ent, notificationData)) {
toShow.add(ent.icon);//把需要显示的通知图标添加到toShow集合
}
}
ArrayList<View> toRemove = new ArrayList<>();
for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
View child = mNotificationIcons.getChildAt(i);
if (!toShow.contains(child)) {
toRemove.add(child);
}
}
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
mNotificationIcons.removeView(toRemove.get(i));
}
for (int i = 0; i < toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) {
mNotificationIcons.addView(v, i, params);//遍历toShow集合添加到mNotificationIcons中
}
}
...
}
mNotificationIcons是IconMerger的实例继承自LinearLayout的一个线性布局,上篇文章《Android N SystemUI-状态栏》说过,通知图标相关问题,基本上就可以根据notification_icon_area这个id去跟踪,这个是在StatusBarIconController中被findViewById的,notification_icon_area其实就是一个ViewGroup,里面添加了mNotificationIconAreaInner
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
PhoneStatusBar phoneStatusBar) {
...
mNotificationIconAreaInner =
mNotificationIconAreaController.getNotificationInnerAreaView()//返回一个mNotificationIconArea
ViewGroup notificationIconArea =
(ViewGroup) statusBar.findViewById(R.id.notification_icon_area);
notificationIconArea.addView(mNotificationIconAreaInner);
...
}
紧接着看看mNotificationIconAreaInner的初始化
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
public View getNotificationInnerAreaView() {
return mNotificationIconArea;
}
protected void initializeNotificationAreaViews(Context context) {
reloadDimens(context);
LayoutInflater layoutInflater = LayoutInflater.from(context);
mNotificationIconArea = inflateIconArea(layoutInflater);
mNotificationIcons =
(IconMerger) mNotificationIconArea.findViewById(R.id.notificationIcons);
mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon);
if (mMoreIcon != null) {
mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
mNotificationIcons.setOverflowIndicator(mMoreIcon);
}
}
protected View inflateIconArea(LayoutInflater inflater) {
return inflater.inflate(R.layout.notification_icon_area, null);
}
notification_icon_area.xml布局文件如下
frameworks/base/packages/SystemUI/res/layout/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" >
<com.android.systemui.statusbar.StatusBarIconView
android:id="@+id/moreIcon"
android:layout_width="@dimen/status_bar_icon_size"
android:layout_height="match_parent"
android:src="@drawable/stat_notify_more"
android:visibility="gone" />
<com.android.systemui.statusbar.phone.IconMerger
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"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
到这通知栏中每条通知的显示以及状态栏左边通知图标的显示就说的差不多了,如有不对,欢迎留言指出!