前言:
国产ROM定制化比较复杂,最近在做Notification的适配工作。了解Notification源码走向,才知道国产ROM系统对Notification拦截点,找到相应办法绕过。话题有些绕远了,这里还是讲解Notification源码走向。
本篇,介绍,如何从应用层,到远程的Server进程(系统进程),再到SystemUI(系提APP)渲染展示Notification。
1. 应用层中发出关于Notification的信息
android.app.NotificationManager
类中:
创建好的Notification,调用notify()
加入
public void notify(int id, Notification notification){
notify(null, id, notification);
}
接下来,调用多态同名方法:
public void notify(String tag, int id, Notification notification){
notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}
接下来,调用notifyAsUser
:
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user){
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
//为Notification添加一些关于程序的信息
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
//为Notifcation设置SmallIcon
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 {
//远程调用 NotificationManagerService
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();
}
}
先看下,Notification类中的addFieldsFromContext()
:添加一些程序的基本信息
/** * @hide */
public static void addFieldsFromContext(Context context, Notification notification) {
addFieldsFromContext(context.getApplicationInfo(), context.getUserId(), notification);
}
/** * @hide */
public static void addFieldsFromContext(ApplicationInfo ai, int userId,
Notification notification) {
notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
notification.extras.putInt(EXTRA_ORIGINATING_USERID, userId);
}
再来看下getService():
/** @hide */
static public INotificationManager getService(){
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
可知,INotifcationManger是一个aidl生成的java类,通过binder调用到对应的远程服务中。
2. 远程的Server进程
在com.android.server.notification.NotificationManagerService
类中:
private final IBinder mService = new INotificationManager.Stub() {
//.....省略部分源码
/** * 远程客户端,跨进程调用到这里。 */
@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);
}
}
接下来,看下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) {
//.... 检查Notification的个数限制,和相应Pending intents白名单的源码,这里省略
//将Notification相关信息到StatusBarNotification中
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
user);
final NotificationRecord r = new NotificationRecord(getContext(), n);
mHandler.post(new EnqueueNotificationRunnable(userId, r));
}
接下来,在看下EnqueueNotificationRunnable类:
private class EnqueueNotificationRunnable implements Runnable {
private final NotificationRecord r;
private final int userId;
EnqueueNotificationRunnable(int userId, NotificationRecord r) {
this.userId = userId;
this.r = r;
};
@Override
public void run() {
synchronized (mNotificationList) {
final StatusBarNotification n = r.sbn;
//...app拦截规则,设置Notication为前台服务的flag,这里省略对应源码。
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());
}
// 通知status bar显示该notification,确认是否需要声音,震动和闪光,如果需要,那么就发出声音,震动和闪光
buzzBeepBlinkLocked(r);
}
}
}
接下来,继续查看在NotificationManagerService.NotificationListeners类中:
public class NotificationListeners extends ManagedServices {
//....省略部分源码
/** * 异步通知所有监听器,关于新的Notification * * <p> * Also takes care of removing a notification that has been visible to a listener before, * but isn't anymore. */
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
// Lazily initialized snapshots of the notification.
TrimCache trimCache = new TrimCache(sbn);
// for循环方式,跨进程,通知到每一个INotificationLister
for (final ManagedServiceInfo info : mServices) {
boolean sbnVisible = isVisibleToListener(sbn, info);
boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
//省略Notification不可见检查操作,和更新,这里省略该源码
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(new Runnable() {
@Override
public void run() {
notifyPosted(info, sbnToPost, update);
}
});
}
}
}
接下来,查看NotificationManagerService中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);
}
}
INotificationListener 是一个aidl生成Java类,用于远程调用。实际上INotificationListener对象是NotificationListenerSevice类中的NotificationListenerWrapper对象。详情,查看Android 7.0 NotificationListenerService源码分析
在来看一下NotificationListeners,是一个用于保持追踪监听器的MangedServices对象。 那INotificationLister又是如何添加进去的呢?
搜索NotificationManagerService会发现,
private final IBinder mService = new INotificationManager.Stub() {
@Override
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
enforceSystemOrSystemUI("INotificationManager.registerListener");
mListeners.registerService(listener, component, userid);
}
}
顺带来,查看下buzzBeepBlinkLocked()
: 对Notification各种设置参数
@VisibleForTesting
void buzzBeepBlinkLocked(NotificationRecord record) {
boolean buzz = false;
boolean beep = false;
boolean blink = false;
final Notification notification = record.sbn.getNotification();
//.......省略部分源码
// light
// release the light
boolean wasShowLights = mLights.remove(key);
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
&& ((record.getSuppressedVisualEffects()
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
mLights.add(key);
updateLightsLocked();
if (mUseAttentionLight) {
mAttentionLight.pulse();
}
blink = true;
} else if (wasShowLights) {
updateLightsLocked();
}
if (buzz || beep || blink) {
if (((record.getSuppressedVisualEffects()
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {
if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
} else {
EventLogTags.writeNotificationAlert(key,
buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
mHandler.post(mBuzzBeepBlinked);
}
}
}
3. SystemUI渲染展示Notification
SystemUI系统app中BaseStatubar类:
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);
}
}
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() {
// ... 省略部分源码
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap, null /* oldEntry */);
}
}
});
}
}
};
接下来,查看addNotification()
:
该方法是一个抽象方法,在PhoenStatubar中具体实现:
@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();
}
接下来,查看BaseStatuBar的addNotificationViews()
:
protected void addNotificationViews(Entry entry, RankingMap ranking) {
if (entry == null) {
return;
}
// Add the expanded view and icon.
mNotificationData.add(entry, ranking);
updateNotifications();
}
将通知添加到NotificationData中,调用更新方法。
接下来,查看updateNotifications()
:
@Override
protected void updateNotifications() {
mNotificationData.filterAndSort();
updateNotificationShade();
mIconController.updateNotificationIcons(mNotificationData);
}
将通知排序,更新通知栏,和通知栏的icon。
接下来,查看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);
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);
}
}
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.
mStackScroller.changeViewPosition(targetChild, i);
}
j++;
}
// clear the map again for the next usage
mTmpChildOrderMap.clear();
updateRowStates();
updateSpeedbump();
updateClearAll();
updateEmptyShadeView();
updateQsExpansionEnabled();
mShadeUpdates.check();
}
一个通知对应一个ExpandableNotificationRow,添加到NotificationStackScrollLayout类。
接下来,看下StatusBarIconController类中updateNotificationIcons()
public void updateNotificationIcons(NotificationData notificationData) {
mNotificationIconAreaController.updateNotificationIcons(notificationData);
}
接下来,看下NotificationIconAreaController类中updateNotificationIcons()
更新显示出来Notification
/** * Updates the notifications with the given list of notifications to display. */
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);
}
}
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);
}
}
// Re-sort notification icons
final int childCount = mNotificationIcons.getChildCount();
for (int i = 0; i < childCount; i++) {
View actual = mNotificationIcons.getChildAt(i);
StatusBarIconView expected = toShow.get(i);
if (actual == expected) {
continue;
}
mNotificationIcons.removeView(expected);
mNotificationIcons.addView(expected, i);
}
applyNotificationIconsTint();
}
总结一下,Notification的流程走向:
App运用层:
NotificationManager.notify
–>NotificationManager.notifyAsUser()
–>INotificationManager.enqueueNotificationWithTag()
Fragmewrok框架中远程server进程:
NotificationManagerService.enqueueNotificationWithTag()
–>NotificationManagerService.enqueueNotificationInternal()
–>NotificationManagerService.EnqueueNotificationRunnable
–>NotificationManagerService.PostNotificationRunnable
–>NotificationManagerService.NotificationListeners.notifyPostedLocked()
–>NotificationManagerService.NotificationListeners.notifyPosted()
SystemUI.apk中:
BaseStatusBar.INotificationListener.onNotificationPosted()
–>PhoneStatusBar.addNotification()
–>BaseStatusBar.addNotificationViews()
–>PhoneStatusBar.updateNotifications()
资源参考: