Android通知显示framework流程解析

本文基于Android 14源码,来探究通知的发送过程中所发生的步骤,以此来揭开通知的面纱。

1、通知的发送

通常我们会像下面这样来发送一个通知

Intent clickIntent = new Intent(mContext, Settings.MobileNetworkListActivity.class);
TaskStackBuilder stackBuilder =TaskStackBuilder.create(mContext).addNextIntent(clickIntent);
PendingIntent contentIntent =stackBuilder.getPendingIntent(0 /* requestCode */,PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);Notification.Builder builder =new Notification.Builder(mContext, SIM_SETUP_CHANNEL_ID).setContentTitle(title).setContentText(text).setContentIntent(contentIntent).setSmallIcon(R.drawable.ic_sim_alert).setAutoCancel(true);
mNotificationManager.notify(SIM_ACTIVATION_NOTIFICATION_ID, builder.build());

其实最终都是通过notificationManager.notify(id, notification)来把通知发送出去,

2 NotificationManager#notify

//framework/base/core/java/android/app/NotificationManager.java
*** the same id has already been posted by your application and has not yet been canceled, it* will be replaced by the updated information.** @param id An identifier for this notification unique within your*        application.* @param notification A {@link Notification} object describing what to show the user. Must not*        be null.*/
public void notify(int id, Notification notification)
{notify(null, id, notification);
}public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{INotificationManager service = getService();String pkg = mContext.getPackageName();try {if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,fixNotification(notification), user.getIdentifier());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}

NotificationManager中,主要是拿到NotificationManagerService,然后调用其enqueueNotificationWithTag。

注意这里的参数:

pkg: 是发送通知的所在的包名

tag:此通知的字符串标识符

id:就是发送通知传入的int类型的id

notification:是传入的通过Notification.Builder构造的,经过fixNotification之后的notification

来看看这个fixNotification是做啥的。

private Notification fixNotification(Notification notification) {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);}}notification.reduceImageSizes(mContext);return Builder.maybeCloneStrippedForDelivery(notification);
}

一遍看下来,fixNotification主要是对通知的soundsmallIcon进行了处理,然后对通知的大小进行裁剪。

我们重点看下reduceImageSizes,因为如果通知非常多的话,通知里面icon通常也会很多,可能会带来性能问题。

void reduceImageSizes(Context context) {if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {return;}boolean isLowRam = ActivityManager.isLowRamDeviceStatic();if (mSmallIcon != null// Only bitmap icons can be downscaled.&& (mSmallIcon.getType() == Icon.TYPE_BITMAP|| mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {Resources resources = context.getResources();int maxSize = resources.getDimensionPixelSize(isLowRam ? R.dimen.notification_small_icon_size_low_ram: R.dimen.notification_small_icon_size);mSmallIcon.scaleDownIfNecessary(maxSize, maxSize);}if (mLargeIcon != null || largeIcon != null) {Resources resources = context.getResources();Class<? extends Style> style = getNotificationStyle();int maxSize = resources.getDimensionPixelSize(isLowRam? R.dimen.notification_right_icon_size_low_ram: R.dimen.notification_right_icon_size);if (mLargeIcon != null) {mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);}if (largeIcon != null) {largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);}}reduceImageSizesForRemoteView(contentView, context, isLowRam);reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);reduceImageSizesForRemoteView(bigContentView, context, isLowRam);extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
}private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,boolean isLowRam) {if (remoteView != null) {Resources resources = context.getResources();int maxWidth = resources.getDimensionPixelSize(isLowRam? R.dimen.notification_custom_view_max_image_width_low_ramR.dimen.notification_custom_view_max_image_width);int maxHeight = resources.getDimensionPixelSize(isLowRam? R.dimen.notification_custom_view_max_image_height_low_ramR.dimen.notification_custom_view_max_image_height);remoteView.reduceImageSizes(maxWidth, maxHeight);}
}

首先对于icon来说,如果mSmallIcon不为空的话,通常情况下icon最大限制是48dp,也就是Bitmap宽高最大是48dp。后面对于mLargeIcon同样如此。

然后是RemoteView,对于contentView、headsUpContentView、bigContentView处理基本是一样的,它们的宽度限制是450dp,高度是284dp。

接下来,继续看NotificationManagerService里面的流程。

3 Fwk通知处理

3.1 enqueueNotification

