Notification的显示过程

  众所周知,notification是在状态栏上显示的可以定制声音,震动,Led灯,单击跳转,显示内容的通知。通常应用中要发送一个notification都是通过以下方式:

NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 
……//创建notification
manager.notify(1, notification);

我们可以在创建notification时指定notification的声音,震动,Led灯,单击跳转,显示内容,通过NotificationManager发送出去,最终在状态栏上显示,在显示过程中对声音,震动,Led灯做同步处理。

  先来看看NotificationManager发送通知后的显示时序图:


   从时序图上看出,同android所有的Manager类一样,最终都是通过调用Framework中的Service类实现所要完成的操作。下面详细描述每一步所完成的操作:

   1,在调用NotificationManager.notify后,NotificationManager通过内部的NotificationManagerService代理处理后续的发送任务,这里调用的是NotificationManagerService.enqueueNotification.

 2,NotificationManagerService.enqueueNotification中反复调用了自己的几个方法,最终调用的是enqueueNotificationInternal发送,这个方法里完成了所有对notification发送的处理,也包括了对notification的更新。此类在Frameworks的services目录下面,方法如下:

public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
            String tag, int id, int priority, Notification notification, int[] idOut)
    {
        checkIncomingCall(pkg);

        // Limit the number of notifications that any given package except the android
        // package can enqueue.  Prevents DOS attacks and deals with leaks.
        if (!"android".equals(pkg)) {
            synchronized (mNotificationList) {
                int count = 0;
                final int N = mNotificationList.size();
                for (int i=0; i<N; i++) {
                    final NotificationRecord r = mNotificationList.get(i);
                    if (r.pkg.equals(pkg)) {
                        count++;
                        if (count >= 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,
                    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);
            }
        }

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

            int index = indexOfNotificationLocked(pkg, tag, id);
            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;
            }

            if (notification.icon != 0) {
                StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
                        r.uid, r.initialPid, notification);
                n.priority = r.priority;

                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);
                        mAttentionLight.pulse();
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
                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 ))
                    && mSystemReady) {

                final AudioManager audioManager = (AudioManager) mContext
                .getSystemService(Context.AUDIO_SERVICE);
                // sound
                final boolean useDefaultSound =
                    (notification.defaults & Notification.DEFAULT_SOUND) != 0;
                if (useDefaultSound || notification.sound != null) {
                    Uri uri;
                    if (useDefaultSound) {
                        uri = Settings.System.DEFAULT_NOTIFICATION_URI;
                    } else {
                        uri = notification.sound;
                    }
                    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).
                    if (audioManager.getStreamVolume(audioStreamType) != 0) {
                        long identity = Binder.clearCallingIdentity();
                        try {
                            mSound.play(mContext, uri, looping, audioStreamType);
                        }
                        finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    }
                }

                // vibrate
                final boolean useDefaultVibrate =
                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
                if ((useDefaultVibrate || notification.vibrate != null)
                        && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
                    mVibrateNotification = r;

                    mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
                                                        : 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) {
                mLights.add(r);
                updateLightsLocked();
            } else {
                if (old != null
                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
                    updateLightsLocked();
                }
            }
        }

        idOut[0] = id;
    }

3,首先是对Notification的属性做了验证,同一个应用发送notification数量不能超过MAX_PACKAGE_NOTIFICATIONS的值99,其余的验证包括notification不为空,contentView 不为空的验证,条件都满足后先创建一个NotificationRecord

NotificationRecord r = new NotificationRecord(pkg, tag, id, 
                    callingUid, callingPid, 
                    priority,
                    notification);
通过 indexOfNotificationLocked(pkg, tag, id)获取是否已经发送过此notification,如果有发送过,就获取oldNtificationRecord,后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n);如果是新发送的notification就走新增流程r.statusBarKey = mStatusBar.addNotification(n); 如果notification的icon为空,并且存在旧的NotificationRecord,就调用mStatusBar.removeNotification(old.statusBarKey)取消这个notification,代码如下:

  if (notification.icon != 0) {
                StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
                        r.uid, r.initialPid, notification);
                n.priority = r.priority;

                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);
                        mAttentionLight.pulse();
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
                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);
                    }
                }
            }
这里我们走notification的新增流程 mStatusBar.addNotification(n),此mStatusBar是系统启动时创建NotificationManagerService中传的StatusBarManagerService。在上一篇文章中SystemUI的启动过程中说过StatusBarManagerService中的对statusBar的操作都是通过IStatusBar的接口实现类StatusBar完成的,所以addNotification最终是在StatusBar的子类PhoneStatusBar中完成。

4,PhoneStatusBar在SystemUI的包里面,在它的addNotification方法中完成了所有Notification在状态栏上的显示操作。对PhoneStatusBar.addNotification的调用是采用Handle的方式,此Handle的Message成功发送后,StatusBarManagerService的addNotification就返回了, 然后再返回到NotificationManagerService的调用处,接着处理后续的声音播放NotificationPlayer.play、震动Vibrator.vibrator、Led指示灯updateLightsLocked,这些都是和PhoneStatusBar的addNotification同时在进行,当Notification在显示的时候,声音,震动,Led的处理就同步完成了。

5,在PhoneStatusBar.addNotification中调用addNotificationView将notification的icon添加到statusBar,还有notification在ExpandedView(状态栏拉开的那个界面)中显示的View也是在此方法中创建。

接着调用tick方法,显示Notification的tick提示,就是notification显示时在状态栏上滚动的那个效果。

至此,Notification的显示就完成了。StatusBar上除了Notification的显示还有StatusIcon(状态图标),状态图标的显示比较简单。系统的状态图标都是在SystemUI中控制显示,通过注册监听,接收系统广播控制图标的显示。SystemUI在启动时系统设置的状态图标都会加入到状态栏的view中,但不会设置显示,当相应事件发生后,注册的监听或广播就会设置相应图标显示。要显示的状态图标都需要现在/frameworks/base/core/values/config.xml中设置名称,否则系统无法显示状态图标。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值