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.xdnf.cn/news/1541565.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

AI大模型基础概念

什么是人工智能&#xff1f; 人工智能 (AI) 是一种使计算机和机器能够模拟人类智能和解决问题能力的技术。 人工智能 (AI) 可以单独使用或与其他技术&#xff08;例如&#xff0c;传感器、地理定位、机器人&#xff09;相结合&#xff0c;执行原本需要人类智能或人工干预的任…

【Linux篇】Http协议(1)(笔记)

目录 一、http基本认识 1. Web客户端和服务器 2. 资源 3. URI 4. URL 5. 事务 6. 方法 7. 状态码 二、HTTP报文 1. 报文的流动 &#xff08;1&#xff09;流入源端服务器 &#xff08;2&#xff09;向下游流动 2. 报文语法 三、TCP连接 1. TCP传输方式 2. TCP连…

细说渗透测试:阶段、流程、工具和自动化开源方案

不知有多少“曾梦想仗剑走天涯”的网络与信息安全从业者&#xff0c;是因为渗透测试的初心而步入这个行业的。不过&#xff0c;您是否对渗透测试及其漏洞扫描的相关概念感到既熟悉又陌生呢&#xff1f;您是否觉得自己还停留在从工作实践中积累的感性认识呢&#xff1f;下面&…

AI论文写作PPT思维导图PC小程序开发

AI论文写作PPT思维导图PC小程序开发 AI智能PPT功能 一键生成PPT大纲、一键扩写大纲内容、单独扩写某个大纲内容、一键生成内容关键词、单项内容关键词生成、新增大纲项、修改大纲、删除大纲、选择PPT模板、单页模板一键切换、在线编辑模板&#xff1b;支持导出PPTX、JPEG、&am…

Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 DiffUtil 是一个用于计算两个列表之间差异的实用程序类&#xff0c;它可以帮助 RecyclerView 以更高效的方式更新数据。使用 DiffUtil 可以减少…

《线性代数》笔记

文章目录 1 行列式1.1 克拉默法则1.2 基本性质1.3 余子式 M i j M_{ij} Mij​1.4 代数余子式 A i j ( − 1 ) i j ⋅ M i j A_{ij} (-1)^{ij} \cdot M_{ij} Aij​(−1)ij⋅Mij​1.5 具体型行列式计算&#xff08;化为基本型&#xff09;1.5.1 主对角线行列式&#xff1a;主…

[SAP ABAP] 创建数据元素

我们可以使用事务码SE11创建数据元素 输入要创建的数据类型的名称&#xff0c;然后点击创建 选择数据元素并进行确定 输入简短描述并为数据元素分配一个域&#xff0c;会自动带出数据类型以及长度 创建域可参考该篇文章 创建域https://blog.csdn.net/Hudas/article/details/…

【C++】模拟实现二叉搜索(排序)树

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:实战项目集 ⚙️操作环境:Visual Studio 2022 目录 一.了解项目功能 二.逐步实现项目功能模块及其逻辑详解 &#x1f4cc;实现BSTreeNode类模板 &#x1f38f;构造BSTreeNode类成员变量 &#x1f38f;实现BSTreeNode类构…

胤娲科技:马斯克放大招,盲人也能“开眼看世界”你准备好了吗?

导读前沿&#xff1a; 嘿&#xff0c;朋友们&#xff0c;想象一下&#xff0c;你突然发现自己变成了一部老式黑白电视机的观众&#xff0c;屏幕模糊&#xff0c;色彩全无&#xff0c;是不是感觉人生瞬间失去了“高清”模式&#xff1f; 但别急&#xff0c;科技界的“魔术师”马…

CDVAE项目环境配置

CDVAE环境配置 1. 系统环境2. 设置环境变量3. 配置环境变量4. 安装CDVAE虚拟环境5. 资料下载 1. 系统环境 系统环境&#xff1a;Ubuntu22.04GeForce RTX 3090cuda12.6&#xff08;cuda版本11.1以上均适用&#xff09;。 2. 设置环境变量 先按照CDVAE中描述的设置环境变量。 …

