Android6.0 Notification工作原理原始碼解析(二)
時序圖
通知的傳送是通過NotificationManager的notify()方法:
NotificationManger->notify()
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
public void notify(String tag, int id, Notification notification)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
...
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
}
}
/** @hide */
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
可以明顯示的看到上面程式碼中的getService()通過Binder獲取到NotificationManager對應的Service,按Android系統中的命令慣例即是 NotificationManagerService, 真正的處理在此Service中。
NotificationManagerService->enqueueNotificationWithTag()
enqueueNotificationWithTag() -> 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) {
...
//檢測是否是同一APP發出的,是個通知安全檢查,有問題則丟擲異常。
checkCallerIsSystemOrSameApp(pkg);
final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); //是否為系統通知
final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg); //所有系統服務與應用已註冊的服務。
...
if (!isSystemNotification && !isNotificationFromListener) {
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
...
count++;
//最大50條
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " notifications. Not showing more. package=" + pkg);
return;
}
}
}
}
}
...
if (notification.getSmallIcon() != null) {
if (!notification.isValid()) {
throw new IllegalArgumentException("Invalid notification (): pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
}
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
...
// blocked apps 應用被設定不允許彈出通知
if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
if (!isSystemNotification) {
r.score = JUNK_SCORE;
Slog.e(TAG, "Suppressing notification from package " + pkg
+ " by user request.");
mUsageStats.registerBlocked(r);
}
}
if (r.score < SCORE_DISPLAY_THRESHOLD) {
// Notification will be blocked because the score is too low.
return;
}
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);
}
...
}
//通知語言與震動相關處理
buzzBeepBlinkLocked(r);
}
}
});
idOut[0] = id;
}
從上面的方法中我們可以得知:
1. 如果當前應用不是系統應用並且不是已註冊的服務的話,那麼Android系統最多讓同時存在50條通知訊息。
2. 應用被設定禁止彈出通知或通知沒設定圖示的話通知也不能被彈出。
NotificationListenerService->notifyPostedLocked()
通知是被上面的mListeners.notifyPostedLocked()->notifyPosted()->INotificationListener->onNotificationPosted()方法通知到各個服務的,INotificationListener對應的處理服務即是NotificationListenerService:
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);
}
}
BaseStatusBar中的NotificationListenerService例項接收通知訊息
那麼這是在哪裡處理的嗎?
在Android原始碼中全域性搜尋下就可以找到以下程式碼:
private final NotificationListenerService mNotificationListener =
new NotificationListenerService() {
...
@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() {
...
//// Remove existing notification to avoid stale data.
if (isUpdate) {
removeNotification(key, rankingMap);
} else {
mNotificationData.updateRanking(rankingMap); //排序
}
return;
}
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap, null /* oldEntry */); //建立新通知
}
}
});
}
}
}
mNotificationListener即是NotificationListenerService的例項,它在BaseStatusBar的start方法中將此mNotificationListener關聯到NotificationManagerService中:
//BaseStatusBar.java
public void start() {
// Set up the initial notification state.
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);
}
}
//NotificationListenerService.java
@SystemApi
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
mSystemContext = context;
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
INotificationManager noMan = getNotificationInterface();
//即上一小節的final INotificationListener listener = (INotificationListener)info.service
noMan.registerListener(mWrapper, componentName, currentUser);
mCurrentUser = currentUser;
}
PhoneStatusBar建立通知檢視並顯示
BaseStatusBar即是用來處理狀態列相關業務的類,繼承BaseStatusBar的有PhoneStatusBar、TvStatusBar,看名字就可以得知PhoneStatusBar是用於手機螢幕而TvStatusBar是用於TV的,再繼續看PhoneStatusBar中的處理:
//PhoneStatusBar.java
@Override
public void addNotification(StatusBarNotification notification, RankingMap ranking,
Entry oldEntry) {
...
//建立通知對應的檢視
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
//收到通知時在螢幕上方彈出的通知提示相關處理。
boolean isHeadsUped = mUseHeadsUp && shouldInterrupt(shadeEntry);
if (isHeadsUped) {
mHeadsUpManager.showNotification(shadeEntry);
// Mark as seen immediately
setNotificationShown(notification);
}
...
addNotificationViews(shadeEntry, ranking);
...
}
上面的處理中可以很明顯的看到通過我們在最初呼叫 NotificationManager.notify() 時創建出的StatusBarNotification來創建出一個用來狀態列通知顯示的Entry,裡面存有建立好的單個通知檢視:
createNotificationViews()->inflateViews():
//BaseStatusBar.java
protected boolean inflateViews(Entry entry, ViewGroup parent) {
PackageManager pmUser = getPackageManagerForUser(
entry.notification.getUser().getIdentifier());
int maxHeight = mRowMaxHeight;
final StatusBarNotification sbn = entry.notification;
RemoteViews contentView = sbn.getNotification().contentView;
...
// create the row view
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
parent, false);
//繫結點選事件處理
mNotificationClicker.register(row, sbn);
...
//呼叫RemoteViews->apply()方法繫結檢視
contentViewLocal = contentView.apply(
sbn.getPackageContext(mContext),
contentContainer,
mOnClickHandler);
...
entry.row.setHeightRange(mRowMinHeight, maxHeight);
...
從上面的程式碼中可以看到:
1. 每個通知都有個高度範圍,64dp-256dp。
2. 通知的layout模板 R.layout.status_bar_notification_row 。
3. PhoneStatusBar收到通知後最終呼叫RemoteViews->apply()來進行檢視一繫結,證實了我們上一篇文章的推測。
後面的就將View加入到StatusBarView的NotificationStackScrollLayout中,StatusBarView是在BaseStatusBar->start()方法中