当前位置: 首页 > article >正文

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_ram
                        R.dimen.notification_custom_view_max_image_width);
        int maxHeight = resources.getDimensionPixelSize(isLowRam
                          ? R.dimen.notification_custom_view_max_image_height_low_ram
                            R.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 {
    @Override
    public 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 bubbles
        updateNotificationBubbleFlags(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 {
    @Override
    public 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() {
    //没有发送通知的权限,则为true
    boolean 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* old
            mNotificationList.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() {
                    @Override
                    public 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
            // notifications
            Slog.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 event
    final 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 jarring
                        beep = 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 received
                mAccessibilityManager.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 now
    if (wasBeep && !hasValidSound) {  //旧通知有声音,新的没有
        clearSoundLocked(); //停止声音播放
    }
    if (wasBuzz && !hasValidVibrate) {
        clearVibrateLocked();
    }

    // light
    // release the light
    boolean 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种状态封装成一个int
    final 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,时间为-1
    return 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 notified
            final 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;

    @Override
    public 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 {
    @Override
    protected 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;
    }
    
    @Override
    protected 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 extends
    NotificationListenerService {
    
}

而 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 {
        @Override
        public 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 information
                    mHandler.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
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        if (mWrapper == null) {
            mWrapper = new NotificationListenerWrapper();
        }
        return mWrapper;
    }
    
    @SystemApi
    public 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> {
    @Override
    public 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 implements
    PipelineDumpable {
    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);
        }
    }
    
    @Override
    public 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.kler.cn/news/314410.html

相关文章:

  • Python中的魔法:栈与队列的奇妙之旅
  • 大语言模型的发展-OPENBMB
  • ICM20948 DMP代码详解(34)
  • 欧美游戏市场的差异
  • 漏洞复现_永恒之蓝
  • AI助力低代码平台:从智能化到高效交付的全新变革
  • 山体滑坡检测系统源码分享
  • STM32 通过 SPI 驱动 W25Q128
  • 【JS】垃圾回收机制与内存泄漏
  • mxnet 的显存分配机制
  • Gitlab学习(009 gitlab冲突提交)
  • 小程序与APP的区别
  • 大数据-137 - ClickHouse 集群 表引擎详解2 - MergeTree 存储结构 一级索引 跳数索引
  • 面试八股--MySQL命名规范
  • 前端组件库
  • 机器翻译之数据处理
  • 基于redis的HyperLogLog数据结构实现的布隆过滤器在信息流中历史数据的应用
  • 分布式锁优化之 防死锁 及 过期时间的原子性保证(优化之设置锁的过期时间)
  • 创新驱动,技术引领:2025年广州见证汽车电子技术新高度
  • git安装包夸克网盘下载
  • 江协科技STM32学习- P15 TIM输出比较
  • MongoDB在Linux系统中的安装与配置指南
  • 亿发工单系统:让任务风平浪静
  • 一个简单的基于C语言的HTTP代理服务器的案例
  • 基于密码的大模型安全治理的思考
  • 上手一个RGBD深度相机:从原理到实践--ROS noetic+Astra S(中):RGB相机的标定和使用
  • Tomcat 后台弱⼝令部署war包
  • 迪杰斯特拉算法
  • Git clone远程仓库没有其他分支的问题
  • 拥控算法BBR入门1