//framework/base/services/core/java/com/android/server/notification/NotificationManagerService.java
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,Notification notification, int userId) throws RemoteException {enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),Binder.getCallingPid(), tag, id, notification, userId,false /* byForegroundService */);
}void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification,int incomingUserId, boolean postSilently, boolean byForegroundService) {PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);boolean enqueued = false;try {enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid,tag, id, notification, incomingUserId,postSilently, tracker, byForegroundService);} finally {if (!enqueued) {tracker.cancel();}}
}/*** @return True if we successfully processed the notification and handed off the task of* enqueueing it to a background thread; false otherwise.*/
private boolean enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId,boolean postSilently, PostNotificationTracker tracker,boolean byForegroundService) {fixNotification(notification, pkg, tag, id, userId, notificationUid,policy, stripUijFlag);final StatusBarNotification n = new StatusBarNotification(pkg, opPkg, id, tag, notificationUid, callingPid, notification,user, null, System.currentTimeMillis());final NotificationRecord r = new NotificationRecord(getContext(), n, channel);r.setIsAppImportanceLocked(mPermissionHelper.isPermissionUserSet(pkg, userId));r.setPostSilently(postSilently);r.setFlagBubbleRemoved(false);r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId);r.setImportanceFixed(isImportanceFixed);mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, tracker));return true;
}

enqueueNotificationInternal代码比较多。

首先,fixNotification里面主要是对通知的flags等进行适配,比如处理彩色通知权限 、全屏通知需要权限、多媒体类型的通知,权限检测、对Remote views 尺寸检查,太大的话设置为空等等。

然后创建状态栏通知对象StatusBarNotification,然后创建NotificationRecord,并将StatusBarNotification对象,最后,通过post来调动EnqueueNotificationRunnable。

protected class EnqueueNotificationRunnable implements Runnable {@Overridepublic void run() {boolean enqueued = false;try {enqueued = enqueueNotification();} finally {if (!enqueued) {mTracker.cancel();}}}
}

EnqueueNotificationRunnable调用的是enqueueNotification。

