关于NotificationManagerService的使用推荐参考下面资料,写的不错:

Android4.4 NotificationManagerService使用详解与原理分析(一)、(二):点击打开链接

这里主要分析framework层的源码。

相关文件主要有:

frameworks\base\core\java\android\app\NotificationManager.java;

frameworks\base\services\core\java\com\android\server\notification\NotificationManagerService.java;

frameworks\base\services\core\java\com\android\server\statusbar\StatusBarManagerService.java;



第三方应用发起通知时,调用的是NotificationManager的notify方法,我们就从这里入手。

notify方法表示发送一个显示在状态栏上的通知,代码如下:

    /**     * Post a notification to be shown in the status bar. If a notification with     * the same id has already been posted by your application and has not yet been canceled, it     * will be replaced by the updated information.     *     * @param id An identifier for this notification unique within your     *        application.     * @param notification A {@link Notification} object describing what to show the user. Must not     *        be null.     */    public void notify(int id, Notification notification)    {        notify(null, id, notification);    }

如果你的第三方应用发送了一个相同id的通知,并且没有被取消,它将被更新的信息所取代。

    /**     * Post a notification to be shown in the status bar. If a notification with     * the same tag and id has already been posted by your application and has not yet been     * canceled, it will be replaced by the updated information.     *     * @param tag A string identifier for this notification.  May be {@code null}.     * @param id An identifier for this notification.  The pair (tag, id) must be unique     *        within your application.     * @param notification A {@link Notification} object describing what to     *        show the user. Must not be null.     */    public void notify(String tag, int id, Notification notification)    {        notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));    }
如果你的第三方应用发送了一个相同tag和id的通知,并且没有被取消,它将被更新的信息所取代。

    /**     * @hide     */    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)    {        int[] idOut = new int[1];        INotificationManager service = getService();//获取IBinder对象(NotificationManagerService)        String pkg = mContext.getPackageName();        // Fix the notification as best we can.        Notification.addFieldsFromContext(mContext, notification);//"android.appinfo"、"android.originatingUserId"        if (notification.sound != null) {            notification.sound = notification.sound.getCanonicalUri();            if (StrictMode.vmFileUriExposureEnabled()) {                notification.sound.checkFileUriExposed("Notification.sound");            }        }        fixLegacySmallIcon(notification, pkg);        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {            if (notification.getSmallIcon() == null) {                throw new IllegalArgumentException("Invalid notification (no valid small icon): "                        + notification);            }        }        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);        try {            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,                    copy, idOut, user.getIdentifier());            if (id != idOut[0]) {                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);            }        } catch (RemoteException e) {            throw e.rethrowFromSystemServer();        }    }
  • 我们看下Notification的addFieldsFromContext方法做了哪些事情:

    /**     * @hide     */    public static void addFieldsFromContext(Context context, Notification notification) {        addFieldsFromContext(context.getApplicationInfo(), context.getUserId(), notification);    }    /**     * @hide     */    public static void addFieldsFromContext(ApplicationInfo ai, int userId,            Notification notification) {        notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);//"android.appinfo"        notification.extras.putInt(EXTRA_ORIGINATING_USERID, userId);//"android.originatingUserId"    }

主要是保存了当前应用对应的ApplicationInfo对象和对应的userId。在Android4.4中Notification新增了extras字段,用来保存系统通知的一些具体信息,定义如下:

    /**     * Additional semantic data to be carried around with this Notification.     * 

* The extras keys defined here are intended to capture the original inputs to {@link Builder} * APIs, and are intended to be used by * {@link android.service.notification.NotificationListenerService} implementations to extract * detailed information from notification objects. */ public Bundle extras = new Bundle();

addFieldsFromContext方法主要实现将当前应用的ApplicationInfo对象保存到“android.appinfo”字段,将当前的userId保存到“android.originatingUserId”字段中。

  • fixLegacySmalIcon,notify函数会判断notification是否有small icon,如果没有设icon或small icon,用notify方法时会抛出异常。fixLegacySmallIcon方法如下:

    private void fixLegacySmallIcon(Notification n, String pkg) {        if (n.getSmallIcon() == null && n.icon != 0) {            n.setSmallIcon(Icon.createWithResource(pkg, n.icon));        }    }

完成上面的操作后,调用通知管理类NotificationManagerService的enqueueNotificationWithTag方法,

        @Override        public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,                Notification notification, int[] idOut, int userId) throws RemoteException {            enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),                    Binder.getCallingPid(), tag, id, notification, idOut, userId);        }

