Android 提供了标准的api供第三方应用去清除通知,如下:

NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);

notificationManager.cancel(id);//删除指定id的通知
notificationManager.cancelAll();//删除全部通知

针对的使用场景: 只能删除从该App中发出的通知,不能删除别的应用或者是系统的通知. 

其中 "删除一条指定id的通知" 的源码分析如下: 

(1)源码路径: frameworks/base/core/java/android/app/NotificationManager.java



 /**
     * 取消之前发布的通知.
     *
     *  如果通知当前不代表
     *  {@link Service#startForeground(int, Notification) foreground service}, 它将是
     *  从用户界面和实时版本中删除
     *  {@link android.service.notification.NotificationListenerService notification listeners}
     *  将收到通知,以便他们可以从用户界面中删除通知
     */

    public void cancel(int id)
    {
        cancel(null, id);
    }


  /**
     * 取消之前发布的通知.
     *
     *  如果通知当前不代表
     *  {@link Service#startForeground(int, Notification) foreground service}, 它将是
     *  从用户界面和实时版本中删除
     *  {@link android.service.notification.NotificationListenerService notification listeners}
     *  将收到通知,以便他们可以从用户界面中删除通知
     */

    public void cancel(@Nullable String tag, int id)
    {
        cancelAsUser(tag, id, mContext.getUser());
    }

其中的 id 是指发送通知时, notificationManager.notify(int id , Notification notification) 中的 id,