final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();/*** @return True if we successfully enqueued the notification and handed off the task of* posting it to a background thread; false otherwise.*/
private boolean enqueueNotification() {synchronized (mNotificationLock) {mEnqueuedNotifications.add(r);scheduleTimeoutLocked(r); //如果有设置自动取消通知时间的final StatusBarNotification n = r.getSbn();if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());NotificationRecord old = mNotificationsByKey.get(n.getKey());final int callingUid = n.getUid();final int callingPid = n.getInitialPid();final Notification notification = n.getNotification();final String pkg = n.getPackageName();final int id = n.getId();final String tag = n.getTag();// We need to fix the notification up a little for bubblesupdateNotificationBubbleFlags(r, isAppForeground);// Handle grouped notifications and bail out early if we// can to avoid extracting signals.handleGroupedNotificationLocked(r, old, callingUid, callingPid);mHandler.post(new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),r.getUid(), mTracker));return true;}
}

首先把前面创建的NotificationRecord对象,存入mEnqueuedNotifications中,然后从mEnqueuedNotifications中拿到StatusBarNotification,然后从StatusBarNotification中取出一些参数,接着调用updateNotificationBubbleFlags对bubbles进行适配。

3.2 postNotification

接着通过handleGroupedNotificationLocked对成组通知进行处理,最后调用PostNotificationRunnable。

protected class PostNotificationRunnable implements Runnable {@Overridepublic void run() {boolean posted = false;try {posted = postNotification();} finally {if (!posted) {mTracker.cancel();}}}
}/*** @return True if we successfully processed the notification and handed off the task of* notifying all listeners to a background thread; false otherwise.*/
private boolean postNotification() {//没有发送通知的权限,则为trueboolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);boolean isCallNotification = isCallNotification(pkg, uid);synchronized (mNotificationLock) {NotificationRecord r = null;int N = mEnqueuedNotifications.size();for (int i = 0; i < N; i++) {final NotificationRecord enqueued = mEnqueuedNotifications.get(i);if (Objects.equals(key, enqueued.getKey())) {r = enqueued;break;}}final StatusBarNotification n = r.getSbn();final Notification notification = n.getNotification();boolean isCallNotificationAndCorrectStyle = isCallNotification&& notification.isStyle(Notification.CallStyle.class);if (!(notification.isMediaNotification() ||isCallNotificationAndCorrectStyle)&& (appBanned || isRecordBlockedLocked(r))) {//通知被禁止的mUsageStats.registerBlocked(r);if (DBG) {Slog.e(TAG, "Suppressing notification from package " + pkg);}return false;}//包是挂起状态final boolean isPackageSuspended =isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());r.setHidden(isPackageSuspended);if (isPackageSuspended) {mUsageStats.registerSuspendedByAdmin(r);}//获取旧的通知记录NotificationRecord old = mNotificationsByKey.get(key);// Make sure the SBN has an instance ID for statsd logging.if (old == null || old.getSbn().getInstanceId() == null) {n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId());} else {n.setInstanceId(old.getSbn().getInstanceId());}//获取在通知列表里的索引int index = indexOfNotificationLocked(n.getKey());if (index < 0) {//没找到,新加通知mNotificationList.add(r);mUsageStats.registerPostedByApp(r);mUsageStatsManagerInternal.reportNotificationPosted(r.getSbn().getOpPkg(),r.getSbn().getUser(), mTracker.getStartTime());final boolean isInterruptive = isVisuallyInterruptive(null, r);r.setInterruptive(isInterruptive);r.setTextChanged(isInterruptive);} else {//有旧的,更新通知old = mNotificationList.get(index);  // Potentially *changes* oldmNotificationList.set(index, r);mUsageStats.registerUpdatedByApp(r, old);mUsageStatsManagerInternal.reportNotificationUpdated(r.getSbn().getOpPkg(),r.getSbn().getUser(), mTracker.getStartTime());// Make sure we don't lose the foreground service state.notification.flags |=old.getNotification().flags & FLAG_FOREGROUND_SERVICE;r.isUpdate = true;final boolean isInterruptive = isVisuallyInterruptive(old, r);r.setTextChanged(isInterruptive);}mNotificationsByKey.put(n.getKey(), r);// Ensure if this is a foreground service that the proper additional// flags are set.if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {notification.flags |= FLAG_NO_CLEAR; //前台服务通知,添加必要的flag}//排序mRankingHelper.extractSignals(r);mRankingHelper.sort(mNotificationList);final int position = mRankingHelper.indexOf(mNotificationList, r);int buzzBeepBlinkLoggingCode = 0;if (!r.isHidden()) {buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r); //获取声音震动闪灯的状态,封装成一个值返回}if (notification.getSmallIcon() != null) {NotificationRecordLogger.NotificationReported maybeReport =mNotificationRecordLogger.prepareToLogNotificationPosted(r, old,position, buzzBeepBlinkLoggingCode,getGroupInstanceId(r.getSbn().getGroupKey()));//更新状态栏通知notifyListenersPostedAndLogLocked(r, old, mTracker, maybeReport);posted = true;StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;//旧的通知为空或者新旧通知的group不一样。并且新的非紧急通知if (oldSbn == null|| !Objects.equals(oldSbn.getGroup(), n.getGroup())|| oldSbn.getNotification().flags != n.getNotification().flags) {if (!isCritical(r)) {mHandler.post(() -> {synchronized (mNotificationLock) {//发布新的状态栏通知mGroupHelper.onNotificationPosted(n, hasAutoGroupSummaryLocked(n));}});}}} else {Slog.e(TAG, "Not posting notification without small icon: " + notification);//通知没有设置smallIcon,这是有问题的if (old != null && !old.isCanceled) {mListeners.notifyRemovedLocked(r,NotificationListenerService.REASON_ERROR, r.getStats());mHandler.post(new Runnable() {@Overridepublic void run() {mGroupHelper.onNotificationRemoved(n);}});}// ATTENTION: in a future release we will bail out here// so that we do not play sounds, show lights, etc. for invalid// notificationsSlog.e(TAG, "WARNING: In a future release this will crash the app: "+ n.getPackageName());}} finally {//末尾从队列集合里移除当前已处理的通知int N = mEnqueuedNotifications.size();for (int i = 0; i < N; i++) {final NotificationRecord enqueued = mEnqueuedNotifications.get(i);if (Objects.equals(key, enqueued.getKey())) {mEnqueuedNotifications.remove(i);break;}}}
}@GuardedBy("mNotificationLock")
final ArrayList<NotificationRecord> mNotificationList = new ArrayList<>();@GuardedBy("mNotificationLock")
final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>();

3.2.1 通知禁止

postNotification中,首先会对比StatusBarNotification中key是否一致,然后判断如果通知被禁止,就返回。

3.2.2 加入或更新通知列表

接着从通知列表mNotificationList获取在通知列表里的索引,如果没有找到,说明是新通知,把它加入mNotificationList中,否则存在旧的通知,那么就把新通知的NotificationRecord更新到通知列表mNotificationList中。

然后把以通知的key为键,NotificationRecord为值,存到mNotificationsByKey中。

3.2.3 排序

然后对通知进行排序,并取出排序后通知的位置。

3.2.4 获取声音震动闪灯的状态

确定通知是否应该发出声音,震动,闪烁

/*** Determine whether this notification should attempt to make noise, vibrate, or flash the LED* @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)*/
int buzzBeepBlinkLocked(NotificationRecord record) {//汽车并且通知效果不可用if (mIsAutomotive && !mNotificationEffectsEnabledForAutomotive) {return 0;}boolean buzz = false;boolean beep = false;boolean blink = false;final String key = record.getKey();//重要性大于等于默认值3的才有效果的// Should this notification make noise, vibe, or use the LED?final boolean aboveThreshold =mIsAutomotive? record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT: record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;// Remember if this notification already owns the notification channels.boolean wasBeep = key != null && key.equals(mSoundNotificationKey);boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);// These are set inside the conditional if the notification is allowed to make noise.boolean hasValidVibrate = false;boolean hasValidSound = false;boolean sentAccessibilityEvent = false;// 如果通知将出现在状态栏中,它应该发送一个可访问性事件// If the notification will appear in the status bar, it should send an accessibility eventfinal boolean suppressedByDnd = record.isIntercepted()&& (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0;if (!record.isUpdate&& record.getImportance() > IMPORTANCE_MIN&& !suppressedByDnd&& isNotificationForCurrentUser(record)) {sendAccessibilityEvent(record);sentAccessibilityEvent = true;}if (aboveThreshold && isNotificationForCurrentUser(record)) {if (mSystemReady && mAudioManager != null) {Uri soundUri = record.getSound();hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);VibrationEffect vibration = record.getVibration();// Demote sound to vibration if vibration missing & phone in vibration mode.if (vibration == null&& hasValidSound&& (mAudioManager.getRingerModeInternal()== AudioManager.RINGER_MODE_VIBRATE)&& mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0;vibration = mVibratorHelper.createFallbackVibration(insistent);}hasValidVibrate = vibration != null;boolean hasAudibleAlert = hasValidSound || hasValidVibrate;if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {if (!sentAccessibilityEvent) {sendAccessibilityEvent(record);sentAccessibilityEvent = true;}if (DBG) Slog.v(TAG, "Interrupting!");//持续更新,就是连续的通知boolean isInsistentUpdate = isInsistentUpdate(record);if (hasValidSound) {if (isInsistentUpdate) { // 声音为true// don't reset insistent sound, it's jarringbeep = true;} else {if (isInCall()) {playInCallNotification();beep = true;} else {beep = playSound(record, soundUri); //播放声音返回结果}if (beep) {mSoundNotificationKey = key;}}}final boolean ringerModeSilent =mAudioManager.getRingerModeInternal()== AudioManager.RINGER_MODE_SILENT;//非通话中,有设置震动,非静音模式if (!isInCall() && hasValidVibrate && !ringerModeSilent) {if (isInsistentUpdate) {buzz = true;} else {//震动buzz = playVibration(record, vibration, hasValidSound);if (buzz) {mVibrateNotificationKey = key;}}}// Try to start flash notification event whenever an audible and non-suppressed// notification is receivedmAccessibilityManager.startFlashNotificationEvent(getContext(),AccessibilityManager.FLASH_REASON_NOTIFICATION,record.getSbn().getPackageName());} else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {hasValidSound = false;}}}// If a notification is updated to remove the actively playing sound or vibrate,// cancel that feedback nowif (wasBeep && !hasValidSound) {  //旧通知有声音,新的没有clearSoundLocked(); //停止声音播放}if (wasBuzz && !hasValidVibrate) {clearVibrateLocked();}// light// release the lightboolean wasShowLights = mLights.remove(key); //旧的light状态if (canShowLightsLocked(record, aboveThreshold)) { //是否可以闪灯mLights.add(key); //加入集合updateLightsLocked(); //闪灯或者灭灯if (mUseAttentionLight && mAttentionLight != null) {mAttentionLight.pulse(); ///如果支持提示灯并且不为空的话}blink = true;} else if (wasShowLights) {updateLightsLocked();}//3种状态封装成一个intfinal int buzzBeepBlink = (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0);if (buzzBeepBlink > 0) {// Ignore summary updates because we don't display most of the information.if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) {if (DEBUG_INTERRUPTIVENESS) {Slog.v(TAG, "INTERRUPTIVENESS: "+ record.getKey() + " is not interruptive: summary");}} else if (record.canBubble()) {if (DEBUG_INTERRUPTIVENESS) {Slog.v(TAG, "INTERRUPTIVENESS: "+ record.getKey() + " is not interruptive: bubble");}} else {record.setInterruptive(true);if (DEBUG_INTERRUPTIVENESS) {Slog.v(TAG, "INTERRUPTIVENESS: "+ record.getKey() + " is interruptive: alerted");}}MetricsLogger.action(record.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ALERT).setType(MetricsEvent.TYPE_OPEN).setSubtype(buzzBeepBlink));EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);}record.setAudiblyAlerted(buzz || beep); //为true记录下震动或声音的开始时间,为false,时间为-1return buzzBeepBlink;
}

3.2.5 更新通知

通知的更新分有SmallIcon的通知和没有的情况。

1、有SmallIcon通知:

/*** Asynchronously notify all listeners about a posted (new or updated) notification. This* should be called from {@link PostNotificationRunnable} to "complete" the post (since SysUI is* one of the NLSes, and will display it to the user).** <p>This method will call {@link PostNotificationTracker#finish} on the supplied tracker* when every {@link NotificationListenerService} has received the news.** <p>Also takes care of removing a notification that has been visible to a listener before,* but isn't anymore.*/
@GuardedBy("mNotificationLock")
private void notifyListenersPostedAndLogLocked(NotificationRecord r, NotificationRecord old,@NonNull PostNotificationTracker tracker,@Nullable NotificationRecordLogger.NotificationReported report) {List<Runnable> listenerCalls = mListeners.prepareNotifyPostedLocked(r, old, true);mHandler.post(() -> {for (Runnable listenerCall : listenerCalls) {listenerCall.run();}long postDurationMillis = tracker.finish();if (report != null) {report.post_duration_millis = postDurationMillis;mNotificationRecordLogger.logNotificationPosted(report);}});
}

首先调用notifyListenersPostedAndLogLocked更新状态栏通知。

/*** "Prepares" to notify all listeners about the posted notification.** <p>This method <em>does not invoke</em> the listeners; the caller should post each* returned {@link Runnable} on a suitable thread to do so.** @param notifyAllListeners notifies all listeners if true, else only notifies listeners*                           targeting <= O_MR1* @return A list of {@link Runnable} operations to notify all listeners about the posted* notification.*/
@VisibleForTesting
@GuardedBy("mNotificationLock")
List<Runnable> prepareNotifyPostedLocked(NotificationRecord r,NotificationRecord old, boolean notifyAllListeners) {if (isInLockDownMode(r.getUser().getIdentifier())) {return new ArrayList<>();}ArrayList<Runnable> listenerCalls = new ArrayList<>();try {// Lazily initialized snapshots of the notification.StatusBarNotification sbn = r.getSbn();StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;TrimCache trimCache = new TrimCache(sbn);for (final ManagedServiceInfo info : getServices()) {boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);boolean oldSbnVisible = (oldSbn != null)&& isVisibleToListener(oldSbn, old.getNotificationType(), info);// This notification hasn't been and still isn't visible -> ignore.if (!oldSbnVisible && !sbnVisible) {continue;}// If the notification is hidden, don't notifyPosted listeners targeting < P.// Instead, those listeners will receive notifyPosted when the notification is// unhidden.if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {continue;}// If we shouldn't notify all listeners, this means the hidden state of// a notification was changed.  Don't notifyPosted listeners targeting >= P.// Instead, those listeners will receive notifyRankingUpdate.if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {continue;}final NotificationRankingUpdate update = makeRankingUpdateLocked(info);// This notification became invisible -> remove the old one.if (oldSbnVisible && !sbnVisible) {final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();listenerCalls.add(() -> notifyRemoved(info, oldSbnLightClone, update, null, REASON_USER_STOPPED));continue;}// Grant access before listener is notifiedfinal int targetUserId = (info.userid == UserHandle.USER_ALL)? UserHandle.USER_SYSTEM : info.userid;updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);mPackageManagerInternal.grantImplicitAccess(targetUserId, null /* intent */,UserHandle.getAppId(info.uid),sbn.getUid(),false /* direct */, false /* retainOnUpdate */);final StatusBarNotification sbnToPost = trimCache.ForListener(info);listenerCalls.add(() -> notifyPosted(info, sbnToPost, update));}} catch (Exception e) {Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);}return listenerCalls;
}

在notifyListenersPostedAndLogLocked中,主要是对是添加、移除通知等进行判断:

如果旧通知可见、新通知不可见,就将notifyRemoved加入listenerCalls队列

如果不需要移除,就是添加通知,将notifyPosted加入listenerCalls队列

然后notifyListenersPostedAndLogLocked中,调用listenerCall.run(),执行notifyRemoved或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 (android.os.DeadObjectException ex) {Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);} catch (RemoteException ex) {Slog.e(TAG, "unable to notify listener (posted): " + info, ex);}
}

