Android通知之NotificationManagerService 源码分析

Android的通知系统比较庞大复杂,牵扯到系统服务NotificationManagerService,SystemUI以及Launcher,本文将基于Android12L从源码角度分析一下NotificationManagerService流程,通知其他部分将分为多个章节展开。

一、常用发送通知方式

// 创建一个NotificationManager实例
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

// 创建一个NotificationCompat.Builder实例
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle("通知标题")
        .setContentText("通知内容")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT);

// 发送通知
notificationManager.notify(NOTIFICATION_ID, builder.build());

NotificationManager最终调用了notifyAsUser方法。

    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();
        }
    }

可以看到具体工作是交给了系统服务INotificationManager,其实现类就是NotificationManagerService 。

二、NotificationManagerService

NotificationManagerService 是在系统启动时在SystemService.startOtherServicess的时候通过mSystemServiceManager.startService(NotificationManagerService.class)方式启动的,并且加入到了系统服务的列表当中,在Android系统中可以通过ServiceManager.getService("notification")拿到这个服务,后面统称为NMS。

2.1-NotificationManagerService.enqueueNotificationInternal

前面讲到NotificationManager调用了NMS.enqueueNotificationWithTag方法,其实内部通过几次跳转最终走到了NMS.enqueueNotificationInternal方法。我们来看下具体实现。

[->NotificationManagerService.java]

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) {
        //权限检查
                、、、
        // Fix the notification as best we can.
        try {
            fixNotification(notification, pkg, tag, id, userId);
        } catch (Exception e) {
            return;
        }

        final StatusBarNotification n = new StatusBarNotification(
                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                user, null, System.currentTimeMillis());

        String shortcutId = n.getShortcutId();
        final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
                pkg, notificationUid, channelId, shortcutId,
                true /* parent ok */, false /* includeDeleted */);
        //必须要有channel
        if (channel == null) {
           、、、
            return;
        }
        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
        r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
        r.setPostSilently(postSilently);
        r.setFlagBubbleRemoved(false);
        r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));

        、、、

        boolean isAppForeground;
        try {
            isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
        } finally {
            Binder.restoreCallingIdentity(token);
        }
        mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
    }

NMS.fixNotification

1.检查android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS权限有就

notification.flags |= Notification.FLAG_CAN_COLORIZE;无就清空对应标记

2.检查notification.fullScreenIntent,如果没有android.Manifest.permission.USE_FULL_SCREEN_INTENT权限将其置为空。

3.检查通知的Style样式

4.检查RemoteViews的contentView、bigContentView、headsUpContentView是否超出了空间大小限制,主要是通过bitmap所占空间大小判断。

我们看到方法最后通过Handler Post了一个Runable。

2.2-EnqueueNotificationRunnable

[->NotificationManagerService.java]

protected class EnqueueNotificationRunnable implements Runnable {
        
        @Override
        public void run() {
            synchronized (mNotificationLock) {
                、、、
                //存进集合,后续会用到

                mEnqueuedNotifications.add(r);
                // 当用户设置了 Builder.setTimeoutAfter(long durationMs) 则会在这里做处理
                scheduleTimeoutLocked(r);

                final StatusBarNotification n = r.getSbn();
                if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
                NotificationRecord old = mNotificationsByKey.get(n.getKey());
                if (old != null) {
                    // Retain ranking information from previous record
                    r.copyRankingInformation(old);
                }

                //成组通知处理
                handleGroupedNotificationLocked(r, old, callingUid, callingPid);
                、、、

                // tell the assistant service about the notification
                if (mAssistants.isEnabled()) {
                    mAssistants.onNotificationEnqueuedLocked(r);
                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                            DELAY_FOR_ASSISTANT_TIME);
                } else {
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
            }
        }
}

将通知存进了mEnqueuedNotifications队列,这个队列保存的是未处理的通知,当通知被取消、超时、处理完成后会从队列移除。还进行了超时逻辑的处理,分组处理,我们看下分组的处理。

