不管是发出一个新的通知还是对已经存在的通知进行更新,调用的都是NotificationManager.notify(int id,Notification notification)。最后走到SystemUI的时候首先调用StatusBar中的成员变量mNotificationListener的onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap)方法。成员变量mNotificationListener是一个NotificationListenerWithPlugins类的对象,NotificationListenerWithPlugins是NotificationListenerService的父类,NotificationListenerService不在SystemUI中,在android.service.notification中,暂不分析.
来看mNotificationListener对onNotificationPosted(final StatusBarNotification sbn,final RankingMap rankingMap)方法的实现。
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mHandler.post(new Runnable() {
@Override
public void run() {
processForRemoteInput(sbn.getNotification());
String key = sbn.getKey();
mKeysKeptForRemoteInput.remove(key);
boolean isUpdate = mNotificationData.get(key) != null;
......
// Remove existing notification to avoid stale data.
if (isUpdate) {
removeNotification(key, rankingMap);
} else {
mNotificationData.updateRanking(rankingMap);
}
return;
}
try {
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
} catch (InflationException e) {
handleInflationException(sbn, e);
}
}
});
}
}
首先来看方法中的两个参数:1.StatusBarNotification sbn;2.RankingMap rankingMap。
StatusBarNotification点进去看,发现其实是由Notification组装而成,里面比较重要的属性有String pkg,int id,String key,Notification notification,保存着通知的内容,发出通知的报名信息,以及id等。StatusBarNotification 具体的组装生成过程不是在SystemUI包中进行,暂不关注。
RankingMap则是NotificationListenerService的一个静态内部类,里面保存着所有Notification相关的信息,具体的我们先往下看。
在onNotificationPosted()方法中,先对传过来的sbn进行空判断,不为空,则用mHandler发一个新的runnable来处理
String key = sbn.getKey();
mKeysKeptForRemoteInput.remove(key);
boolean isUpdate = mNotificationData.get(key) != null;
先拿到sbn里面key属性,再根据这个key去mNotificationData取对象。
mNotificationData是StatusBar的一个protected成员变量,可被子类继承,自己本身的类是NotificationData,位于SystemUI工程下的com.android.systemui.statusbar。它的get(key)方法如下:
public Entry get(String key) {
return mEntries.get(key);
}
返回了一个Entry对象mEntries.get(key),我们来看看这个Entry是什么。Entry是NotificationData的一个内部类。其中包含的几个重要的属性的属性:
public String key;
public StatusBarNotification notification;
public NotificationChannel channel;
public StatusBarIconView icon;
public StatusBarIconView expandedIcon;
public ExpandableNotificationRow row; // the outer expanded view
显然,我们的Notification先被封装成StatusBarNotification传到SystemUI,然后在SystemUI中又再一次被封装进这个Entry中。
然后我们再看看这个mEntries,这是NotificationData的一个成员变量:
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
暂时没看到这个map的数据是在哪里添加的。
我们回到onNotificationPosted方法,跳过一部分代码,直接来到重点。
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
如果mNotificationData能通过sbn的key拿到的Entry不为空,说明这个通知已经存在了,isUpdate为true走更新流程,否则走添加流程。到此,onNotificationPosted方法就结束了。
我们先来看添加流程addNotification(sbn, rankingMap)。
在StatusBar中,这个方法是被这么实现的
public void addNotification(StatusBarNotification notification, RankingMap ranking)
throws InflationException {
String key = notification.getKey();
mNotificationData.updateRanking(ranking);
Entry shadeEntry = createNotificationViews(notification);
boolean isHeadsUped = shouldPeek(shadeEntry);
......
abortExistingInflation(key);
mForegroundServiceController.addNotification(notification,
mNotificationData.getImportance(key));
mPendingNotifications.put(key, shadeEntry);
}
首先通过传来的StatusBarNotification notification封装构造出一个Entry对象(注意,这的notification是StabusBarNotification不是Notification,也就是onNotificationPosted传入的sbn)
Entry shadeEntry = createNotificationViews(notification);
跟过去看createNotificationViews(notification)方法,这里又跳回了StatusBar。
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
throws InflationException {
NotificationData.Entry entry = new NotificationData.Entry(sbn);
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
inflateViews(entry, mStackScroller);
return entry;
}
这里面的重点是inflateViews(entry, mStackScroller)。第二个参数mStackScroller,就是SystemUI中的下拉通知栏里面所有通知以及一些其他view的父view,是StatusBar中一个成员变量。
跟过去看方法细节
protected void inflateViews(Entry entry, ViewGroup parent) {
PackageManager pmUser = 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().inflate方法,该方法在RowInflaterTask中
public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
RowInflationFinishedListener listener) {
mListener = listener;
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
这个row(ExpandableNotificationRow)就是最终添加到通知栏上的通知对应的view,它的布局文件是R.layout.status_bar_notification_row。
这里重点说一下AsyncLayoutInflater这个类,它是NotificationInflater的静态内部类,其中有方法onAsyncInflationFinished如下
public void onAsyncInflationFinished(NotificationData.Entry entry) {
mRow.getEntry().onInflationTaskFinished();
mRow.onNotificationUpdated();
mCallback.onAsyncInflationFinished(mRow.getEntry());
}
onAsyncInflationFinished的实现在StatusBar中,如下
public void onAsyncInflationFinished(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(entry);//重点
} else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
mVisualStabilityManager.onLowPriorityUpdated(entry);
updateNotificationShade();
}
entry.row.setLowPriorityStateUpdated(false);
}
addEntry是重点,如下
private void addEntry(Entry shadeEntry) {
boolean isHeadsUped = shouldPeek(shadeEntry);
if (isHeadsUped) {
mHeadsUpManager.showNotification(shadeEntry);
// Mark as seen immediately
setNotificationShown(shadeEntry.notification);
}
addNotificationViews(shadeEntry);//重点
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
}
addNotificationViews的内容如下
protected void addNotificationViews(Entry entry) {
if (entry == null) {
return;
}
// Add the expanded view and icon.
mNotificationData.add(entry);
updateNotifications();
}
mNotificationData.add(entry);对应了前面分析的boolean isUpdate = mNotificationData.get(key) != null;
进入updateNotifications()方法,内容如下
protected void updateNotifications() {
mNotificationData.filterAndSort();
updateNotificationShade();
}
重点在updateNotificationShade()(实际上,我就是在这个方法里进行了一些修改达到我想要的需求)
private void updateNotificationShade() {
if (mStackScroller == null) return;
// Do not modify the notifications during collapse.
if (isCollapsing()) {
addPostCollapseAction(new Runnable() {
@Override
public void run() {
updateNotificationShade();
}
});
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);
if (ent.row.isDismissed() || ent.row.isRemoved()) {
// we don't want to update removed notifications because they could
// temporarily become children if they were isolated before.
continue;
}
int userId = ent.notification.getUserId();
// Display public version of the notification if we need to redact.
boolean devicePublic = isLockscreenPublicMode(mCurrentUserId);
boolean userPublic = devicePublic || isLockscreenPublicMode(userId);
boolean needsRedaction = needsRedaction(ent);
boolean sensitive = userPublic && needsRedaction;
boolean deviceSensitive = devicePublic
&& !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
ent.row.setSensitive(sensitive, deviceSensitive);
ent.row.setNeedsRedaction(needsRedaction);
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) {
//*/ freeme.biantao, 20180207. Theme.Freeme 8.1 - notification.
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
if (row.isStyleInGroup()) {
row.setStyleInGroup(false);
}
}
//*/
mVisualStabilityManager.notifyViewAddition(v);
mStackScroller.addView(v);
}
}
addNotificationChildrenAndSort();
// So after all this work notifications still aren't sorted correctly.
// Let's do that now by advancing through toShow and mStackScroller in
// lock-step, making sure mStackScroller matches what we see in toShow.
int j = 0;
for (int i = 0; i < mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
if (!(child instanceof ExpandableNotificationRow)) {
// We don't care about non-notification views.
continue;
}
ExpandableNotificationRow targetChild = toShow.get(j);
if (child != targetChild) {
// Oops, wrong notification at this position. Put the right one
// here and advance both lists.
if (mVisualStabilityManager.canReorderNotification(targetChild)) {
mStackScroller.changeViewPosition(targetChild, i);
} else {
mVisualStabilityManager.addReorderingAllowedCallback(this);
}
}
j++;
}
mVisualStabilityManager.onReorderingFinished();
// clear the map again for the next usage
mTmpChildOrderMap.clear();
updateRowStates();
updateSpeedBumpIndex();
updateClearAll();
updateEmptyShadeView();
updateQsExpansionEnabled();
// Let's also update the icons
mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
}
显示做mStackScroller的空判断,然后是通知栏动画状态的判断。一切OK,就:
1.mNotificationData获取数据Entry集合,构造一个大小和这个Entry集合一样的ExpandableNotificationRow集合toShow
2.遍历entry,把entry.row添加到toshow里面
3.原有的通知,但是toshow里没有的则移除,然后toshow里没添加上的添加上去
至此我们的通知就成功地添加到通知栏了。
看bindRow(entry, pmUser, sbn, row)方法
private void bindRow(Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
row.setHeadsUpManager(mHeadsUpManager);
row.setAboveShelfChangedListener(mAboveShelfObserver);
row.setRemoteInputController(mRemoteInputController);
row.setOnExpandClickListener(this);
row.setRemoteViewClickHandler(mOnClickHandler);
row.setInflationCallback(this);
row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
final String pkg = sbn.getPackageName();
String appname = pkg;
try {
final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS);
if (info != null) {
appname = String.valueOf(pmUser.getApplicationLabel(info));
}
} catch (NameNotFoundException e) {
// Do nothing
}
row.setAppName(appname);
row.setOnDismissRunnable(() ->
performRemoveNotification(row.getStatusBarNotification()));
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
//该句实现了焦点的策略,row(也就是我们的通知)有子控件需要焦点则把焦点交给子控件,否则给row。
if (ENABLE_REMOTE_INPUT) {
row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
}
看updateNotification(entry,pmUser,sbn,entry.row)方法
private void updateNotification(Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setNeedsRedaction(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 (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(this);
boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
mNotificationData.getImportance(sbn.getKey()));
boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
row.updateNotification(entry);
}
mNotificationClicker.register(row, sbn);mNotificationClicker是StatusBar的一个私有成员,对应的类是NotificationClicker,是一个StatusBar的内部类,实现了View.OnClickListener接口,它就是row的监听类,实现的功能是row被点击时,启动相应的pendingIntent.注册操作代码如下
public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
Notification notification = sbn.getNotification();
if (notification.contentIntent != null || notification.fullScreenIntent != null) {
row.setOnClickListener(this);
} else {
row.setOnClickListener(null);
}
}
进入ExpandableNotificationRow的updateNotification方法
public void updateNotification(NotificationData.Entry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
mNotificationInflater.inflateNotificationViews();
}
至此inflateViews()结束,Entry生成完毕,Entry中的row生成完毕。
Notification的更新
接着回到最开始的updateNotification(StatusBarNotification notification, RankingMap ranking)方法中
public void updateNotification(StatusBarNotification notification, RankingMap ranking)
throws InflationException {
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
abortExistingInflation(key);
Entry entry = mNotificationData.get(key);
if (entry == null) {
return;
}
mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
if (key.equals(mKeyToRemoveOnGutsClosed)) {
mKeyToRemoveOnGutsClosed = null;
Log.w(TAG, "Notification that was kept for guts was updated. " + key);
}
Notification n = notification.getNotification();
mNotificationData.updateRanking(ranking);
final StatusBarNotification oldNotification = entry.notification;
entry.notification = notification;
mGroupManager.onEntryUpdated(entry, oldNotification);
entry.updateIcons(mContext, notification);
inflateViews(entry, mStackScroller);
mForegroundServiceController.updateNotification(notification,
mNotificationData.getImportance(key));
boolean shouldPeek = shouldPeek(entry, notification);
boolean alertAgain = alertAgain(entry, n);
updateHeadsUp(key, entry, shouldPeek, alertAgain);
updateNotifications();
if (!notification.isClearable()) {
// The user may have performed a dismiss action on the notification, since it's
// not clearable we should snap it back.
mStackScroller.snapViewIfNeeded(entry.row);
}
if (DEBUG) {
// Is this for you?
boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
}
setAreThereNotifications();
}
更新Notification走的还是inflateViews方法和updateNotifications方法
移除通知
首先还是StatusBar中的mNotificationListener但是和notification的添加/更新不同的是,走的不再是onNotificationPosted方法,而是onNotificationRemoved
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (true/**DEBUG*/) Log.d(TAG, "onNotificationRemoved: " + sbn);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
mHandler.post(() -> removeNotification(key, rankingMap));
}
}
这个方法就是很简单地拿个key,然后走removeNotification(key, rankingMap)方法.
public void removeNotification(String key, RankingMap ranking) {
boolean deferRemoval = false;
abortExistingInflation(key);
if (mHeadsUpManager.isHeadsUp(key)) {
// A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
// sending look longer than it takes.
// Also we should not defer the removal if reordering isn't allowed since otherwise
// some notifications can't disappear before the panel is closed.
boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key)
&& !FORCE_REMOTE_INPUT_HISTORY
|| !mVisualStabilityManager.isReorderingAllowed();
deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
}
if (key.equals(mMediaNotificationKey)) {
clearCurrentMediaNotification();
updateMediaMetaData(true, true);
}
if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
Entry entry = mNotificationData.get(key);
StatusBarNotification sbn = entry.notification;
Notification.Builder b = Notification.Builder
.recoverBuilder(mContext, sbn.getNotification().clone());
CharSequence[] oldHistory = sbn.getNotification().extras
.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
CharSequence[] newHistory;
if (oldHistory == null) {
newHistory = new CharSequence[1];
} else {
newHistory = new CharSequence[oldHistory.length + 1];
for (int i = 0; i < oldHistory.length; i++) {
newHistory[i + 1] = oldHistory[i];
}
}
newHistory[0] = String.valueOf(entry.remoteInputText);
b.setRemoteInputHistory(newHistory);
Notification newNotification = b.build();
// Undo any compatibility view inflation
newNotification.contentView = sbn.getNotification().contentView;
newNotification.bigContentView = sbn.getNotification().bigContentView;
newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
sbn.getOpPkg(),
sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
boolean updated = false;
try {
updateNotification(newSbn, null);
updated = true;
} catch (InflationException e) {
deferRemoval = false;
}
if (updated) {
mKeysKeptForRemoteInput.add(entry.key);
return;
}
}
if (deferRemoval) {
mLatestRankingMap = ranking;
mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
return;
}
Entry entry = mNotificationData.get(key);
if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
&& (entry.row != null && !entry.row.isDismissed())) {
mLatestRankingMap = ranking;
mRemoteInputEntriesToRemoveOnCollapse.add(entry);
return;
}
if (entry != null && mNotificationGutsExposed != null
&& mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null
&& !entry.row.getGuts().isLeavebehind()) {
Log.w(TAG, "Keeping notification because it's showing guts. " + key);
mLatestRankingMap = ranking;
mKeyToRemoveOnGutsClosed = key;
return;
}
if (entry != null) {
mForegroundServiceController.removeNotification(entry.notification);
}
if (entry != null && entry.row != null) {
entry.row.setRemoved();
mStackScroller.cleanUpViewState(entry.row);
}
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key, ranking);
StatusBarNotification old = removeNotificationViews(key, ranking);
/// M: Enable this log for unusual case debug.
if (true/**SPEW*/) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
if (old != null) {
if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
&& !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
if (mState == StatusBarState.SHADE) {
animateCollapsePanels();
} else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) {
goToKeyguard();
}
}
}
setAreThereNotifications();
}
这里面的重点是removeNotificationViews(key, ranking)方法,这个方法是在StatusBar中定义的
protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
NotificationData.Entry entry = mNotificationData.remove(key, ranking);
if (entry == null) {
Log.w(TAG, "removeNotification for unknown key: " + key);
return null;
}
updateNotifications();
Dependency.get(LeakDetector.class).trackGarbage(entry);
return entry.notification;
}
里面逻辑也很简单,根据key,从mNotificationData移除entry,然后就是走回我们熟悉的updateNotifications()刷新UI。