notifyPosted中是先拿到INotificationListener,并调用它的onNotificationPosted。

在更新通知之后,如果旧的通知为空或者新旧通知的group不一样。并且新的非紧急通知,发布新的状态栏通知。

2、没有SmallIcon:

如果通知没有设置smallIcon,这是有问题的, 移除旧的通知。

最后,末尾从队列集合里移除当前已处理的通知。

4 INotificationListener的目的地

mService是从mService = asInterface(binder)取到的,然后保存在ManagedServiceInfo中。

ServiceConnection serviceConnection = new ServiceConnection() {IInterface mService;@Overridepublic void onServiceConnected(ComponentName name, IBinder binder) {Slog.v(TAG,  userid + " " + getCaption() + " service connected: " + name);boolean added = false;ManagedServiceInfo info = null;synchronized (mMutex) {mServicesRebinding.remove(servicesBindingTag);try {mService = asInterface(binder);info = newServiceInfo(mService, name,userid, isSystem, this, targetSdkVersion, uid);binder.linkToDeath(info, 0);added = mServices.add(info);} catch (RemoteException e) {Slog.e(TAG, "Failed to linkToDeath, already dead", e);}}if (added) {onServiceAdded(info);}}
}private ManagedServiceInfo newServiceInfo(IInterface service,ComponentName component, int userId, boolean isSystem, ServiceConnection connection,int targetSdkVersion, int uid) {return new ManagedServiceInfo(service, component, userId, isSystem, connection,targetSdkVersion, uid);
}