2.3-handleGroupedNotificationLocked

[->NotificationManagerService.java]

private void handleGroupedNotificationLocked(NotificationRecord r, NotificationRecord old,
            int callingUid, int callingPid) {
        StatusBarNotification sbn = r.getSbn();
        Notification n = sbn.getNotification();
        //注释1
        if (n.isGroupSummary() && !sbn.isAppGroup())  {
            // notifications without a group shouldn't be a summary, otherwise autobundling can
            // lead to bugs
            n.flags &= ~Notification.FLAG_GROUP_SUMMARY;
        }

        String group = sbn.getGroupKey();
        boolean isSummary = n.isGroupSummary();

        Notification oldN = old != null ? old.getSbn().getNotification() : null;
        String oldGroup = old != null ? old.getSbn().getGroupKey() : null;
        boolean oldIsSummary = old != null && oldN.isGroupSummary();
        //注释2
        if (oldIsSummary) {
            NotificationRecord removedSummary = mSummaryByGroupKey.remove(oldGroup);
            if (removedSummary != old) {
                String removedKey =
                        removedSummary != null ? removedSummary.getKey() : "<null>";
                Slog.w(TAG, "Removed summary didn'
t match old notification: old=" + old.getKey() +
                        "
, removed=" + removedKey);
            }
        }
        if (isSummary) {
            mSummaryByGroupKey.put(group, r);
        }

        // Clear out group children of the old notification if the update
        // causes the group summary to go away. This happens when the old
        // notification was a summary and the new one isn't, or when the old
        // notification was a summary and its group key changed.
        //注释3
        if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
            cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
                    null, REASON_APP_CANCEL);
        }
    }

注释1:修正通知,通知是一个summary但是没有设置group,说明用户错误的设置了summary属性,去掉Notification.FLAG_GROUP_SUMMARY标志位。

注释2:如果必要的话更新集合mSummaryByGroupKey,旧的summary通知发生了变化。

注释3:如果旧通知是一条父通知,新通知变成了非父通知;或者旧通知新通知均是父通知,但是group key已经发生了变化,则原来父通知下的所有子通知会被移除

再回到EnqueueNotificationRunnable,在2.2最后我们看到EnqueueNotificationRunnable最后有Post了一个Runable。

2.4-PostNotificationRunnable

[->NotificationManagerService.java]

protected class PostNotificationRunnable implements Runnable {
        private final String key;

        、、、
        
        public void run() {
            synchronized (mNotificationLock) {
                try {
                    NotificationRecord r = null;
                    int N = mEnqueuedNotifications.size();
                    //从入队待处理的通知mEnqueuedNotifications中找到要处理的通知
                    for (int i = 0; i < N; i++) {
                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            r = enqueued;
                            break;
                        }
                    }
                    //没找到,可能被取消了返回
                    if (r == null) {
                        return;
                    }
                    //检查是避免在中间处理过程中 blocked 属性发生了改变
                    if (isBlocked(r)) {
                        return;
                    }
                    //该应用是否被系统限制了,是的话hidden为true,
                    //这个属性在后面决定是否播放通知声音、震动等提醒的时候会用到
                    final boolean isPackageSuspended =
                            isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());
                    r.setHidden(isPackageSuspended);
        
                    、、、

                    //从mNotificationList找到对应key的下标mNotificationList是存储 已排序 的通知,
                    //这里判断新来的通知是不是更新类型的,
                    //不是的话就直接add进mNotificationList,是的话则会将旧的通知替换掉,排序不变
                    int index = indexOfNotificationLocked(n.getKey());
                    if (index < 0) {
                        mNotificationList.add(r);
                        、、、

                    } else {
                        old = mNotificationList.get(index);  // Potentially *changes* old
                        mNotificationList.set(index, r);
                        、、、
                    }

                    //将即将发送的通知存进集合mNotificationsByKey,
                    //这也是为什么前面我们可以通过mNotificationsByKey获取到某通知是否存在旧通知的原因
                    mNotificationsByKey.put(n.getKey(), r);
                    //前台服务通知是强制常驻通知面板的,不管你发送的时候是否设置了
                    //相关的常驻标志(FLAG_ONGOING_EVENT / FLAG_NO_CLEAR),系统都会帮你加上
                    if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
                        notification.flags |= FLAG_ONGOING_EVENT
                                | FLAG_NO_CLEAR;
                    }
                    //注释1:
                    mRankingHelper.extractSignals(r);
                    mRankingHelper.sort(mNotificationList);
                    final int position = mRankingHelper.indexOf(mNotificationList, r);

