前文講解了Notification的構造,如今來說講notification的發送,以及公佈前文留下的疑問(自定義view不論高度是多高,最後只能顯示爲64dp,why?)javascript
在Notification構造完成後,會調用NotificationManager的notify
方法來發送通知,咱們就來看看該方法
frameworks/base/core/java/android/app/NotificationManager.javajava
public void notify(String tag, int id, Notification notification)
{
...
INotificationManager service = getService();
...
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
...
}複製代碼
能夠看出NotificationManager只是一個空殼,沒有作什麼實際上的事情,只是把notify的動做交給了service來作。
爲了主幹的清晰,直接給出enqueueNotificationWithTag的實如今NotificationManagerService中android
frameworks/base/services/java/com/android/server/NotificationManagerService.java數據庫
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);
}複製代碼
因此重要的是enqueueNotificationInternal方法數據結構
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
...
if (!isSystemNotification && !isNotificationFromListener) {
...
//MAX_PACKAGE_NOTIFICATIONS = 50;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
return;
}
}
...
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
...
// blocked apps
//若是用戶設置了該引用不顯示通知,而且不是系統通知的話,直接將該通知打分爲-1000
if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
if (!isSystemNotification) {
//JUNK_SCORE = -1000;
r.score = JUNK_SCORE;
}
}
//SCORE_DISPLAY_THRESHOLD = -20;
//打分小於閾值的通知不顯示
if (r.score < SCORE_DISPLAY_THRESHOLD) {
// Notification will be blocked because the score is too low.
return;
}
//垃圾通知,也不會顯示
if (isNotificationSpam(notification, pkg)) {
mArchive.record(r.sbn);
return;
}
...
//只顯示有圖標的通知
if (notification.icon != 0) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
}
...
//聲音,震動,閃光燈的控制
buzzBeepBlinkLocked(r);
}
}
});
}複製代碼
能夠看到要想發出通知必須得知足如下幾個條件app
檢查經過後再使用notifyPostedLocked
方法作真正的發送動做。buzzBeepBlinkLocked很簡單,不浪費篇幅敘述了。ide
notifyPostedLocked方法最後調用notifyPosted方法,咱們直接來看看該方法佈局
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener)info.service;
...
listener.onNotificationPosted(sbnHolder, rankingUpdate);
...
}複製代碼
這裏有一個INotificationListener對象,一看到以I
開頭的就能夠知道,這裏確定又是一個IPC通訊。
查看源碼能夠知道,onNotificationPosted
的實現是在SystemUI進程中,也就是咱們的狀態欄進程。post
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.javaui
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
mHandler.post(new Runnable() {
@Override
public void run() {
...
boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
|| isHeadsUp(sbn.getKey());
...
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
}
});
}複製代碼
狀態欄會根據通知的惟一key值來判斷該通知是不是更新仍是新增的。
咱們以新增的爲例來說.addNotification
是一個抽象方法,實現是在BaseStatusBar的子類PhoneStatusBar
中
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
...
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
...
addNotificationViews(shadeEntry, ranking);
...
}複製代碼
該方法作了2個重要的事情,一個就是建立Entry實例,另一個就是將Entry添加到狀態欄上,而後就顯示完成了。
由於createNotificationViews
的實現是在父類中,而且該方法十分重要,因此咱們先跳過該方法。
先把Entry理解成一條通知,來說addNotificationViews的實現。
protected void addNotificationViews(Entry entry, RankingMap ranking) {
if (entry == null) {
return;
}
// Add the expanded view and icon.
mNotificationData.add(entry, ranking);
updateNotifications();
}複製代碼
先直接將獲得的Entry添加到mNotificationData裏面
最終updateNotifications會調用PhoneStatusBar中的updateNotificationShade
方法
private void updateNotificationShade() {
...
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
...
for (int i=0; i<N; i++) {
Entry ent = activeNotifications.get(i);
...
toShow.add(ent.row);
}
for (int i=0; i<toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) {
mStackScroller.addView(v);
}
}
...
}複製代碼
這個mStackScroller是NotificationStackScrollLayout的對象,而這個NotificationStackScrollLayout是一個繼承自ViewGroup的,也就是咱們下拉狀態欄看到的整片view的根view.
那麼ExpandableNotificationRow也就是對應着每個通知了. ExpandableNotificationRow是繼承自FrameLayout的
咱們前面說到把Entry先理解爲一條通知,看到這裏,其實添加的是Entry對象裏面的row屬性到界面上,也就是ExpandableNotificationRow
這個是解答開頭疑問的關鍵。 該方法是BaseStatusBar
類的方法。
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
...
// Construct the expanded view.
NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
if (!inflateViews(entry, mStackScroller)) {
handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
return null;
}
return entry;
}複製代碼
這裏首先實例化了NotificationData的內部類Entry。
NotificationData是一個十分重要的類,裏面有幾個比較重要的數據結構
ArrayMapmEntries = new ArrayMap<>(); //全部Entry的集合
ArrayListmSortedAndFiltered = new ArrayList<>(); //排序後的Entry集合
public static final class Entry {
...
public ExpandableNotificationRow row; // the outer expanded view
public View expanded; // the inflated RemoteViews
public View expandedPublic; // for insecure lockscreens
public View expandedBig;
...
}複製代碼
從定義裏面能夠看出,一個Entry對應了一條通知欄的全部Data信息,其中比較重要的是row屬性,前面已經碰到過了。最後添加界面上的也就是這個row。
在inflateViews
方法裏面,這個row會被賦值,咱們來看看row是怎麼被賦值的
private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
...
//contentView和bigContentView是咱們構造Notification時傳過來的view
RemoteViews contentView = sbn.getNotification().contentView;
RemoteViews bigContentView = sbn.getNotification().bigContentView;
...
ExpandableNotificationRow row;
...
//使用指定view填充
row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
parent, false);
...
//這個expanded view就是咱們在下拉狀態欄中看到的每一條view,這裏命名爲expanded 應該是狀態欄展開,而不是通知展開
//NotificationContentView是繼承自FrameLayout的,會根據不一樣狀態來控制顯示哪一個view(默認通知/展開通知)
NotificationContentView expanded =
(NotificationContentView) row.findViewById(R.id.expanded);
...
//給每一條通知設置onClick的點擊事件,以來相應咱們設置的動做.
PendingIntent contentIntent = sbn.getNotification().contentIntent;
final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(),
isHeadsUp);
row.setOnClickListener(listener);
...
///////關鍵////////////
View contentViewLocal = null;
View bigContentViewLocal = null;
//將構造通知欄時設置的contentView & bigContentView(RemoteView)轉換爲view
contentViewLocal = contentView.apply(mContext, expanded,
mOnClickHandler, themePackageName);
if (bigContentView != null) {
bigContentViewLocal = bigContentView.apply(mContext, expanded,
mOnClickHandler, themePackageName);
}
...
//由於expanded 是一個FrameLayout的ViewGroup,因此往裏面塞了2個view
expanded.setContractedChild(contentViewLocal);
expanded.setExpandedChild(bigContentViewLocal);
}複製代碼
看完上面的代碼,先來坐個小節,整理下思路。在Entry.row添加到屏幕上前,作了以下的屬性賦值
到這裏,一個通知欄從初始化到顯示的流程就講完了,可是最開頭的疑問不是尚未解答嗎?來看答案
在expanded.setContractedChild
方法前,傳遞進來的ContentView都仍是自義定的view,沒有作高度限制或者系統默認的view. 最後顯示的時候卻被限制了,說明在setContractedChild方法裏作了手腳
public void setContractedChild(View child) {
...
sanitizeContractedLayoutParams(child);
addView(child);
...
}複製代碼
private void sanitizeContractedLayoutParams(View contractedChild) {
LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
lp.height = mSmallHeight;
contractedChild.setLayoutParams(lp);
}複製代碼
能夠看到在sanitizeContractedLayoutParams
方法裏面,不論傳遞進來的contentView有多高最後的會被改爲mSmallHeight的高度。這個mSmallHeight的值就是在SystemUI裏面配置的,64dp
在expanded.setExpandedChild
的方法裏面卻沒有作最大高度的限制,那麼最大高度是在哪限制的呢?
這個時候就要看看ExpandableNotificationRow這個根view了
ExpandableNotificationRow繼承自ExpandableView,來看看onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//mMaxNotificationHeight是systemui中配置的值,256dp
int ownMaxHeight = mMaxNotificationHeight;
...
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int childHeightSpec = newHeightSpec;
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
if (layoutParams.height >= 0) {
// An actual height is set
childHeightSpec = layoutParams.height > ownMaxHeight
? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
: MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
}
child.measure(
getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
childHeightSpec);
int childHeight = child.getMeasuredHeight();
maxChildHeight = Math.max(maxChildHeight, childHeight);
} else {
mMatchParentViews.add(child);
}
}
int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
for (View child : mMatchParentViews) {
child.measure(getChildMeasureSpec(
widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
newHeightSpec);
}
...複製代碼
若是bigviewlayoutParams.height == ViewGroup.LayoutParams.MATCH_PARENT
則高度就是newHeightSpec。這個newHeightSpec要麼是ownMaxHeight 要麼是maxChildHeight,而這2個值的最大值就是256dp
若是bigviewlayoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT
,最大值也是maxChildHeight 也就是256dp
注意: 這裏並無顯示bigview的最小高度,因此bigview的高度範圍是能夠在(0,256dp ] 區間的
a pic worth thousands of words, tow pics worth double, lol
Notification之----Android5.0實現原理(一)
Notification之----自定義樣式
Notification之----默認樣式
Notification之----任務棧