我们看到在enqueueNotificationWithTag方法中调用了enqueueNotificationInternal方法,这里就是通知处理的核心了。

    void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,            final int callingPid, final String tag, final int id, final Notification notification,            int[] idOut, int incomingUserId) {        if (DBG) {            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id                    + " notification=" + notification);        }        checkCallerIsSystemOrSameApp(pkg);        // 校验UID        final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));        final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);        final int userId = ActivityManager.handleIncomingUser(callingPid,                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);        final UserHandle user = new UserHandle(userId);        // Fix the notification as best we can.        try {            final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(                    pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,                    (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);            Notification.addFieldsFromContext(ai, userId, notification);        } catch (NameNotFoundException e) {            Slog.e(TAG, "Cannot create a context for sending app", e);            return;        }        mUsageStats.registerEnqueuedByApp(pkg);        // Limit the number of notifications that any given package except the android        // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.        // 这里会做一个限制,除了系统级别的应用之外,其他应用的notification数量会做限制,用来防止DOS攻击导致的泄露        if (!isSystemNotification && !isNotificationFromListener) {            synchronized (mNotificationList) {                final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);                if (appEnqueueRate > mMaxPackageEnqueueRate) {                    mUsageStats.registerOverRateQuota(pkg);                    final long now = SystemClock.elapsedRealtime();                    if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {                        Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate                                + ". Shedding events. package=" + pkg);                        mLastOverRateLogTime = now;                    }                    return;                }                int count = 0;                final int N = mNotificationList.size();                for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) {// 同一个应用发送notification数量不能超过50                            mUsageStats.registerOverCountQuota(pkg);                            Slog.e(TAG, "Package has already posted " + count                                    + " notifications.  Not showing more.  package=" + pkg);                            return;                        }                    }                }            }        }        if (pkg == null || notification == null) {//通知不能为空            throw new IllegalArgumentException("null not allowed: pkg=" + pkg                    + " id=" + id + " notification=" + notification);        }        // Whitelist pending intents.        if (notification.allPendingIntents != null) {            final int intentCount = notification.allPendingIntents.size();            if (intentCount > 0) {                final ActivityManagerInternal am = LocalServices                        .getService(ActivityManagerInternal.class);                final long duration = LocalServices.getService(                        DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();                for (int i = 0; i < intentCount; i++) {                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);                    if (pendingIntent != null) {                        am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);                    }                }            }        }        // Sanitize inputs        notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,                Notification.PRIORITY_MAX);        // setup local book-keeping        // 验证完条件后,将前面传递进来的Notification封装成一个StatusBarNotification对象,        final StatusBarNotification n = new StatusBarNotification(                pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,                user);        // 封装NotificationRecord对象        final NotificationRecord r = new NotificationRecord(getContext(), n);        mHandler.post(new EnqueueNotificationRunnable(userId, r));        idOut[0] = id;    }
