赞
踩
应用发送一个显示在状态栏上的通知,对于移动设备来说是很常见的一种功能需求,本篇文章我们将会结合Android9.0系统源码具体来分析一下,应用调用notificationManager触发通知栏通知功能的源码流程。
应用可以通过调用如下notity方法可以发送一个显示在状态栏上的通知。
/** * 发送通知(支持8.0+) */ public void nofify() { // 1. Set the notification content - 创建通知基本内容 // https://developer.android.google.cn/training/notify-user/build-notification.html#builder NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_background) .setContentTitle("My notification") // 这是单行 //.setContentText("Much longer text that cannot fit one line...") // 这是多行 .setStyle(new NotificationCompat.BigTextStyle() .bigText("Much longer text that cannot fit one line..." + "Much longer text that cannot fit one line..." + "Much longer text that cannot fit one line...")) .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true); // 2. Create a channel and set the importance - 8.0后需要设置Channel // https://developer.android.google.cn/training/notify-user/build-notification.html#builder createNotificationChannel(); // 3. Set the notification's tap action - 创建一些点击事件,比如点击跳转页面 // https://developer.android.google.cn/training/notify-user/build-notification.html#click // 4. Show the notification - 展示通知 // https://developer.android.google.cn/training/notify-user/build-notification.html#notify NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); // 5.调用notificationManager的notify方法 // notificationId is a unique int for each notification that you must define notificationManager.notify((int) System.currentTimeMillis(), builder.build()); } private void createNotificationChannel() { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library CharSequence name = getString(R.string.app_name); String description = getString(R.string.app_name); int importance = NotificationManager.IMPORTANCE_HIGH; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); // Register the channel with the system; you can't change the importance // or other notification behaviors after this NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); }
notify方法先是构建Notification对象,然后调用NotificationManager的notify方法发送构建的这个对象。
1、NotificationManager的notify方法如下所示:
frameworks/base/core/java/android/app/NotificationManager.java
public class NotificationManager {
//如果应用发送了一个相同id的通知,并且没有被取消,它将被更新的信息所取代。
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
//应用发送了一个相同tag和id的通知,并且没有被取消,它将被更新的信息所取代。
public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, mContext.getUser());
}
}
2、notify方法最终都会进一步调用notifyAsUser。
public class NotificationManager {
/**
* @hide
*/
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
...代码省略...
}
}
notifyAsUser方法首先获取NotificationManagerService服务,然后调用了Notification的addFieldsFromContext方法。
3、Notification的addFieldsFromContext方法如下所示:
frameworks/base/core/java/android/app/Notification.java
public class Notification implements Parcelable { public Bundle extras = new Bundle(); /** * @hide */ public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; /** * @hide */ public static void addFieldsFromContext(Context context, Notification notification) { addFieldsFromContext(context.getApplicationInfo(), notification); } /** * @hide */ public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); } }
主要是在Notification对象中类型为Bundle的属性变量extras中保存了当前应用所对应的ApplicationInfo对象。
4、继续往下看NotificationManager的notifyAsUser方法
public class NotificationManager { /** * @hide */ public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) { INotificationManager service = getService(); String pkg = mContext.getPackageName(); // Fix the notification as best we can. Notification.addFieldsFromContext(mContext, notification); if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); if (StrictMode.vmFileUriExposureEnabled()) { notification.sound.checkFileUriExposed("Notification.sound"); } } //通过包名获取对应包名的应用图标设置为通知的小图标 fixLegacySmallIcon(notification, pkg); if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { //Android5.1之后,会判断notification是否有small icon,没有则抛出异常 if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("Invalid notification (no valid small icon): " + notification); } } ...代码省略... try { service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, copy, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }
会调用fixLegacySmallIcon方法通,过包名获取对应包名的应用图标设置为通知的小图标,在Android5.1之后的版本中,然后会判断notification是否有small icon,如果没有设icon或small icon,用notify方法时会抛出异常,最终会调用NotificationManagerService的enqueueNotificationWithTag方法。
1、NotificationManagerService的enqueueNotificationWithTag方法如下所示。
frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
public class NotificationManagerService extends SystemService {
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, userId);
}
}
2、enqueueNotificationWithTag方法会继续调用了enqueueNotificationInternal方法,该方法发送通知的核心。
public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); } checkCallerIsSystemOrSameApp(pkg); // 校验UID final int userId = ActivityManager.handleIncomingUser(callingPid, callingUid, incomingUserId, true, false, "enqueueNotification", pkg); final UserHandle user = new UserHandle(userId); if (pkg == null || notification == null) { throw new IllegalArgumentException("null not allowed: pkg=" + pkg + " id=" + id + " notification=" + notification); } // The system can post notifications for any package, let us resolve that. final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId); ...代码省略... //上面会进行一系列验证,验证之后,会将传递进来的Notification封装成一个StatusBarNotification对象 final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, notificationUid, callingPid, notification, user, null, System.currentTimeMillis()); // 封装NotificationRecord对象 final NotificationRecord r = new NotificationRecord(getContext(), n, channel); ...代码省略... mHandler.post(new EnqueueNotificationRunnable(userId, r)); } }
enqueueNotificationInternal会进行一些列验证,待验证完成之后,会调用Handler的post方法开启线程,发起异步操作,触发EnqueueNotificationRunnable对象。
3、EnqueueNotificationRunnable对象如下所示。
public class NotificationManagerService extends SystemService { protected class EnqueueNotificationRunnable implements Runnable { private final NotificationRecord r; private final int userId; EnqueueNotificationRunnable(int userId, NotificationRecord r) { this.userId = userId; this.r = r; }; @Override public void run() { synchronized (mNotificationLock) { //将当前通知相关的NotificationRecord对象放到集合中 mEnqueuedNotifications.add(r); scheduleTimeoutLocked(r); final StatusBarNotification n = r.sbn; if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey()); //获取是否存在相同的NotificationRecord NotificationRecord old = mNotificationsByKey.get(n.getKey()); if (old != null) { // Retain ranking information from previous record r.copyRankingInformation(old); } final int callingUid = n.getUid(); final int callingPid = n.getInitialPid(); final Notification notification = n.getNotification(); final String pkg = n.getPackageName(); final int id = n.getId(); final String tag = n.getTag(); // Handle grouped notifications and bail out early if we // can to avoid extracting signals. handleGroupedNotificationLocked(r, old, callingUid, callingPid); // if this is a group child, unsnooze parent summary if (n.isGroup() && notification.isGroupChild()) { mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey()); } // 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)) { int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW; if (old != null) { enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE; } EventLogTags.writeNotificationEnqueue(callingUid, callingPid, pkg, id, tag, userId, notification.toString(), enqueueStatus); } mRankingHelper.extractSignals(r); // tell the assistant service about the notification if (mAssistants.isEnabled()) { mAssistants.onNotificationEnqueued(r); //开启延时线程 mHandler.postDelayed(new PostNotificationRunnable(r.getKey()), DELAY_FOR_ASSISTANT_TIME); } else { //开启线程 mHandler.post(new PostNotificationRunnable(r.getKey())); } } } } }
NotificationManagerService 的run方法会将当前NotificationRecord存放到类型为ArrayList的mEnqueuedNotifications集合中,最终会再次调用Handler的post方法开启线程,发起异步操作,触发PostNotificationRunnable对象。
4、PostNotificationRunnable对象如下所示。
public class NotificationManagerService extends SystemService { private NotificationListeners mListeners; private GroupHelper mGroupHelper; protected class PostNotificationRunnable implements Runnable { private final String key; PostNotificationRunnable(String key) { this.key = key; } @Override public void run() { synchronized (mNotificationLock) { try { NotificationRecord r = null; //从类型为ArrayList<NotificationRecord>的mEnqueuedNotifications集合中 //取当前key所对应的NotificationRecord对象 int N = mEnqueuedNotifications.size(); for (int i = 0; i < N; i++) { final NotificationRecord enqueued = mEnqueuedNotifications.get(i); if (Objects.equals(key, enqueued.getKey())) { r = enqueued; break; } } if (r == null) { Slog.i(TAG, "Cannot find enqueued record for key: " + key); return; } r.setHidden(isPackageSuspendedLocked(r)); NotificationRecord old = mNotificationsByKey.get(key); final StatusBarNotification n = r.sbn; final Notification notification = n.getNotification(); // 判断是否是已经发送过此notification int index = indexOfNotificationLocked(n.getKey()); if (index < 0) { //如果是新发送的notification,就走新增流程. mNotificationList.add(r); mUsageStats.registerPostedByApp(r); r.setInterruptive(isVisuallyInterruptive(null, r)); } else { //如果有发送过,就获取已经存在的NtificationRecord, // 后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n) old = mNotificationList.get(index); mNotificationList.set(index, r); mUsageStats.registerUpdatedByApp(r, old); // Make sure we don't lose the foreground service state. notification.flags |= old.getNotification().flags & FLAG_FOREGROUND_SERVICE; r.isUpdate = true; r.setTextChanged(isVisuallyInterruptive(old, r)); } //将当前NotificationRecord对象以StatusBarNotification为键存放到mNotificationsByKey中 mNotificationsByKey.put(n.getKey(), r); // Ensure if this is a foreground service that the proper additional // flags are set. if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) { notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; } applyZenModeLocked(r); mRankingHelper.sort(mNotificationList); if (notification.getSmallIcon() != null) { // 如果notification设置了smallIcon,调用所有NotificationListeners的notifyPostedLocked方法, // 通知有新的notification,传入的参数为上面的NotificationRecord对象 StatusBarNotification oldSbn = (old != null) ? old.sbn : null; //通知监听者回调方法 mListeners.notifyPostedLocked(r, old); if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) { mHandler.post(new Runnable() { @Override public void run() { mGroupHelper.onNotificationPosted( n, hasAutoGroupSummaryLocked(n)); } }); } } else { //移除已经存在的 Slog.e(TAG, "Not posting notification without small icon: " + notification); if (old != null && !old.isCanceled) { mListeners.notifyRemovedLocked(r, NotificationListenerService.REASON_ERROR, null); mHandler.post(new Runnable() { @Override public void run() { mGroupHelper.onNotificationRemoved(n); } }); } // ATTENTION: in a future release we will bail out here // so that we do not play sounds, show lights, etc. for invalid // notifications Slog.e(TAG, "WARNING: In a future release this will crash the app: " + n.getPackageName()); } if (!r.isHidden()) { //如果不是隐藏,则触发振动/铃声/呼吸灯 buzzBeepBlinkLocked(r); } maybeRecordInterruptionLocked(r); } finally { int N = mEnqueuedNotifications.size(); for (int i = 0; i < N; i++) { final NotificationRecord enqueued = mEnqueuedNotifications.get(i); if (Objects.equals(key, enqueued.getKey())) { mEnqueuedNotifications.remove(i); break; } } } } } } }
5、如果验证通过,还会调用buzzBeepBlinkLocked方法触发震动、铃声、呼吸灯
public class NotificationManagerService extends SystemService { //触发振动/铃声/呼吸灯 void buzzBeepBlinkLocked(NotificationRecord record) { boolean buzz = false;//震动 boolean beep = false;//铃声 boolean blink = false;//呼吸灯 final Notification notification = record.sbn.getNotification(); final String key = record.getKey(); // Should this notification make noise, vibe, or use the LED? final boolean aboveThreshold = record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT; // Remember if this notification already owns the notification channels. boolean wasBeep = key != null && key.equals(mSoundNotificationKey); boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey); // These are set inside the conditional if the notification is allowed to make noise. boolean hasValidVibrate = false; boolean hasValidSound = false; boolean sentAccessibilityEvent = false; // If the notification will appear in the status bar, it should send an accessibility // event if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) { sendAccessibilityEvent(notification, record.sbn.getPackageName()); sentAccessibilityEvent = true; } if (aboveThreshold && isNotificationForCurrentUser(record)) { if (mSystemReady && mAudioManager != null) { Uri soundUri = record.getSound(); hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri); long[] vibration = record.getVibration(); // Demote sound to vibration if vibration missing & phone in vibration mode. if (vibration == null && hasValidSound && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) && mAudioManager.getStreamVolume( AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) { vibration = mFallbackVibrationPattern; } hasValidVibrate = vibration != null; boolean hasAudibleAlert = hasValidSound || hasValidVibrate; if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) { if (!sentAccessibilityEvent) { sendAccessibilityEvent(notification, record.sbn.getPackageName()); sentAccessibilityEvent = true; } if (DBG) Slog.v(TAG, "Interrupting!"); if (hasValidSound) { mSoundNotificationKey = key; if (mInCall) { playInCallNotification(); beep = true; } else { //播放铃声 beep = playSound(record, soundUri); } } final boolean ringerModeSilent = mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT; if (!mInCall && hasValidVibrate && !ringerModeSilent) { mVibrateNotificationKey = key; //震动 buzz = playVibration(record, vibration, hasValidSound); } } } } // If a notification is updated to remove the actively playing sound or vibrate, // cancel that feedback now if (wasBeep && !hasValidSound) { clearSoundLocked(); } if (wasBuzz && !hasValidVibrate) { clearVibrateLocked(); } // light // release the light // 呼吸灯 boolean wasShowLights = mLights.remove(key); if (record.getLight() != null && aboveThreshold && ((record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_LIGHTS) == 0)) { mLights.add(key); updateLightsLocked(); if (mUseAttentionLight) { mAttentionLight.pulse(); } blink = true; } else if (wasShowLights) { updateLightsLocked(); } //应用请求了振动/铃声/呼吸灯,输出notification_alert日志 if (buzz || beep || blink) { record.setInterruptive(true); MetricsLogger.action(record.getLogMaker() .setCategory(MetricsEvent.NOTIFICATION_ALERT) .setType(MetricsEvent.TYPE_OPEN) .setSubtype((buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0))); EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。