这里INotificationListener是从ManagedServiceInfo的service中取到的,

public ManagedServices(Context context, Object mutex, UserProfiles userProfiles,IPackageManager pm) {mContext = context;mMutex = mutex;mUserProfiles = userProfiles;mPm = pm;mConfig = getConfig();mApprovalLevel = APPROVAL_BY_COMPONENT;mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}abstract protected Config getConfig();

从NotificationManagerService中拿到getConfig(),因此asInterface调用的是NotificationManagerService的asInterface。

//framework/base/services/core/java/com/android/server/notification/NotificationManagerService.java
public class NotificationAssistants extends ManagedServices {@Overrideprotected Config getConfig() {Config c = new Config();c.caption = "notification assistant";c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;c.clientLabel = R.string.notification_ranker_binding_label;return c;}@Overrideprotected IInterface asInterface(IBinder binder) {return INotificationListener.Stub.asInterface(binder);}
}

可看到实现类是NotificationAssistantService

//framework/base/core/java/android/service/notification/NotificationAssistantService.java
/*** The {@link Intent} that must be declared as handled by the service.*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE= "android.service.notification.NotificationAssistantService";public abstract class NotificationAssistantService extendsNotificationListenerService {}

而 NotificationAssistantService又是继承的NotificationListenerService,所以3.2.4中的onNotificationPosted走到了这里。

//framework/base/core/java/android/service/notification/NotificationListenerService.java
public abstract class NotificationListenerService extends Service {protected class NotificationListenerWrapper extends INotificationListener.Stub {@Overridepublic void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,NotificationRankingUpdate update) {// protect subclass from concurrent modifications of (@link mNotificationKeys}.synchronized (mLock) {applyUpdateLocked(update);if (sbn != null) {SomeArgs args = SomeArgs.obtain();args.arg1 = sbn;args.arg2 = mRankingMap;mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,args).sendToTarget();} else {// still pass along the ranking map, it may contain other informationmHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,mRankingMap).sendToTarget();}}}}case MSG_ON_NOTIFICATION_POSTED: {SomeArgs args = (SomeArgs) msg.obj;StatusBarNotification sbn = (StatusBarNotification) args.arg1;RankingMap rankingMap = (RankingMap) args.arg2;args.recycle();onNotificationPosted(sbn, rankingMap);} break;/*** Implement this method to learn about new notifications as they are posted by apps.** @param sbn A data structure encapsulating the original {@link android.app.Notification}*            object as well as its identifying information (tag and id) and source*            (package name).* @param rankingMap The current ranking map that can be used to retrieve ranking information*                   for active notifications, including the newly posted one.*/public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {onNotificationPosted(sbn);}public void onNotificationPosted(StatusBarNotification sbn) {// optional}@Overridepublic IBinder onBind(Intent intent) {if (mWrapper == null) {mWrapper = new NotificationListenerWrapper();}return mWrapper;}@SystemApipublic void registerAsSystemService(Context context, ComponentName componentName,int currentUser) throws RemoteException {if (mWrapper == null) {mWrapper = new NotificationListenerWrapper();}mSystemContext = context;INotificationManager noMan = getNotificationInterface();mHandler = new MyHandler(context.getMainLooper());mCurrentUser = currentUser;noMan.registerListener(mWrapper, componentName, currentUser);}
}

