Android NotificationManager简读

今天发觉系统状态栏不能够显示通知条了,但是通知还是有声音之类的,只是不能够显示了,是突然不可以显示了.所以突然看了一下源代码.

应用程序中发送通知Notification都需要获取通知服务:

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

看来是通过NotificationManager管理器获取的,根据Android的代理模式,就会对应NotificationManagerService,所以大致梳理一下.

源代码路径都在:

~\base\services\core\java\com\android\server\notification

思路差不多如下:

一般从NotificationManager开始:为什么从notify开始呢?因为每次要发送通知都是通过最后notify方法的.

public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }

然后进一步:

public void notify(String tag, int id, Notification notification)
    {
        int[] idOut = new int[1];
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        try {
            service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
                    UserHandle.myUserId());
            if (id != idOut[0]) {
                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
            }
        } catch (RemoteException e) {
        }
    }


其中程序中的service即NofiticationManagerService,然后再看看:这个服务都是系统SystemServer中的run启动的.

service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
                    UserHandle.myUserId());

...

// Notifications
    // ============================================================================
    public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
            int[] idOut, int userId)
    {
        enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
                tag, id, notification, idOut, userId);
    }

...

// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
    // uid/pid of another application)
    public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
            String tag, int id, Notification notification, int[] idOut, int userId)
    {
        if (DBG) {
            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
        }
        checkCallerIsSystemOrSameApp(pkg);
        final boolean isSystemNotification = ("android".equals(pkg));

        userId = ActivityManager.handleIncomingUser(callingPid,
                callingUid, userId, true, false, "enqueueNotification", pkg);
        final UserHandle user = new UserHandle(userId);

        // Limit the number of notifications that any given package except the android
        // package can enqueue.  Prevents DOS attacks and deals with leaks.
        if (!isSystemNotification) {
            synchronized (mNotificationList) {
                int count = 0;
                final int N = mNotificationList.size();
                for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) {
                            Slog.e(TAG, "Package has already posted " + count
                                    + " notifications.  Not showing more.  package=" + pkg);
                            return;
                        }
                    }
                }
            }
        }

        // 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)) {
            EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId,
                    notification.toString());
        }

        if (pkg == null || notification == null) {
            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
                    + " id=" + id + " notification=" + notification);
        }
        if (notification.icon != 0) {
            if (notification.contentView == null) {
                throw new IllegalArgumentException("contentView required: pkg=" + pkg
                        + " id=" + id + " notification=" + notification);
            }
        }

        // === Scoring ===

        // 0. Sanitize inputs
        notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX);
        // Migrate notification flags to scores
        if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) {
            if (notification.priority < Notification.PRIORITY_MAX) notification.priority = Notification.PRIORITY_MAX;
        } else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
            if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH;
        }

        // 1. initial score: buckets of 10, around the app 
        int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]

        // 2. Consult external heuristics (TBD)

        // 3. Apply local rules

        // blocked apps
        if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
            score = JUNK_SCORE;
            Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
        }

        if (DBG) {
            Slog.v(TAG, "Assigned score=" + score + " to " + notification);
        }

        if (score < SCORE_DISPLAY_THRESHOLD) {
            // Notification will be blocked because the score is too low.
            return;
        }

        // Should this notification make noise, vibe, or use the LED?
        final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD);

        synchronized (mNotificationList) {
            NotificationRecord r = new NotificationRecord(pkg, tag, id, 
                    callingUid, callingPid, userId,
                    score,
                    notification);
            NotificationRecord old = null;

            int index = indexOfNotificationLocked(pkg, tag, id, userId);
            if (index < 0) {
                mNotificationList.add(r);
            } else {
                old = mNotificationList.remove(index);
                mNotificationList.add(index, r);
                // Make sure we don't lose the foreground service state.
                if (old != null) {
                    notification.flags |=
                        old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
                }
            }

            // 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;
            }

            final int currentUser;
            final long token = Binder.clearCallingIdentity();
            try {
                currentUser = ActivityManager.getCurrentUser();
            } finally {
                Binder.restoreCallingIdentity(token);
            }

            if (notification.icon != 0) {
                final StatusBarNotification n = new StatusBarNotification(
                        pkg, id, tag, r.uid, r.initialPid, score, notification, user);
                if (old != null && old.statusBarKey != null) {
                    r.statusBarKey = old.statusBarKey;
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mStatusBar.updateNotification(r.statusBarKey, n);
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                } else {
                    long identity = Binder.clearCallingIdentity();
                    try {
                        r.statusBarKey = mStatusBar.addNotification(n);
                        if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
                                && canInterrupt) {
                            mAttentionLight.pulse();
                        }
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
                // Send accessibility events only for the current user.
                if (currentUser == userId) {
                    sendAccessibilityEvent(notification, pkg);
                }
            } else {
                Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
                if (old != null && old.statusBarKey != null) {
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mStatusBar.removeNotification(old.statusBarKey);
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            }

            // If we're not supposed to beep, vibrate, etc. then don't.
            if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
                    && (!(old != null
                        && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
                    && (r.userId == UserHandle.USER_ALL ||
                        (r.userId == userId && r.userId == currentUser))
                    && canInterrupt
                    && mSystemReady) {

                final AudioManager audioManager = (AudioManager) mContext
                .getSystemService(Context.AUDIO_SERVICE);

                // sound
                final boolean useDefaultSound =
                    (notification.defaults & Notification.DEFAULT_SOUND) != 0;

                Uri soundUri = null;
                boolean hasValidSound = false;

                if (useDefaultSound) {
                    soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;

                    // check to see if the default notification sound is silent
                    ContentResolver resolver = mContext.getContentResolver();
                    hasValidSound = Settings.System.getString(resolver,
                           Settings.System.NOTIFICATION_SOUND) != null;
                } else if (notification.sound != null) {
                    soundUri = notification.sound;
                    hasValidSound = (soundUri != null);
                }

                if (hasValidSound) {
                    boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
                    int audioStreamType;
                    if (notification.audioStreamType >= 0) {
                        audioStreamType = notification.audioStreamType;
                    } else {
                        audioStreamType = DEFAULT_STREAM_TYPE;
                    }
                    mSoundNotification = r;
                    // do not play notifications if stream volume is 0
                    // (typically because ringer mode is silent) or if speech recognition is active.
                    if ((audioManager.getStreamVolume(audioStreamType) != 0)
                            && !audioManager.isSpeechRecognitionActive()) {
                        final long identity = Binder.clearCallingIdentity();
                        try {
                            final IRingtonePlayer player = mAudioService.getRingtonePlayer();
                            if (player != null) {
                                player.playAsync(soundUri, user, looping, audioStreamType);
                            }
                        } catch (RemoteException e) {
                        } finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    }
                }

                // vibrate
                // 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
                        && (audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE);

                // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
                final boolean useDefaultVibrate =
                        (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;

                if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
                        && !(audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT)) {
                    mVibrateNotification = r;

                    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(useDefaultVibrate ? mDefaultVibrationPattern
                                                                : mFallbackVibrationPattern,
                                ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
                        } finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    } else if (notification.vibrate.length > 1) {
                        // If you want your own vibration pattern, you need the VIBRATE permission
                        mVibrator.vibrate(notification.vibrate,
                            ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
                    }
                }
            }

            // this option doesn't shut off the lights

            // light
            // the most recent thing gets the light
            mLights.remove(old);
            if (mLedNotification == old) {
                mLedNotification = null;
            }
            //Slog.i(TAG, "notification.lights="
            //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
            if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
                    && canInterrupt) {
                mLights.add(r);
                updateLightsLocked();
            } else {
                if (old != null
                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
                    updateLightsLocked();
                }
            }
        }

        idOut[0] = id


这一段程序就有点又臭又长,但是其实很简单.

synchronized (mNotificationList) {
            NotificationRecord r = new NotificationRecord(pkg, tag, id, 
                    callingUid, callingPid, userId,
                    score,
                    notification);
            NotificationRecord old = null;

            int index = indexOfNotificationLocked(pkg, tag, id, userId);
            if (index < 0) {
                mNotificationList.add(r);
            } else {
                old = mNotificationList.remove(index);
                mNotificationList.add(index, r);
                // Make sure we don't lose the foreground service state.
                if (old != null) {
                    notification.flags |=
                        old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
                }
            }
上面的通过一个NotificationRecord结构体记录所有的通知信息并且保存,有点类似ActivityRecord等,这个***Record在Android系统里还是比较常见的.其中里面mNotificationList就是ArrayList,这个都是老套路了.

其实看到下面的就差不多了:

 if (notification.icon != 0) {
                final StatusBarNotification n = new StatusBarNotification(
                        pkg, id, tag, r.uid, r.initialPid, score, notification, user);
                if (old != null && old.statusBarKey != null) {
                    r.statusBarKey = old.statusBarKey;
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mStatusBar.updateNotification(r.statusBarKey, n);
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                } else {
                    long identity = Binder.clearCallingIdentity();
                    try {
                        r.statusBarKey = mStatusBar.addNotification(n);
                        if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
                                && canInterrupt) {
                            mAttentionLight.pulse();
                        }
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }

其他后面都是处理这个通知是否需要一个通知音播放,或者闪灯等一些视觉听觉方面的操作.

上面程序,其中StatusBarNotification是一个Parcel的数据结构体.

mStatusBar是StatusBarManagerService对象,

如果这个通知是第一次生成:

r.statusBarKey = mStatusBar.addNotification(n);
                        if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
                                && canInterrupt) {
                            mAttentionLight.pulse();
                        }


如果不是第一次了,那么就更新一次:

mStatusBar.updateNotification(r.statusBarKey, n);

他就会添加,看StatusBarManagerService类中:

// ================================================================================
    // Callbacks for NotificationManagerService.
    // ================================================================================
    public IBinder addNotification(StatusBarNotification notification) {
        synchronized (mNotifications) {
            IBinder key = new Binder();
            mNotifications.put(key, notification);
            if (mBar != null) {
                try {
                    mBar.addNotification(key, notification);
                } catch (RemoteException ex) {
                }
            }
            return key;
        }
    }


其实前面看到parcelable就需要联想到Binder这个提供储存的"代理商".

其中:

mBar.addNotification(key, notification);

这个mBar是一个IStatusBar对象,这个地方addNotification就是有CommandQueue类完成的:

    public void addNotification(IBinder key, StatusBarNotification notification) {
        synchronized (mList) {
            NotificationQueueEntry ne = new NotificationQueueEntry();
            ne.key = key;
            ne.notification = notification;
            mHandler.obtainMessage(MSG_ADD_NOTIFICATION, 0, 0, ne).sendToTarget();
        }
    }


防止堵塞,发送一个Handler事件,处理这个MSG_ADD_NOTIFICATION事件如下:

case MSG_ADD_NOTIFICATION: {
                    final NotificationQueueEntry ne = (NotificationQueueEntry)msg.obj;
                    mCallbacks.addNotification(ne.key, ne.notification);
                    break;
                }

其中:

mCallbacks.addNotification(ne.key, ne.notification);

是一个回调.

上面的通过StatusBarManagerService这个服务进程进行添加,更新和保存管理通知信息,在SystemUI工程中使用如下BastStatusBar.java类:

mBarService = IStatusBarService.Stub.asInterface(
                ServiceManager.getService(Context.STATUS_BAR_SERVICE));

// Connect in to the status bar manager service
        StatusBarIconList iconList = new StatusBarIconList();
        ArrayList notificationKeys = new ArrayList();
        ArrayList notifications = new ArrayList();
        mCommandQueue = new CommandQueue(this, iconList);

        int[] switches = new int[7];
        ArrayList binders = new ArrayList();
        try {
            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
                    switches, binders);
        } catch (RemoteException ex) {
            // If the system process isn't there we're doomed anyway.
        }
首先连接上面的服务,然后从服务中获取所有的通知List信息.

后面显示到状态栏上面:

protected StatusBarIconView addNotificationViews(IBinder key,
            StatusBarNotification notification)

通过它生产通知View,然后被状态面板添加进去.



下面还有几个细节需要注意:

<1> : 由何APP发出的通知,是有记录在案的,即通知同时附带了发送通知APP的所有信息,比如报名,UID之类的等,通知分为系统通知和普通应用通知.

<2> : 5.0版本以后的新版本,通知是有优先级的(当然mNotificationList这个NotificationRecord集合在手,也可以人为在老版本自己定义优先级规则,通过上面获取的APP信息判断哪些APP的通知可以在何种情况下被添加,然后同步到状态栏).

<3> : 5.0版本以后的新版本,通知需要考虑更多条件,程序里面用Conditions,以及很多手机已经在用的ZenMode,即勿扰模式,这里面约束条件还是挺多的(比如通知来了不亮屏,通知来了不播放提示音,即使设置了,等等),有兴趣的可以自行下载N版本的源代码去看一看.


至于里面涉及的SystemUI状态栏工程,在我个人看来就是APP Service+WindowManager链接各种系统Service撑起来的.

































你可能感兴趣的:(Android,源码分析)