Ubuntu 20.04 内核升级后网络丢失问题的解决过程

在 Ubuntu 系统中&#xff0c;内核升级是一个常见的操作&#xff0c;旨在提升系统性能、安全性和兼容性。然而&#xff0c;有时这一操作可能会带来一些意外的副作用&#xff0c;比如导致网络功能的丧失。 本人本来是想更新 Nvidia 显卡的驱动&#xff0c;使用 ubuntu-drivers …

element-ui 日期选择器禁用某段特定日期

element-ui 日期选择器设置禁用日期 效果图如下: 2024-09-01 到2024-09-18之间的日期都不可选 2024-01-01之前的日期都不可选 官方文档中 picker-options 相关的介绍 实现功能: ​ 某仓库有限制最大可放置资产数量,且资产出借和存放都有记录。由于线下仓库资产出借和购…

c++实现类

Date类的实现-->(里面涉及类&#xff0c;this指针&#xff0c;引用&#xff0c;复用&#xff0c;运算符重载&#xff0c;友元函数&#xff0c;) Date类的实现 本章节我们将根据前面所学过的知识&#xff0c;综合运用来完成一个日期类代码的实现&#xff0c;里面的知识点也能…

yolo自动化项目实例解析(二)ui页面整理 1.78

我们在上一章整理main.py 的if __name__ __main__: 内容还留下面这一段&#xff0c; from PyQt5.QtWidgets import *from lanrenauto.moni.moni import *from PyQt5.QtGui import *app QApplication(sys.argv) # 初始化Qt应用ratio screen_width / 2560 # 分辨率比例# 设…

简单题69.x的平方根 (Java)20240919

问题描述&#xff1a; java代码&#xff1a; class Solution {public int mySqrt(int x) {if (x < 2) {return x; // 0 和 1 的平方根分别是它们自己}int left 2; // 从2开始&#xff0c;因为0和1已经处理了int right x / 2; // 最大可能的平方根不会超过 x / 2int mid;w…

【6DRepNet360全范围头部姿态估计onnxruntime推理】

6DRepNet360全范围头部姿态估计 标题摘要关键词主要贡献方法概述实验结论模型转换和onnxruntime推理模型和代码下载可视化结果代码 这篇论文的核心内容是关于一种用于全范围旋转头部姿态估计的新方法。以下是关键点的总结&#xff1a; 标题 Towards Robust and Unconstrained…

1.Spring-容器-注册

一、Bean和获取Bean &#xff08;1&#xff09;创建IoC容器&#xff1a; SpringApplication.run(类名.class, args); ConfigurableApplicationContext ioc SpringApplication.run(Spring01IocApplication.class, args); &#xff08;2&#xff09;将对象注册到IoC容器中&am…

粘接黑科技标杆专业展会-ASE CHINA 2024 震撼开幕!

2024年9月19日&#xff0c;第27届国际胶粘剂及密封剂展暨第19届国际胶粘带与薄膜展&#xff08;以下简称ASE CHINA 2024&#xff09;在上海新国际博览中心N3-N4-N5馆璀璨揭幕。ASE CHINA作为粘接新材料产业风向标&#xff0c;历经27年的辛苦耕耘&#xff0c;与业界同仁并肩而行…

sql执行流程经典案例分析

现在有联合索引(a,b),select* form tb where b xx group by a执行流程是什么样子的? CREATE TABLE IF NOT EXISTS test(id INT(10) NOT NULL AUTO_INCREMENT COMMENT主键,a INT(10) NULL,b INT(10) NULL,PRIMARY KEY(id),INDEX idx_a_b(a,b))ENGINE INNODB;INSERT INTO test…

828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统

828华为云征文&#xff5c;华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、Grav介绍2.1 CMS介绍2.2 Grav简介2.3 Grav特点2.4 …