最终,NotificationListenerService的onNotificationPosted只是一个空实现,那么它的子类是谁呢?继续看。

//framework/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
public class NotificationListenerWithPlugins extends NotificationListenerService implements PluginListener<NotificationListenerController> {@Overridepublic void registerAsSystemService(Context context, ComponentName componentName,int currentUser) throws RemoteException {super.registerAsSystemService(context, componentName, currentUser);mPluginManager.addPluginListener(this, NotificationListenerController.class);}
}

SystemUI中的NotificationListenerWithPlugins实现了NotificationListenerService。

//framework/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
public class NotificationListener extends NotificationListenerWithPlugins implementsPipelineDumpable {public void registerAsSystemService() {try {registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),UserHandle.USER_ALL);} catch (RemoteException e) {Log.e(TAG, "Unable to register notification listener", e);}}@Overridepublic void onNotificationPosted(final StatusBarNotification sbn,final RankingMap rankingMap) {}
}

然后NotificationListener又继承了NotificationListenerWithPlugins。

//framework/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
override fun initialize(centralSurfaces: CentralSurfaces,presenter: NotificationPresenter,listContainer: NotificationListContainer,stackController: NotifStackController,notificationActivityStarter: NotificationActivityStarter,bindRowCallback: NotificationRowBinderImpl.BindRowCallback) {notificationListener.registerAsSystemService()
}