(2) 接着调用 cancelAsUser( ) 

 /**
     * @hide
     */
    @UnsupportedAppUsage
    public void cancelAsUser(String tag, int id, UserHandle user)
    {
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();//通过context获取应用的包名,即定义NotificationManager的应用的包名
        if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
        try {
            //跨进程通信调用NotificationManagerService中的方法
            service.cancelNotificationWithTag(
                    pkg, mContext.getOpPackageName(), tag, id, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

上面通过跨进程通讯,从而调用 NotificationManagerService 的 cancelNotificationWithTag() 方法.

(3) 源码路径: frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

 @Override
        public void cancelNotificationWithTag(String pkg, String opPkg, String tag, int id,
                int userId) {
            cancelNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(),
                    tag, id, userId);
        }



    void cancelNotificationInternal(String pkg, String opPkg, int callingUid, int callingPid,
            String tag, int id, int userId) {
        userId = ActivityManager.handleIncomingUser(callingPid,
                callingUid, userId, true, false, "cancelNotificationWithTag", pkg);

        // 获取 uid
        int uid = resolveNotificationUid(opPkg, pkg, callingUid, userId);

        if (uid == INVALID_UID) {
            Slog.w(TAG, opPkg + ":" + callingUid + " trying to cancel notification "
                    + "for nonexistent pkg " + pkg + " in user " + userId);
            return;
        }

        // if opPkg is not the same as pkg, make sure the notification given was posted
        // by opPkg
        if (!Objects.equals(pkg, opPkg)) {
            synchronized (mNotificationLock) {
                // Look for the notification, searching both the posted and enqueued lists.
                NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
                if (r != null) {
                    if (!Objects.equals(opPkg, r.getSbn().getOpPkg())) {
                        throw new SecurityException(opPkg + " does not have permission to "
                                + "cancel a notification they did not post " + tag + " " + id);
                    }
                }
            }
        }

        // 不允许客户端应用程序取消前台服务通知或自动捆绑
        // summaries.
        final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
                (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY);
        
        //清除通知
        cancelNotification(uid, callingPid, pkg, tag, id, 0,
                mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null);
    }

(4) 接着继续执行 cancelNotification () 清除通知的操作

/**
     * 仅当通知具有所有 {@code MustHaveFlags} 时才取消通知 
     * 且没有任何 {@code MustNotHaveFlags}。.
     */
    void cancelNotification(final int callingUid, final int callingPid,
            final String pkg, final String tag, final int id,
            final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
            final int userId, final int reason, final ManagedServiceInfo listener) {
      
        //接着调用
        cancelNotification(callingUid, callingPid, pkg, tag, id, mustHaveFlags, mustNotHaveFlags,
                sendDelete, userId, reason, -1 /* rank */, -1 /* count */, listener);
    }



 void cancelNotification(final int callingUid, final int callingPid,
            final String pkg, final String tag, final int id,
            final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
            final int userId, final int reason, int rank, int count,
            final ManagedServiceInfo listener) {

        //通过Handler发送信息到 CancelNotificationRunnable 线程中去删除通知的操作
        mHandler.scheduleCancelNotification(new CancelNotificationRunnable(callingUid, callingPid,
                pkg, tag, id, mustHaveFlags, mustNotHaveFlags, sendDelete, userId, reason, rank,
                count, listener));
    }

(5) 删除通知的操作在 CancelNotificationRunnable 线程中去处理,其中,CancelNotificationRunnable 线程 在NotificaitonManagerService 中,接着, 直接看该线程的run()

 @Override
        public void run() {
            String listenerName = mListener == null ? null : mListener.component.toShortString();
            if (DBG) {
                EventLogTags.writeNotificationCancel(mCallingUid, mCallingPid, mPkg, mId, mTag,
                        mUserId, mMustHaveFlags, mMustNotHaveFlags, mReason, listenerName);
            }

            synchronized (mNotificationLock) {
                // 查询需要删除的指定id的通知, 
                NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId);
                if (r != null) {
                    // 发现通知,检查是否应将其删除。

                    if (mReason == REASON_CLICK) {
                        mUsageStats.registerClickedByUser(r);
                    }

                    if ((mReason == REASON_LISTENER_CANCEL
                            && r.getNotification().isBubbleNotification())
                            || (mReason == REASON_CLICK && r.canBubble()
                            && r.isFlagBubbleRemoved())) {
                        boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null
                                && r.getNotification().getBubbleMetadata().isBubbleSuppressed();
                        mNotificationDelegate.onBubbleNotificationSuppressionChanged(
                                r.getKey(), true /* notifSuppressed */, isBubbleSuppressed);
                        return;
                    }

                    if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) {
                        return;
                    }
                    if ((r.getNotification().flags & mMustNotHaveFlags) != 0) {
                        return;
                    }

                    // Bubbled children get to stick around if the summary was manually cancelled
                    // (user removed) from systemui.
                    FlagChecker childrenFlagChecker = null;
                    if (mReason == REASON_CANCEL
                            || mReason == REASON_CLICK
                            || mReason == REASON_CANCEL_ALL) {
                        childrenFlagChecker = (flags) -> {
                            if ((flags & FLAG_BUBBLE) != 0) {
                                return false;
                            }
                            return true;
                        };
                    }

                    // 清除通知,通获得是否成功清除,根据返回值,再清理发送通知的一些资源,分析请看(6)
                    boolean wasPosted = removeFromNotificationListsLocked(r);
                    
                    //请看分析(7)
                    cancelNotificationLocked(
                            r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
                    cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                            mSendDelete, childrenFlagChecker, mReason);
                    updateLightsLocked();
                    if (mShortcutHelper != null) {
                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
                                true /* isRemoved */,
                                mHandler);
                    }
                } else {
                    // 未找到通知,假设该通知已暂停并取消。
                    if (mReason != REASON_SNOOZED) {
                        final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId);
                        if (wasSnoozed) {
                            handleSavePolicyFile();
                        }
                    }
                }
            }
        }
    }

(6)查看通知是如何删除的, 继续分析 removeFromNotificationListsLocked()


    @GuardedBy("mNotificationLock")
    private boolean removeFromNotificationListsLocked(NotificationRecord r) {

        // 从两个列表中删除,任一列表都可以有一个单独的记录,实际上是相同的通知。
        boolean wasPosted = false;

       //NotificationRecord在NotificationManagerService中代表一个通知,
        NotificationRecord recordInList = null;
       
       //根据指定的通知的key,在通知列表中查询是否有符合的通知
        if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey()))
                != null) {
            //找到通知后,把该通知从通知列表中清除,并把返回值设置为true
            mNotificationList.remove(recordInList);
            mNotificationsByKey.remove(recordInList.getSbn().getKey());
            wasPosted = true;
        }
        while ((recordInList = findNotificationByListLocked(mEnqueuedNotifications, r.getKey()))
                != null) {
           //mEnqueuedNotifications 是一个list,里面保存了所有发送的通知,如果在该list中也存在需要删除的通知,则把该通知也从list中清除
            mEnqueuedNotifications.remove(recordInList);
        }
        return wasPosted;
    }

