Android 12.0 中 清除通知 , 系统源码分析(一)

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的通知 就分析完毕!

相关推荐
Monkey-旭2 小时前
Android Bitmap 完全指南:从基础到高级优化
android·java·人工智能·计算机视觉·kotlin·位图·bitmap
Mike_Wuzy8 小时前
【Android】发展历程
android
开酒不喝车8 小时前
安卓Gradle总结
android
阿华的代码王国9 小时前
【Android】PopupWindow实现长按菜单
android·xml·java·前端·后端
稻草人不怕疼10 小时前
Android 15 全屏模式适配:A15TopView 自定义组件分享
android
静默的小猫10 小时前
LiveDataBus消息事件总线之二-(不含反射和hook)
android
~央千澈~11 小时前
05百融云策略引擎项目交付-laravel实战完整交付定义常量分文件配置-独立建立lib类处理-成功导出pdf-优雅草卓伊凡
android·laravel·软件开发·金融策略
_一条咸鱼_11 小时前
Android Runtime冷启动与热启动差异源码级分析(99)
android·面试·android jetpack
用户20187928316711 小时前
Java序列化之幽灵船“Serial号”与永生契约
android·java
用户20187928316711 小时前
“对象永生”的奇幻故事
android·java