开启线程,异步处理。
    private class EnqueueNotificationRunnable implements Runnable {        private final NotificationRecord r;        private final int userId;        EnqueueNotificationRunnable(int userId, NotificationRecord r) {            this.userId = userId;            this.r = r;        };        @Override        public void run() {            synchronized (mNotificationList) {                final StatusBarNotification n = r.sbn;                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);                }                final int callingUid = n.getUid();                final int callingPid = n.getInitialPid();                final Notification notification = n.getNotification();                final String pkg = n.getPackageName();                final int id = n.getId();                final String tag = n.getTag();                final boolean isSystemNotification = isUidSystem(callingUid) ||                        ("android".equals(pkg));                // Handle grouped notifications and bail out early if we                // can to avoid extracting signals.                handleGroupedNotificationLocked(r, old, callingUid, callingPid);                // This conditional is a dirty hack to limit the logging done on                //     behalf of the download manager without affecting other apps.                if (!pkg.equals("com.android.providers.downloads")                        || Log.isLoggable("DownloadManager", Log.VERBOSE)) {                    int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;                    if (old != null) {                        enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;                    }                    EventLogTags.writeNotificationEnqueue(callingUid, callingPid,                            pkg, id, tag, userId, notification.toString(),                            enqueueStatus);                }                mRankingHelper.extractSignals(r);                final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);                // blocked apps 判断pkg是否可以显示通知                if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE                        || !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {                    if (!isSystemNotification) {//不拦截系统通知                        if (isPackageSuspended) {                            Slog.e(TAG, "Suppressing notification from package due to package "                                    + "suspended by administrator.");                            mUsageStats.registerSuspendedByAdmin(r);                        } else {                            Slog.e(TAG, "Suppressing notification from package by user request.");                            mUsageStats.registerBlocked(r);                        }                        return;                    }                }                // tell the ranker service about the notification                if (mRankerServices.isEnabled()) {                    mRankerServices.onNotificationEnqueued(r);                    // TODO delay the code below here for 100ms or until there is an answer                }                // 获取是否已经发送过此notification                int index = indexOfNotificationLocked(n.getKey());                if (index < 0) {                    // 如果是新发送的notification,就走新增流程.                    mNotificationList.add(r);                    mUsageStats.registerPostedByApp(r);                } else {                    //如果有发送过,就获取oldNtificationRecord,后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n)                    old = mNotificationList.get(index);                    mNotificationList.set(index, r);                    mUsageStats.registerUpdatedByApp(r, old);                    // Make sure we don't lose the foreground service state.                    notification.flags |=                            old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;                    r.isUpdate = true;                }                Slog.d(TAG, "NotificationRecord, r = "+r);                mNotificationsByKey.put(n.getKey(), r);                // Ensure if this is a foreground service that the proper additional                // flags are set.                if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {                    notification.flags |= Notification.FLAG_ONGOING_EVENT                            | Notification.FLAG_NO_CLEAR;                }                applyZenModeLocked(r);                mRankingHelper.sort(mNotificationList);//将mNotificationList排序                // 如果notification设置了smallIcon,调用所有NotificationListeners的notifyPostedLocked方法,                // 通知有新的notification,传入的参数为上面封装成的StatusBarNotification对象.                if (notification.getSmallIcon() != null) {                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;                    mListeners.notifyPostedLocked(n, oldSbn);                } else {                    Slog.e(TAG, "Not posting notification without small icon: " + notification);                    if (old != null && !old.isCanceled) {                        mListeners.notifyRemovedLocked(n);                    }                    // ATTENTION: in a future release we will bail out here                    // so that we do not play sounds, show lights, etc. for invalid                    // notifications                    Slog.e(TAG, "WARNING: In a future release this will crash the app: "                            + n.getPackageName());                }                // buzzBeepBlinkLocked方法负责对消息进行处理。                // 通知status bar显示该notification,确认是否需要声音,震动和闪光,如果需要,那么就发出声音,震动和闪光                buzzBeepBlinkLocked(r);            }        }    }
最后调用buzzBeepBlinkLocked方法对消息进行处理。
    void buzzBeepBlinkLocked(NotificationRecord record) {        boolean buzz = false;        boolean beep = false;        boolean blink = false;        final Notification notification = record.sbn.getNotification();        final String key = record.getKey();        // Should this notification make noise, vibe, or use the LED?是否需要声音、震动、led        final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT;        final boolean canInterrupt = aboveThreshold && !record.isIntercepted();        if (DBG || record.isIntercepted())            Slog.v(TAG,                    "pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt +                            " intercept=" + record.isIntercepted()            );        final int currentUser;        final long token = Binder.clearCallingIdentity();        try {            currentUser = ActivityManager.getCurrentUser();        } finally {            Binder.restoreCallingIdentity(token);        }        // If we're not supposed to beep, vibrate, etc. then don't.        final String disableEffects = disableNotificationEffects(record);        if (disableEffects != null) {            ZenLog.traceDisableEffects(record, disableEffects);        }        // Remember if this notification already owns the notification channels.        boolean wasBeep = key != null && key.equals(mSoundNotificationKey);        boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);        // These are set inside the conditional if the notification is allowed to make noise.        boolean hasValidVibrate = false;        boolean hasValidSound = false;        boolean smsRingtone =  false;        if (mCarrierConfig == null) {            mCarrierConfig = mConfigManager.getConfig();        } else {            smsRingtone = mCarrierConfig.getBoolean(                CarrierConfigManager.KEY_CONFIG_SMS_RINGTONE_INCALL);        }        if ((disableEffects == null || (smsRingtone && mInCall))                && (record.getUserId() == UserHandle.USER_ALL ||                    record.getUserId() == currentUser ||                    mUserProfiles.isCurrentProfile(record.getUserId()))                && canInterrupt                && mSystemReady                && mAudioManager != null) {            if (DBG) Slog.v(TAG, "Interrupting!");            // should we use the default notification sound? (indicated either by            // DEFAULT_SOUND or because notification.sound is pointing at            // Settings.System.NOTIFICATION_SOUND)            final boolean useDefaultSound =                   (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||                           Settings.System.DEFAULT_NOTIFICATION_URI                                   .equals(notification.sound);            Uri soundUri = null;            if (useDefaultSound) {                soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;                // check to see if the default notification sound is silent                ContentResolver resolver = getContext().getContentResolver();                hasValidSound = Settings.System.getString(resolver,                       Settings.System.NOTIFICATION_SOUND) != null;            } else if (notification.sound != null) {                soundUri = notification.sound;                hasValidSound = (soundUri != null);            }            // Does the notification want to specify its own vibration?            final boolean hasCustomVibrate = notification.vibrate != null;            // new in 4.2: if there was supposed to be a sound and we're in vibrate            // mode, and no other vibration is specified, we fall back to vibration            final boolean convertSoundToVibration =                    !hasCustomVibrate                            && hasValidSound                            && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE);            // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.            final boolean useDefaultVibrate =                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;            hasValidVibrate = useDefaultVibrate || convertSoundToVibration ||                    hasCustomVibrate;            // We can alert, and we're allowed to alert, but if the developer asked us to only do            // it once, and we already have, then don't.            if (!(record.isUpdate                    && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) {                sendAccessibilityEvent(notification, record.sbn.getPackageName());                if (hasValidSound) {                    boolean looping =                            (notification.flags & Notification.FLAG_INSISTENT) != 0;                    AudioAttributes audioAttributes = audioAttributesForNotification(notification);                    mSoundNotificationKey = key;                    // do not play notifications if stream volume is 0 (typically because                    // ringer mode is silent) or if there is a user of exclusive audio focus                    if ((mAudioManager.getStreamVolume(                            AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)                            && !mAudioManager.isAudioFocusExclusive()) {                        final long identity = Binder.clearCallingIdentity();                        try {                            final IRingtonePlayer player =                                    mAudioManager.getRingtonePlayer();                            if (player != null) {                                if (DBG) Slog.v(TAG, "Playing sound " + soundUri                                        + " with attributes " + audioAttributes);                                player.playAsync(soundUri, record.sbn.getUser(), looping,                                        audioAttributes);                                beep = true;                            }                        } catch (RemoteException e) {                        } finally {                            Binder.restoreCallingIdentity(identity);                        }                    }                }                if (hasValidVibrate && !(mAudioManager.getRingerModeInternal()                        == AudioManager.RINGER_MODE_SILENT)) {                    mVibrateNotificationKey = key;                    if (useDefaultVibrate || convertSoundToVibration) {                        // Escalate privileges so we can use the vibrator even if the                        // notifying app does not have the VIBRATE permission.                        long identity = Binder.clearCallingIdentity();                        try {                            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),                                useDefaultVibrate ? mDefaultVibrationPattern                                    : mFallbackVibrationPattern,                                ((notification.flags & Notification.FLAG_INSISTENT) != 0)                                    ? 0: -1, audioAttributesForNotification(notification));                            buzz = true;                        } finally {                            Binder.restoreCallingIdentity(identity);                        }                    } else if (notification.vibrate.length > 1) {                        // If you want your own vibration pattern, you need the VIBRATE                        // permission                                                    mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),                                    notification.vibrate,                                ((notification.flags & Notification.FLAG_INSISTENT) != 0)                                        ? 0: -1, audioAttributesForNotification(notification));                            buzz = true;                    }                }            }        }        // If a notification is updated to remove the actively playing sound or vibrate,        // cancel that feedback now        if (wasBeep && !hasValidSound) {            clearSoundLocked();        }        if (wasBuzz && !hasValidVibrate) {            clearVibrateLocked();        }        // light        // release the light        boolean wasShowLights = mLights.remove(key);        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold                && ((record.getSuppressedVisualEffects()                & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {            mLights.add(key);            updateLightsLocked();            if (mUseAttentionLight) {                mAttentionLight.pulse();            }            blink = true;        } else if (wasShowLights) {            updateLightsLocked();        }        if (buzz || beep || blink) {            if (((record.getSuppressedVisualEffects()                    & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {                if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");            } else {                EventLogTags.writeNotificationAlert(key,                        buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);                // 后将mBuzzBeepBlinked post到工作handler,最后会调用到mStatusBar.buzzBeepBlinked()                mHandler.post(mBuzzBeepBlinked);            }        }    }












更多相关文章

  1. Android(安卓)JUnit单元测试基础实例
  2. android中保存Bitmap图片到指定文件夹中的方法
  3. 面试篇--android下网络通讯机制(三种网络通讯方式)
  4. Android(安卓)跨进程通信(二)
  5. SystemClock.sleep和Thread.sleep源码分析
  6. Android使用Jsoup解析Html表格的方法
  7. Android(安卓)Studio Gradle 重命名输出App或者Library的文件名
  8. Android(安卓)intent传递hashMap对象,遍历hashMap,改变menu状态
  9. android scrollview 自动滚动到顶部或者底部

随机推荐

  1. android 通过反射访问内部类(也可访问其他
  2. Android学习资料【转】
  3. Android setText异常
  4. Fragment Management
  5. Android 文件管理器
  6. Android(安卓)UI开源软件(三)
  7. Android使用线程获取网络图片的方法
  8. Activity 以及 Intent的使用
  9. android字符串工具类
  10. ubuntu环境下反编译android apk