博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Notification的显示过程
阅读量:4103 次
发布时间:2019-05-25

本文共 18413 字,大约阅读时间需要 61 分钟。

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

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

[html]
  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
= 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

[java]
  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,代码如下:

[java]
  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。在上一篇文章中中说过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中设置名称,否则系统无法显示状态图标。

转载地址:http://zgfsi.baihongyu.com/

你可能感兴趣的文章
前端设计之特效表单
查看>>
Java的时间操作玩法实例若干
查看>>
JavaScript:时间日期格式验证大全
查看>>
解决SimpleDateFormat线程安全问题NumberFormatException: multiple points
查看>>
MySQL数据库存储引擎简介
查看>>
处理Maven本地仓库.lastUpdated文件
查看>>
计算机网络-网络协议模型
查看>>
计算机网络-OSI各层概述
查看>>
Java--String/StringBuffer/StringBuilder区别
查看>>
分布式之redis复习精讲
查看>>
(python版)《剑指Offer》JZ01:二维数组中的查找
查看>>
(python版)《剑指Offer》JZ06:旋转数组的最小数字
查看>>
(python版)《剑指Offer》JZ13:调整数组顺序使奇数位于偶数前面
查看>>
(python版)《剑指Offer》JZ28:数组中出现次数超过一半的数字
查看>>
(python版)《剑指Offer》JZ30:连续子数组的最大和
查看>>
(python版)《剑指Offer》JZ02:替换空格
查看>>
JSP/Servlet——MVC设计模式
查看>>
使用JSTL
查看>>
Java 8新特性:Stream API
查看>>
管理用户状态——Cookie与Session
查看>>