Notification的显示过程

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

  1. NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);  
  2. ……//创建notification 
  3. manager.notify(1, 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目录下面,方法如下:

  1. public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid, 
  2.             String tag, int id, int priority, Notification notification, int[] idOut) 
  3.     { 
  4.         checkIncomingCall(pkg); 
  5.  
  6.         // Limit the number of notifications that any given package except the android 
  7.         // package can enqueue.  Prevents DOS attacks and deals with leaks. 
  8.         if (!"android".equals(pkg)) { 
  9.             synchronized (mNotificationList) { 
  10.                 int count = 0
  11.                 final int N = mNotificationList.size(); 
  12.                 for (int i=0; i<N; i++) { 
  13.                     final NotificationRecord r = mNotificationList.get(i); 
  14.                     if (r.pkg.equals(pkg)) { 
  15.                         count++; 
  16.                         if (count >= MAX_PACKAGE_NOTIFICATIONS) { 
  17.                             Slog.e(TAG, "Package has already posted " + count 
  18.                                     + " notifications.  Not showing more.  package=" + pkg); 
  19.                             return; 
  20.                         } 
  21.                     } 
  22.                 } 
  23.             } 
  24.         } 
  25.  
  26.         // This conditional is a dirty hack to limit the logging done on 
  27.         //     behalf of the download manager without affecting other apps. 
  28.         if (!pkg.equals("com.android.providers.downloads") 
  29.                 || Log.isLoggable("DownloadManager", Log.VERBOSE)) { 
  30.             EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, 
  31.                     notification.toString()); 
  32.         } 
  33.  
  34.         if (pkg == null || notification == null) { 
  35.             throw new IllegalArgumentException("null not allowed: pkg=" + pkg 
  36.                     + " id=" + id + " notification=" + notification); 
  37.         } 
  38.         if (notification.icon != 0) { 
  39.             if (notification.contentView == null) { 
  40.                 throw new IllegalArgumentException("contentView required: pkg=" + pkg 
  41.                         + " id=" + id + " notification=" + notification); 
  42.             } 
  43.         } 
  44.  
  45.         synchronized (mNotificationList) { 
  46.             NotificationRecord r = new NotificationRecord(pkg, tag, id,  
  47.                     callingUid, callingPid,  
  48.                     priority, 
  49.                     notification); 
  50.             NotificationRecord old = null
  51.  
  52.             int index = indexOfNotificationLocked(pkg, tag, id); 
  53.             if (index < 0) { 
  54.                 mNotificationList.add(r); 
  55.             } else { 
  56.                 old = mNotificationList.remove(index); 
  57.                 mNotificationList.add(index, r); 
  58.                 // Make sure we don't lose the foreground service state. 
  59.                 if (old != null) { 
  60.                     notification.flags |= 
  61.                         old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE; 
  62.                 } 
  63.             } 
  64.  
  65.             // Ensure if this is a foreground service that the proper additional 
  66.             // flags are set. 
  67.             if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { 
  68.                 notification.flags |= Notification.FLAG_ONGOING_EVENT 
  69.                         | Notification.FLAG_NO_CLEAR; 
  70.             } 
  71.  
  72.             if (notification.icon != 0) { 
  73.                 StatusBarNotification n = new StatusBarNotification(pkg, id, tag, 
  74.                         r.uid, r.initialPid, notification); 
  75.                 n.priority = r.priority; 
  76.  
  77.                 if (old != null && old.statusBarKey != null) { 
  78.                     r.statusBarKey = old.statusBarKey; 
  79.                     long identity = Binder.clearCallingIdentity(); 
  80.                     try { 
  81.                         mStatusBar.updateNotification(r.statusBarKey, n); 
  82.                     } 
  83.                     finally { 
  84.                         Binder.restoreCallingIdentity(identity); 
  85.                     } 
  86.                 } else { 
  87.                     long identity = Binder.clearCallingIdentity(); 
  88.                     try { 
  89.                         r.statusBarKey = mStatusBar.addNotification(n); 
  90.                         mAttentionLight.pulse(); 
  91.                     } 
  92.                     finally { 
  93.                         Binder.restoreCallingIdentity(identity); 
  94.                     } 
  95.                 } 
  96.                 sendAccessibilityEvent(notification, pkg); 
  97.             } else { 
  98.                 Slog.e(TAG, "Ignoring notification with icon==0: " + notification); 
  99.                 if (old != null && old.statusBarKey != null) { 
  100.                     long identity = Binder.clearCallingIdentity(); 
  101.                     try { 
  102.                         mStatusBar.removeNotification(old.statusBarKey); 
  103.                     } 
  104.                     finally { 
  105.                         Binder.restoreCallingIdentity(identity); 
  106.                     } 
  107.                 } 
  108.             } 
  109.  
  110.             // If we're not supposed to beep, vibrate, etc. then don't. 
  111.             if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) 
  112.                     && (!(old != null 
  113.                         && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) 
  114.                     && mSystemReady) { 
  115.  
  116.                 final AudioManager audioManager = (AudioManager) mContext 
  117.                 .getSystemService(Context.AUDIO_SERVICE); 
  118.                 // sound 
  119.                 final boolean useDefaultSound
  120.                     (notification.defaults & Notification.DEFAULT_SOUND) != 0; 
  121.                 if (useDefaultSound || notification.sound != null) { 
  122.                     Uri uri; 
  123.                     if (useDefaultSound) { 
  124.                         uri = Settings.System.DEFAULT_NOTIFICATION_URI; 
  125.                     } else { 
  126.                         uri = notification.sound; 
  127.                     } 
  128.                     boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; 
  129.                     int audioStreamType; 
  130.                     if (notification.audioStreamType >= 0) { 
  131.                         audioStreamType = notification.audioStreamType; 
  132.                     } else { 
  133.                         audioStreamType = DEFAULT_STREAM_TYPE
  134.                     } 
  135.                     mSoundNotification = r
  136.                     // do not play notifications if stream volume is 0 
  137.                     // (typically because ringer mode is silent). 
  138.                     if (audioManager.getStreamVolume(audioStreamType) != 0) { 
  139.                         long identity = Binder.clearCallingIdentity(); 
  140.                         try { 
  141.                             mSound.play(mContext, uri, looping, audioStreamType); 
  142.                         } 
  143.                         finally { 
  144.                             Binder.restoreCallingIdentity(identity); 
  145.                         } 
  146.                     } 
  147.                 } 
  148.  
  149.                 // vibrate 
  150.                 final boolean useDefaultVibrate
  151.                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; 
  152.                 if ((useDefaultVibrate || notification.vibrate != null) 
  153.                         && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) { 
  154.                     mVibrateNotification = r
  155.  
  156.                     mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN 
  157.                                                         : notification.vibrate, 
  158.                               ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1); 
  159.                 } 
  160.             } 
  161.  
  162.             // this option doesn't shut off the lights 
  163.  
  164.             // light 
  165.             // the most recent thing gets the light 
  166.             mLights.remove(old); 
  167.             if (mLedNotification == old) { 
  168.                 mLedNotification = null
  169.             } 
  170.             //Slog.i(TAG, "notification.lights=" 
  171.             //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0)); 
  172.             if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) { 
  173.                 mLights.add(r); 
  174.                 updateLightsLocked(); 
  175.             } else { 
  176.                 if (old != null 
  177.                         && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) { 
  178.                     updateLightsLocked(); 
  179.                 } 
  180.             } 
  181.         } 
  182.  
  183.         idOut[0] = id; 
  184.     } 
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

  1. NotificationRecord r = new NotificationRecord(pkg, tag, id,  
  2.                     callingUid, callingPid,  
  3.                     priority, 
  4.                     notification); 
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,代码如下:

  1. if (notification.icon != 0) { 
  2.               StatusBarNotification n = new StatusBarNotification(pkg, id, tag, 
  3.                       r.uid, r.initialPid, notification); 
  4.               n.priority = r.priority; 
  5.  
  6.               if (old != null && old.statusBarKey != null) { 
  7.                   r.statusBarKey = old.statusBarKey; 
  8.                   long identity = Binder.clearCallingIdentity(); 
  9.                   try
  10.                       mStatusBar.updateNotification(r.statusBarKey, n); 
  11.                   } 
  12.                   finally
  13.                       Binder.restoreCallingIdentity(identity); 
  14.                   } 
  15.               } else
  16.                   long identity = Binder.clearCallingIdentity(); 
  17.                   try
  18.                       r.statusBarKey = mStatusBar.addNotification(n); 
  19.                       mAttentionLight.pulse(); 
  20.                   } 
  21.                   finally
  22.                       Binder.restoreCallingIdentity(identity); 
  23.                   } 
  24.               } 
  25.               sendAccessibilityEvent(notification, pkg); 
  26.           } else
  27.               Slog.e(TAG, "Ignoring notification with icon==0: " + notification); 
  28.               if (old != null && old.statusBarKey != null) { 
  29.                   long identity = Binder.clearCallingIdentity(); 
  30.                   try
  31.                       mStatusBar.removeNotification(old.statusBarKey); 
  32.                   } 
  33.                   finally
  34.                       Binder.restoreCallingIdentity(identity); 
  35.                   } 
  36.               } 
  37.           } 
  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中设置名称,否则系统无法显示状态图标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值