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

相关推荐
花王江不语31 分钟前
android studio 配置硬件加速 haxm
android·ide·android studio
江太翁2 小时前
mediapipe流水线分析 三
android·mediapipe
与火星的孩子对话3 小时前
Unity进阶课程【六】Android、ios、Pad 终端设备打包局域网IP调试、USB调试、性能检测、控制台打印日志等、C#
android·unity·ios·c#·ip
tmacfrank4 小时前
Android 网络全栈攻略(四)—— TCPIP 协议族与 HTTPS 协议
android·网络·https
fundroid5 小时前
Kotlin 协程:Channel 与 Flow 深度对比及 Channel 使用指南
android·kotlin·协程
草字5 小时前
cocos 打包安卓
android
DeBuggggggg6 小时前
centos 7.6安装mysql8
android
浩浩测试一下7 小时前
渗透信息收集- Web应用漏洞与指纹信息收集以及情报收集
android·前端·安全·web安全·网络安全·安全架构
移动开发者1号8 小时前
深入理解原子类与CAS无锁编程:原理、实战与优化
android·kotlin
陈卓4108 小时前
MySQL-主从复制&分库分表
android·mysql·adb