                    int buzzBeepBlinkLoggingCode = 0;
                    if (!r.isHidden()) {
                        //震动、声音、LED提醒
                        buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r);
                    }
                    //SmallIcon 不能为空,Android高版本规定必须要有小图标否则不展示
                    if (notification.getSmallIcon() != null) {
                        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                        mListeners.notifyPostedLocked(r, old);
                        、、、
                    } else {
                        if (old != null && !old.isCanceled) {
                            mListeners.notifyRemovedLocked(r,
                                    NotificationListenerService.REASON_ERROR, r.getStats());
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mGroupHelper.onNotificationRemoved(n);
                                }
                            });
                        }
                    }

                    if (mShortcutHelper != null) {
                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
                                false /* isRemoved */,
                                mHandler);
                    }

                    maybeRecordInterruptionLocked(r);
                    maybeRegisterMessageSent(r);
                    maybeReportForegroundServiceUpdate(r, true);
                } finally {
                    int N = mEnqueuedNotifications.size();
                    //处理完成了在这里将通知从mEnqueuedNotifications队列移除
                    for (int i = 0; i < N; i++) {
                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            mEnqueuedNotifications.remove(i);
                            break;
                        }
                    }
                }
            }
        }
    }

这里主要是根据key找到要处理的通知,在2.3中PostNotificationRunnable的构造方法传入了key。找到要处理的通知之后进行一些状态判断处理。这里说明一下StatusBarNotification与NotificationRecord,两个都是对通知的封装,NotificationRecord是Service端使用的,另一个是给用户端使用的。

注释1:在这里将通知交给了多个NotificationSignalExtractor去处理,mRankingHelper是一个辅助类,内部维护了多个NotificationSignalExtractor的实现类,比如BubbleExtractor显示通知气泡,ImportanceExtractor确定通知重要性等,这些实现是在一个config.xml通过一个列表定义的。这里只是说明一下,后面会展开详细介绍。

高版本的Android系统规定通知必须有小图标,此时notification.getSmallIcon()不为空,调用mListeners.notifyPostedLocked。

2.5-notifyPostedLocked 通知监听者通知发生变更

[->NotificationManagerService.java]

 public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
            notifyPostedLocked(r, old, true);
        }

    private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                boolean notifyAllListeners) {
            try {
                、、、
                for (final ManagedServiceInfo info : getServices()) {
                    、、、

                    final StatusBarNotification sbnToPost = trimCache.ForListener(info);
                    mHandler.post(() -> notifyPosted(info, sbnToPost, update));
                }
            } catch (Exception e) {
                Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
            }
        }
        private void notifyPosted(final ManagedServiceInfo info,
                final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
            final INotificationListener listener = (INotificationListener) info.service;
            StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
            try {
                listener.onNotificationPosted(sbnHolder, rankingUpdate);
            } catch (RemoteException ex) {
                Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
            }
        }

通过调用notifyPostedLocked->notifyPostedLocked->notifyPosted,我们发现这里又有一个跨进程的接口调用INotificationListener。我们看到监听回调是在一个for循环中调用的,说明有多个模块注册了通知变化的监听,例如SystemUI中的通知栏,后面章节会讲到。到这里NMS已经处理完相关的工作,并且将事件通知了出去。

三、总结

前面NMS主干脉络绘制的流程调用结构图。 alt

本文由 mdnice 多平台发布

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值