继续看SystemUI里面的listener是如何注册的。

//framework/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@Override
public void start() {setUpPresenter()
}private void setUpPresenter() {mNotificationsController.initialize(this,mPresenter,mNotifListContainer,mStackScrollerController.getNotifStackController(),mNotificationActivityStarter,mCentralSurfacesComponent.getBindRowCallback());
}

最终是在CentralSurfacesImpl在start的时候注册的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/428808.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cypress安装与启动(开始学习记录)

一 Cypress安装 使用npm安装 1.查看node.js npm的版本&#xff0c;输入 npm --version 和 node --version&#xff0c;node.js没安装的可以去中文网下载最新稳定版安装&#xff0c;npm不建议升级到最新版本&#xff0c;会导致安装Cypress时Error: Cannot find module ansi-st…

一个基于 laravel 和 amis 开发的后台框架, 友好的组件使用体验,可轻松实现复杂页面(附源码)

前言 随着互联网应用的发展&#xff0c;后台管理系统的复杂度不断增加&#xff0c;对于开发者而言&#xff0c;既要系统的功能完备&#xff0c;又要追求开发效率的提升。然而&#xff0c;传统的开发方式往往会导致大量的重复劳动&#xff0c;尤其是在构建复杂的管理页面时。有…

MQ入门(4)

Erlang&#xff1a;面向高并发的 单机的吞吐量就是并发性&#xff1a;Rabbitmq是10w左右&#xff08;现实项目中已经足够用了&#xff09;&#xff0c;RocketMQ是10w到20w&#xff0c;Kafka是100w左右。 公司里的并发&#xff08;QPS&#xff09; 大部分的公司每天的QPS大概…

Elionix 电子束曝光系统

Elionix 电子束曝光系统 - 上海纳腾仪器有限公司 -

【FreeRTOS】信号量

1.概念 当访问一个共享资源时&#xff0c;两个任务&#xff0c;并发访问出现不一致的问题&#xff0c;需要通过信号量解决 那么信号量是如何解决这个问题的呢&#xff1f; 任务量你可以认为是一把锁&#xff0c;一个任务拿到这个锁之后访问这个临界资源&#xff0c; 其他任务…

如何设计出一个比较全面的测试用例

目录 1. 测试用例的基本要素(不需要执行结果) 2. 测试用例的给我们带来的好处 3. 用例编写步骤 4. 设计测试用例的方法 4.1 基于需求进行测试用例的设计 4.2 具体的设计方法 1.等价类 2.边界值 3.判定表&#xff08;因果图&#xff09; 4.正交表法 5.场景设计法 6.错误猜测…

视频去噪技术分享

视频去噪是一种视频处理技术&#xff0c;旨在从视频帧中移除噪声和干扰&#xff0c;提高视频质量。噪声可能由多种因素引起&#xff0c;包括低光照条件、高ISO设置、传感器缺陷等。视频去噪对于提升视频内容的可视性和可用性至关重要&#xff0c;特别是在安全监控、医疗成像和视…

【MySQL】基础部分——DDL,DML,DQL,DCL,函数,约束,多表查询,事务

