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

相关推荐
OkeyProxy9 小时前
設置Android設備全局代理
android·代理模式·proxy模式·代理服务器·海外ip代理
刘志辉10 小时前
vue传参方法
android·vue.js·flutter
前期后期13 小时前
Android OkHttp源码分析(一):为什么OkHttp的请求速度很快?为什么可以高扩展?为什么可以高并发
android·okhttp
轻口味15 小时前
Android应用性能优化
android
全职计算机毕业设计15 小时前
基于 UniApp 平台的学生闲置物品售卖小程序设计与实现
android·uni-app
dgiij16 小时前
AutoX.js向后端传输二进制数据
android·javascript·websocket·node.js·自动化
SevenUUp16 小时前
Android Manifest权限清单
android
高林雨露17 小时前
Android 检测图片抓拍, 聚焦图片后自动完成拍照,未对准图片的提示请将摄像头对准要拍照的图片
android·拍照抓拍
wilanzai17 小时前
Android View 的绘制流程
android
INSBUG18 小时前
CVE-2024-21096:MySQLDump提权漏洞分析
android·adb