(7) 正常情况下,都能找到需要删除的通知,并顺利清除,通知清除后,还需要做一些通知资源的后续工作,继续分析 cancelNotificationLocked()

 @GuardedBy("mNotificationLock")
    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete,
            @NotificationListenerService.NotificationCancelReason int reason,
            int rank, int count, boolean wasPosted, String listenerName) {
        final String canceledKey = r.getKey();

 
        final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
                REQUEST_CODE_TIMEOUT,
                new Intent(ACTION_NOTIFICATION_TIMEOUT)
                        .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
                                .appendPath(r.getKey()).build())
                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);

        // 清除通知的alarm
        if (pi != null) {
            mAlarmManager.cancel(pi);
        }

        // Record caller.
        recordCallerLocked(r);

        if (r.getStats().getDismissalSurface() == NotificationStats.DISMISSAL_NOT_DISMISSED) {
            r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
        }

        // tell the app
        if (sendDelete) {
            final PendingIntent deleteIntent = r.getNotification().deleteIntent;
            if (deleteIntent != null) {
                try {
                    // 确保deleteIntent不能用于从后台启动activity
                    LocalServices.getService(ActivityManagerInternal.class)
                            .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
                                    ALLOWLIST_TOKEN);
                    deleteIntent.send();
                } catch (PendingIntent.CanceledException ex) {
                    // do nothing - there's no relevant way to recover, and
                    //     no reason to let this propagate
                    Slog.w(TAG, "canceled PendingIntent for " + r.getSbn().getPackageName(), ex);
                }
            }
        }

        // 仅当确实要发布此通知时才取消这些通知。该wasPosted参数是从分析(6)中传递过来的,即成功清除通知时,wasPosted为true,
        if (wasPosted) {
            // status bar
            if (r.getNotification().getSmallIcon() != null) {
                if (reason != REASON_SNOOZED) {
                    r.isCanceled = true;
                }
                mListeners.notifyRemovedLocked(r, reason, r.getStats());
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mGroupHelper.onNotificationRemoved(r.getSbn());
                    }
                });
            }

            // 清除 sound
            if (canceledKey.equals(mSoundNotificationKey)) {
                clearSoundLocked();
            }

            // 清除  vibrate
            if (canceledKey.equals(mVibrateNotificationKey)) {
                clearVibrateLocked();
            }

            // 清除  light
            mLights.remove(canceledKey);
        }

        // Record usage stats
        // TODO: add unbundling stats?
        switch (reason) {
            case REASON_CANCEL:
            case REASON_CANCEL_ALL:
            case REASON_LISTENER_CANCEL:
            case REASON_LISTENER_CANCEL_ALL:
                mUsageStats.registerDismissedByUser(r);
                break;
            case REASON_APP_CANCEL:
            case REASON_APP_CANCEL_ALL:
                mUsageStats.registerRemovedByApp(r);
                break;
        }

        String groupKey = r.getGroupKey();
        NotificationRecord groupSummary = mSummaryByGroupKey.get(groupKey);
        if (groupSummary != null && groupSummary.getKey().equals(canceledKey)) {
            mSummaryByGroupKey.remove(groupKey);
        }
        final ArrayMap<String, String> summaries =
                mAutobundledSummaries.get(r.getSbn().getUserId());
        if (summaries != null && r.getSbn().getKey().equals(
                summaries.get(r.getSbn().getPackageName()))) {
            summaries.remove(r.getSbn().getPackageName());
        }

        // Save it for users of getHistoricalNotifications(), unless the whole channel was deleted
        if (reason != REASON_CHANNEL_REMOVED) {
            mArchive.record(r.getSbn(), reason);
        }

        final long now = System.currentTimeMillis();
        final LogMaker logMaker = r.getItemLogMaker()
                .setType(MetricsEvent.TYPE_DISMISS)
                .setSubtype(reason);
        if (rank != -1 && count != -1) {
            logMaker.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank)
                    .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, count);
        }
        MetricsLogger.action(logMaker);
        EventLogTags.writeNotificationCanceled(canceledKey, reason,
                r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
                rank, count, listenerName);
        if (wasPosted) {
            mNotificationRecordLogger.logNotificationCancelled(r, reason,
                    r.getStats().getDismissalSurface());
        }
    }

至此,notificationManager.cancel(id);//删除指定id的通知 就分析完毕!

11-14 20:27