个人学习记录&#xff0c;供以后回顾和复习 ubuntu下安装使用1.DDL&#xff0c;DML&#xff0c;DQL&#xff0c;DCLDDL数据库表 DML增改删 DQL条件查询分组查询排序查询分页查询 DCL管理用户权限控制 2.函数字符串函数数值函数日期函数流程函数 3.约束4.多表查询多表关系内连接…

【Git必看系列】—— Git巨好用的神器之git stash篇

应用场景 当我们开发一个新功能时会先从master拉出一个分支dev&#xff0c;然后在这个dev分支下吭哧吭哧的开始写代码开发新功能&#xff0c;就如下代码所示&#xff0c;我们在dev分支下开发Person类的新功能getId() public class Person {private int id;private String nam…

零工市场小程序:推动零工市场建设

人力资源和社会保障部在2024年4月发布了标题为《地方推进零工市场建设经验做法》的文章。 零工市场小程序的功能 信息登记与发布 精准匹配、推送 在线沟通 权益保障 零工市场小程序作为一个找零工的渠道&#xff0c;在往后随着技术的发展和政策的支持下&#xff0c;功能必然…

C++——初步认识C++和namespace的用法

1.编程语言排行榜 我们通过排行可以看出 C在变成语言中还是占据着重要的地位 2.C在工作领域中的应用 1.PC客户端开发。⼀般是开发Windows上的桌面软件&#xff0c;比如WPS之类的&#xff0c;技术栈的话⼀般是C和 QT&#xff0c;QT 是⼀个跨平台的 C图形用户界面&#xff08;G…

表格标记<table>

一.表格标记、 1table&#xff1a;表格标记 2.caption:表单标题标记 3.tr:表格行标记 4.td:表格中数据单元格标记 5.th:标题单元格 table标记是表格中最外层标记&#xff0c;tr表示表格中的行标记&#xff0c;一对<tr>表示表格中的一行&#xff0c;在<tr>中可…

Scrapy爬虫IP代理池:提升爬取效率与稳定性

在互联网时代&#xff0c;数据就是新的黄金。无论是企业还是个人&#xff0c;数据的获取和分析能力都显得尤为重要。而在众多数据获取手段中&#xff0c;使用爬虫技术无疑是一种高效且广泛应用的方法。然而&#xff0c;爬虫在实际操作中常常会遇到IP被封禁的问题。为了解决这个…

小程序构建npm失败

小程序构建npm失败 项目工程结构说明解决方法引入依赖导致的其他问题 今天在初始化后的小程序中引入TDesign组件库&#xff0c;构建npm时报错。 项目工程结构说明 初始化后的项目中&#xff0c;包含miniprogram文件夹和一些项目配置文件&#xff0c;在project.config.json文件中…

【TypeScript入坑】TypeScript 的复杂类型「Interface 接口、class类、Enum枚举、Generics泛型、类型断言」

TypeScript入坑 Interface 接口简介接口合并TS 强校验Interface 里支持方法的写入class 类应用接口接口之间互相继承接口定义函数interface 与 type 的异同小案例 class 类类的定义与继承类的访问类型构造器 constructor静态属性&#xff0c;Setter 和 Getter做个小案例抽象类 …

fiddler抓包06_抓取https请求(chrome)

课程大纲 首次安装Fiddler&#xff0c;抓https请求&#xff0c;除打开抓包功能&#xff08;F12&#xff09;还需要&#xff1a; ① Fiddler开启https抓包 ② Fiddler导出证书&#xff1b; ③ 浏览器导入证书。 否则&#xff0c;无法访问https网站&#xff08;如下图&#xff0…

(十五)、把自己的镜像推送到 DockerHub

文章目录 1、登录Docker Hub2、标记&#xff08;Tag&#xff09;镜像3、推送&#xff08;Push&#xff09;镜像4、查看镜像5、下载镜像6、设置镜像为公开或者私有 1、登录Docker Hub 需要科学上网 https://hub.docker.com/ 如果没有账户&#xff0c;需要先注册一个。登录命令如…

docker搭建个人网盘,支持多种格式,还能画图,一键部署

1&#xff09;效果 2&#xff09;步骤 2.1&#xff09;docker安装 docker脚本 bash <(curl -sSL https://cdn.jsdelivr.net/gh/SuperManito/LinuxMirrorsmain/DockerInstallation.sh)docker-compose脚本 curl -L "https://github.com/docker/compose/releases/late…

技术成神之路:设计模式(十四)享元模式

介绍 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构性设计模式&#xff0c;旨在通过共享对象来有效地支持大量细粒度的对象。 1.定义 享元模式通过将对象状态分为内部状态&#xff08;可以共享&#xff09;和外部状态&#xff08;不可共享&#xff09;&#xf…

基于SpringBoot+Vue的高校竞赛管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…