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

应用程序中发送通知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撑起来的.































更多相关文章

  1. tcping测试服务器TCP端口
  2. android之socket编程实例一
  3. Android面试题总结加强版(二)
  4. Android(安卓)7.0修改系统时间
  5. Inflater
  6. Android开机自启动+屏蔽系统功能
  7. android Unterminated string at character 44 of
  8. Android(安卓)Service的生命周期2
  9. 32位机器Ubuntu系统编译android 内核注意修改点

随机推荐

  1. android 同时发送几条通知
  2. 自定义RatingBar的样式
  3. 记一次Android(安卓)OOM探险之旅
  4. 从网络获取图片,并缓存到SD卡
  5. Picasso学习
  6. 最新Android系统版本与API等级对应关系表
  7. Android AsyncTask 深入解析
  8. Android 中的定时事件使用
  9. Android(安卓)Notification.Builder通知
  10. Android利用JDBC连接服务器数据库